From a7ad1066cf3e2289559f21758114f8715e2d4e31 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 7 Oct 2026 15:56:52 -0400 Subject: [PATCH] Add self-compilation tools and CI runners [GH #219] --- .../actions/build-softfloat-msvc/action.yml | 122 +++++++++ .github/workflows/self-compile-ci.yml | 94 +++++++ .github/workflows/strict-build-check.yml | 106 +------- BUILD.md | 25 ++ dev_tools/bin/edg-compare-gen-c | 198 ++++++++++++++ dev_tools/typecheck-python.sh | 1 + dev_tools/win-bin/edg-compare-gen-c.bat | 2 + src/CMakeLists.txt | 9 + src/cmake/cpfe-self/CMakeLists.txt | 250 ++++++++++++++++++ src/cmake/cpfe-self/runtime_vars.c | 24 ++ 10 files changed, 727 insertions(+), 104 deletions(-) create mode 100644 .github/actions/build-softfloat-msvc/action.yml create mode 100644 .github/workflows/self-compile-ci.yml create mode 100755 dev_tools/bin/edg-compare-gen-c create mode 100644 dev_tools/win-bin/edg-compare-gen-c.bat create mode 100644 src/cmake/cpfe-self/CMakeLists.txt create mode 100644 src/cmake/cpfe-self/runtime_vars.c diff --git a/.github/actions/build-softfloat-msvc/action.yml b/.github/actions/build-softfloat-msvc/action.yml new file mode 100644 index 0000000000..6670f6641a --- /dev/null +++ b/.github/actions/build-softfloat-msvc/action.yml @@ -0,0 +1,122 @@ +# Part of the EDG Compiler Project, under the Apache License v2.0 with LLVM +# Exceptions. +# See https://edgcpp.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# Build SoftFloat (at the commit pinned in +# dev_tools/docker/softfloat/SOFTFLOAT_COMMIT) with MSVC, leaving the headers +# in softfloat/include and the library in softfloat/lib/softfloat.lib under +# the workspace. A cached build is reused when one is available. The MSVC +# developer environment must already be set up (e.g., with +# ilammy/msvc-dev-cmd). + +name: Build SoftFloat (MSVC) +description: Build (or restore from the cache) SoftFloat with MSVC. + +runs: + using: composite + steps: + - name: Read SoftFloat commit pin + id: softfloat-pin + shell: bash + run: | + echo "commit=$(tr -d '[:space:]' < dev_tools/docker/softfloat/SOFTFLOAT_COMMIT)" \ + >> "${GITHUB_OUTPUT}" + + - name: Cache SoftFloat + id: cache-softfloat + uses: actions/cache@v6 + with: + path: softfloat + key: softfloat-msvc-win-${{ steps.softfloat-pin.outputs.commit }} + + - name: Build SoftFloat (MSVC) + if: steps.cache-softfloat.outputs.cache-hit != 'true' + shell: bash + run: | + set -euo pipefail + commit="${{ steps.softfloat-pin.outputs.commit }}" + mkdir softfloat-src + git -C softfloat-src init + git -C softfloat-src remote add origin \ + https://github.com/ucb-bar/berkeley-softfloat-3.git + git -C softfloat-src fetch --depth 1 origin "${commit}" + git -C softfloat-src checkout FETCH_HEAD + + # EDG Windows SoftFloat build: cl + lib (not MinGW). Emit MSVC + # platform bits inline; derive sources.rsp from upstream's + # Win32-SSE2-MinGW Makefile (8086-SSE / non-FAST_INT64). + build_dir=softfloat-src/build/Win-MSVC + mkdir -p "${build_dir}" + + cat > "${build_dir}/platform.h" <<'EOF' + /*============================================================================ + MSVC platform header for SoftFloat. Keep free of GCC-only opts + (opts-GCC.h, SOFTFLOAT_BUILTIN_CLZ, SOFTFLOAT_INTRINSIC_INT128). + =============================================================================*/ + + #define LITTLEENDIAN 1 + + #ifdef _MSC_VER + #define INLINE static __inline + #else + #define INLINE static inline + #endif + EOF + + # Strip the leading spaces YAML left in the heredoc body. + sed -i 's/^ //' "${build_dir}/platform.h" + + cat > "${build_dir}/build.bat" <<'EOF' + REM In place of a Makefile, use this batch file to create softfloat.lib + REM Source paths live in sources.rsp to avoid CreateProcess length limits. + + set LIB=softfloat.lib + set SOFTFLOAT_OPTS=-DSOFTFLOAT_ROUND_ODD -DINLINE_LEVEL=5 -DSOFTFLOAT_FAST_DIV32TO16 -DSOFTFLOAT_FAST_DIV64TO32 + set SPECIALIZE_TYPE=8086-SSE + set SOURCE=..\..\source + set S=%SOURCE%\ + set P=%SOURCE%\%SPECIALIZE_TYPE%\ + + del *.obj %LIB% 2>nul + cl -nologo -c -O2 -I. -I%S% -I%P% -I%S%include %SOFTFLOAT_OPTS% @sources.rsp + if errorlevel 1 exit /b 1 + lib -nologo /out:%LIB% *.obj + if errorlevel 1 exit /b 1 + EOF + sed -i 's/^ //' "${build_dir}/build.bat" + + python - <<'PY' + import re + from pathlib import Path + + makefile = Path( + 'softfloat-src/build/Win32-SSE2-MinGW/Makefile' + ).read_text() + + def section(name): + match = re.search( + rf'{name}\s*=\s*((?:.*\\\n)*.*)', makefile + ) + return re.findall(r'(\w+)\$\(OBJ\)', match.group(1)) + + paths = ( + [fr'..\..\source\{n}.c' for n in section('OBJS_PRIMITIVES')] + + [ + fr'..\..\source\8086-SSE\{n}.c' + for n in section('OBJS_SPECIALIZE') + ] + + [fr'..\..\source\{n}.c' for n in section('OBJS_OTHERS')] + ) + Path('softfloat-src/build/Win-MSVC/sources.rsp').write_text( + '\r\n'.join(paths) + '\r\n' + ) + PY + + cmd.exe //C "cd softfloat-src\\build\\Win-MSVC && build.bat" + + mkdir -p softfloat/include softfloat/lib + cp softfloat-src/source/include/softfloat.h \ + softfloat-src/source/include/softfloat_types.h \ + softfloat/include/ + cp softfloat-src/build/Win-MSVC/softfloat.lib softfloat/lib/ diff --git a/.github/workflows/self-compile-ci.yml b/.github/workflows/self-compile-ci.yml new file mode 100644 index 0000000000..addb04b8a5 --- /dev/null +++ b/.github/workflows/self-compile-ci.yml @@ -0,0 +1,94 @@ +# Part of the EDG Compiler Project, under the Apache License v2.0 with LLVM +# Exceptions. +# See https://edgcpp.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# Check that the front end can still be built from the C that its +# C-generating back end produces for the front end's own sources, which is how +# C++ code is built for a system that has only a C compiler. Each job builds +# cpfe with the host C++ compiler, uses it to translate the front end to C, +# builds cpfe-self from that C with the host C compiler, and then checks that +# cpfe-self translates the front end to the same C as cpfe (see +# src/cmake/cpfe-self/CMakeLists.txt). + +name: Self-Compile CI + +on: + push: + branches: + - main + pull_request: + +jobs: + linux: + name: Linux ${{ matrix.name }} + runs-on: ubuntu-latest + timeout-minutes: 120 + strategy: + fail-fast: false + matrix: + include: + - name: GCC x64 + preset: linux-gcc-debug + build-dir: build/gcc + base: gcc + - name: GCC x32 + preset: linux-i686-gcc-debug + build-dir: build/gcc-i686 + base: gcc-i686 + - name: Clang x64 + preset: linux-clang-debug + build-dir: build/clang + base: clang + container: + image: docker.io/edgcpp/ci-runner:latest + defaults: + run: + shell: edg-container-exec "bash -eo pipefail {0}" + env: + EDG_BASE: ${{ github.workspace }}/bases/docker/dev-env/${{ matrix.base }} + + steps: + - name: Check out repository + uses: actions/checkout@v7 + + - name: Configure ${{ matrix.preset }} + run: cmake --preset ${{ matrix.preset }} -DEDG_SELF_COMPILE=ON + + - name: Build cpfe-self + run: cmake --build ${{ matrix.build-dir }} --target cpfe-self + + - name: Check cpfe-self + run: cmake --build ${{ matrix.build-dir }} --target cpfe-self-check + + windows-msvc: + name: Windows MSVC x64 + runs-on: windows-latest + timeout-minutes: 120 + env: + EDG_SOFTFLOAT_INCLUDE_PATH: ${{ github.workspace }}/softfloat/include + EDG_SOFTFLOAT_LIB_PATH: ${{ github.workspace }}/softfloat/lib/softfloat.lib + + steps: + - name: Check out repository + uses: actions/checkout@v7 + + - name: Install Ninja + run: choco install ninja -y + + - name: Set up MSVC developer environment + uses: ilammy/msvc-dev-cmd@v1 + + - name: Build SoftFloat + uses: ./.github/actions/build-softfloat-msvc + + # The debug macro configuration does not use Microsoft bit-field + # allocation, so its layouts do not match the code that MSVC generates. + - name: Configure windows-msvc-release + run: cmake --preset windows-msvc-release -DEDG_SELF_COMPILE=ON + + - name: Build cpfe-self + run: cmake --build build/msvc-release --target cpfe-self + + - name: Check cpfe-self + run: cmake --build build/msvc-release --target cpfe-self-check diff --git a/.github/workflows/strict-build-check.yml b/.github/workflows/strict-build-check.yml index 8fe66727b1..99945ce752 100644 --- a/.github/workflows/strict-build-check.yml +++ b/.github/workflows/strict-build-check.yml @@ -26,113 +26,11 @@ jobs: - name: Install Ninja run: choco install ninja -y - - name: Read SoftFloat commit pin - id: softfloat-pin - shell: bash - run: | - echo "commit=$(tr -d '[:space:]' < dev_tools/docker/softfloat/SOFTFLOAT_COMMIT)" \ - >> "${GITHUB_OUTPUT}" - - - name: Cache SoftFloat - id: cache-softfloat - uses: actions/cache@v6 - with: - path: softfloat - key: softfloat-msvc-win-${{ steps.softfloat-pin.outputs.commit }} - - name: Set up MSVC developer environment uses: ilammy/msvc-dev-cmd@v1 - - name: Build SoftFloat (MSVC) - if: steps.cache-softfloat.outputs.cache-hit != 'true' - shell: bash - run: | - set -euo pipefail - commit="${{ steps.softfloat-pin.outputs.commit }}" - mkdir softfloat-src - git -C softfloat-src init - git -C softfloat-src remote add origin \ - https://github.com/ucb-bar/berkeley-softfloat-3.git - git -C softfloat-src fetch --depth 1 origin "${commit}" - git -C softfloat-src checkout FETCH_HEAD - - # EDG Windows SoftFloat build: cl + lib (not MinGW). Emit MSVC - # platform bits inline; derive sources.rsp from upstream's - # Win32-SSE2-MinGW Makefile (8086-SSE / non-FAST_INT64). - build_dir=softfloat-src/build/Win-MSVC - mkdir -p "${build_dir}" - - cat > "${build_dir}/platform.h" <<'EOF' - /*============================================================================ - MSVC platform header for SoftFloat. Keep free of GCC-only opts - (opts-GCC.h, SOFTFLOAT_BUILTIN_CLZ, SOFTFLOAT_INTRINSIC_INT128). - =============================================================================*/ - - #define LITTLEENDIAN 1 - - #ifdef _MSC_VER - #define INLINE static __inline - #else - #define INLINE static inline - #endif - EOF - - # Strip the leading spaces YAML left in the heredoc body. - sed -i 's/^ //' "${build_dir}/platform.h" - - cat > "${build_dir}/build.bat" <<'EOF' - REM In place of a Makefile, use this batch file to create softfloat.lib - REM Source paths live in sources.rsp to avoid CreateProcess length limits. - - set LIB=softfloat.lib - set SOFTFLOAT_OPTS=-DSOFTFLOAT_ROUND_ODD -DINLINE_LEVEL=5 -DSOFTFLOAT_FAST_DIV32TO16 -DSOFTFLOAT_FAST_DIV64TO32 - set SPECIALIZE_TYPE=8086-SSE - set SOURCE=..\..\source - set S=%SOURCE%\ - set P=%SOURCE%\%SPECIALIZE_TYPE%\ - - del *.obj %LIB% 2>nul - cl -nologo -c -O2 -I. -I%S% -I%P% -I%S%include %SOFTFLOAT_OPTS% @sources.rsp - if errorlevel 1 exit /b 1 - lib -nologo /out:%LIB% *.obj - if errorlevel 1 exit /b 1 - EOF - sed -i 's/^ //' "${build_dir}/build.bat" - - python - <<'PY' - import re - from pathlib import Path - - makefile = Path( - 'softfloat-src/build/Win32-SSE2-MinGW/Makefile' - ).read_text() - - def section(name): - match = re.search( - rf'{name}\s*=\s*((?:.*\\\n)*.*)', makefile - ) - return re.findall(r'(\w+)\$\(OBJ\)', match.group(1)) - - paths = ( - [fr'..\..\source\{n}.c' for n in section('OBJS_PRIMITIVES')] - + [ - fr'..\..\source\8086-SSE\{n}.c' - for n in section('OBJS_SPECIALIZE') - ] - + [fr'..\..\source\{n}.c' for n in section('OBJS_OTHERS')] - ) - Path('softfloat-src/build/Win-MSVC/sources.rsp').write_text( - '\r\n'.join(paths) + '\r\n' - ) - PY - - cmd.exe //C "cd softfloat-src\\build\\Win-MSVC && build.bat" - - mkdir -p softfloat/include softfloat/lib - cp softfloat-src/source/include/softfloat.h \ - softfloat-src/source/include/softfloat_types.h \ - softfloat/include/ - cp softfloat-src/build/Win-MSVC/softfloat.lib softfloat/lib/ + - name: Build SoftFloat + uses: ./.github/actions/build-softfloat-msvc - name: Configure windows-msvc-release env: diff --git a/BUILD.md b/BUILD.md index 5dd7bc77b5..14415c617e 100644 --- a/BUILD.md +++ b/BUILD.md @@ -61,3 +61,28 @@ print: simply enter that directory and run the `ninja` build tool (or your specified build tool if you chose to use an alternative build generator). +## Building the Front End from Generated C + +The C-generating back end allows C++ code to be built for systems that have +only a C compiler, and the front end itself can be built that way. Configuring +with `-DEDG_SELF_COMPILE=ON` adds two targets: + +- `cpfe-self`: `cpfe` translates the front end's sources to C, and the C + compiler builds that C into `cpfe-self`. +- `cpfe-self-check`: `cpfe-self` translates the same sources, and the result is + compared with the C generated by `cpfe` (ignoring the differences that are + expected between any two runs; see `dev_tools/bin/edg-compare-gen-c`). + +For example: + +``` +cmake --preset 'linux-gcc-debug' -DEDG_SELF_COMPILE=ON +cmake --build build/gcc --target cpfe-self-check +``` + +This is supported with GCC, Clang, and MSVC and a single-configuration build +generator such as ninja. With MSVC, use a release preset: the debug +configurations do not use Microsoft bit-field allocation, so the front end +would not lay out structs as MSVC does. The "Self-Compile CI" GitHub workflow +runs these targets with GCC (x64 and x32) and Clang on Linux and with MSVC +(x64) on Windows. diff --git a/dev_tools/bin/edg-compare-gen-c b/dev_tools/bin/edg-compare-gen-c new file mode 100755 index 0000000000..dc611cb9a5 --- /dev/null +++ b/dev_tools/bin/edg-compare-gen-c @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 + +# Part of the EDG Compiler Project, under the Apache License v2.0 with LLVM +# Exceptions. +# See https://edgcpp.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +import argparse +import re +import sys + +from pathlib import Path +from typing import Iterator, List, Optional, Tuple + +CMD_DESCRIPTION = ''' +Compare the C code produced by two runs of the C-generating back end. + +Two front ends built from the same sources must generate the same C for the +same input, but some parts of the output are expected to differ from run to +run: + + - the date and time in the header comment (and in the expansions of + __DATE__ and __TIME__); + - the names of temporaries, which are formed from IL pointer values + (e.g., "__T139870915871024"); + - where long lines are wrapped (which depends on the lengths of those + names), including the line directives and split comments that wrapping + introduces. + +The comparison ignores comments, line directives, white space, the digits of +temporary names, and date and time strings. Everything else must match token +for token. + +For example, in a build configured with -DEDG_SELF_COMPILE=ON, the following +compares the C generated by cpfe and by cpfe-self (the front end compiled +from that C): + + edg-compare-gen-c src/cmake/cpfe-self/cpfe-c src/cmake/cpfe-self/cpfe-self-c +''' + +# This regex splits generated C into the pieces relevant to the comparison. +# Comments, line directives (e.g., '# 123 "file.c"'), and white space are +# ignored; string and character literals are kept whole so that their contents +# are compared exactly. +_TOKEN_REGEX = re.compile( + r''' + (?P/\*.*?\*/) + | (?P^[ \t]*\#[ \t]*(?:line[ \t]+)?[0-9][^\n]*$) + | (?P\s+) + | (?P"(?:\\.|[^"\\\n])*"|'(?:\\.|[^'\\\n])*') + | (?P[A-Za-z_][A-Za-z_0-9]*) + | (?P.) + ''', + re.DOTALL | re.MULTILINE | re.VERBOSE +) + +_IGNORED_TOKEN_KINDS = ('comment', 'line_directive', 'white_space') + +# Temporary names are "__T" followed by a number derived from an IL pointer. +_TEMPORARY_NAME_REGEX = re.compile(r'__T[0-9]+') + +# The expansions of __DATE__ (e.g., "Oct 7 2026") and __TIME__ (e.g., +# "17:16:23"). +_DATE_OR_TIME_REGEX = re.compile( + r'"(?:[A-Z][a-z]{2} [ 0-9][0-9] [0-9]{4}|[0-9]{2}:[0-9]{2}:[0-9]{2})"' +) + +# The number of tokens shown on each side of the first difference. +_CONTEXT_TOKEN_COUNT = 12 + +def _parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description = CMD_DESCRIPTION, + formatter_class = argparse.RawDescriptionHelpFormatter + ) + parser.add_argument( + 'first_dir', + type = Path, + help = 'the directory containing the first set of generated C files' + ) + parser.add_argument( + 'second_dir', + type = Path, + help = 'the directory containing the second set of generated C files' + ) + parser.add_argument( + 'file_names', + nargs = '*', + help = ( + 'the names of the files to compare (by default, every "*.c" file in ' + 'first_dir)' + ) + ) + return parser.parse_args() + +def _normalized_tokens(file_path: Path) -> Iterator[Tuple[str, int]]: + ''' + Yield the tokens of the generated C file at file_path that take part in the + comparison (with the expected run-to-run differences normalized away), each + paired with its line number. + ''' + # Generated C need not be valid UTF-8 (e.g., it may contain string literals + # in other encodings), so read it as Latin-1, which maps every byte. + text = file_path.read_text(encoding = 'latin-1') + line_number = 1 + for match in _TOKEN_REGEX.finditer(text): + kind = match.lastgroup + token = match.group() + token_line_number = line_number + line_number += token.count('\n') + if kind in _IGNORED_TOKEN_KINDS: + continue + if kind == 'identifier' and _TEMPORARY_NAME_REGEX.fullmatch(token): + token = '__T' + elif kind == 'literal' and _DATE_OR_TIME_REGEX.fullmatch(token): + token = '""' + yield token, token_line_number + +def _tokenize(file_path: Path) -> Tuple[List[str], List[int]]: + ''' + Return the normalized tokens of the generated C file at file_path and their + line numbers. + ''' + tokens: List[str] = [] + line_numbers: List[int] = [] + for token, line_number in _normalized_tokens(file_path): + tokens.append(token) + line_numbers.append(line_number) + return tokens, line_numbers + +def _describe_location(file_path: Path, tokens: List[str], + line_numbers: List[int], index: int) -> str: + ''' + Return a description of where the token at index is in the generated C file + at file_path (whose normalized tokens and their line numbers are given), + along with the surrounding tokens. + ''' + if index < len(tokens): + location = f"{file_path}, line {line_numbers[index]}" + else: + location = f"{file_path}, at the end" + start = max(0, index - _CONTEXT_TOKEN_COUNT) + context = ' '.join(tokens[start:index + _CONTEXT_TOKEN_COUNT]) + return f" {location}:\n ... {context} ..." + +def _describe_difference(first_path: Path, + second_path: Path) -> Optional[str]: + ''' + Return None if the two generated C files match, and otherwise a description + of the first difference between them. + ''' + first_tokens, first_line_numbers = _tokenize(first_path) + second_tokens, second_line_numbers = _tokenize(second_path) + if first_tokens == second_tokens: + return None + index = 0 + while (index < len(first_tokens) and index < len(second_tokens) and + first_tokens[index] == second_tokens[index]): + index += 1 + return '\n'.join([ + f"{first_path.name}: the generated C differs:", + _describe_location(first_path, first_tokens, first_line_numbers, index), + _describe_location(second_path, second_tokens, second_line_numbers, + index) + ]) + +def main() -> None: + args = _parse_args() + file_names: List[str] = args.file_names + if not file_names: + file_names = sorted(path.name for path in args.first_dir.glob('*.c')) + if not file_names: + sys.exit(f"error: no generated C files found in {args.first_dir}") + + mismatch_count = 0 + for file_name in file_names: + first_path = args.first_dir / file_name + second_path = args.second_dir / file_name + missing_paths = [path for path in (first_path, second_path) + if not path.is_file()] + if missing_paths: + for path in missing_paths: + print(f"{file_name}: missing {path}") + mismatch_count += 1 + continue + difference = _describe_difference(first_path, second_path) + if difference is not None: + print(difference) + mismatch_count += 1 + + print( + f"{len(file_names) - mismatch_count} of {len(file_names)} generated C " + 'files match.' + ) + if mismatch_count != 0: + sys.exit(1) + +main() diff --git a/dev_tools/typecheck-python.sh b/dev_tools/typecheck-python.sh index 4772b2f9d0..f6a601a7ca 100755 --- a/dev_tools/typecheck-python.sh +++ b/dev_tools/typecheck-python.sh @@ -57,6 +57,7 @@ pushd bin/ > /dev/null check_types_strict edg-bench-run-delta check_types_strict edg-bench-run-save check_types edg-cmakedef + check_types_strict edg-compare-gen-c check_types_strict edg-docker-bench check_types_strict edg-docker-test check_types edg-examine-test diff --git a/dev_tools/win-bin/edg-compare-gen-c.bat b/dev_tools/win-bin/edg-compare-gen-c.bat new file mode 100644 index 0000000000..883315eece --- /dev/null +++ b/dev_tools/win-bin/edg-compare-gen-c.bat @@ -0,0 +1,2 @@ +@echo off +python.exe "%~dp0..\bin\edg-compare-gen-c" %* \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 79b7cde521..55967e1b08 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -254,3 +254,12 @@ add_custom_target(cpfe-all) add_subdirectory(cmake/cpfe) add_subdirectory(cmake/cpfe-cp) add_subdirectory(cmake/cdisp) + +# Optionally add the cpfe-self target, which builds the front end from the C +# that cpfe generates for the front end's own sources, and the cpfe-self-check +# target, which checks the result (see cmake/cpfe-self/CMakeLists.txt). +set(EDG_SELF_COMPILE FALSE CACHE BOOL + "Add targets that build the front end from the C it generates for itself") +if($CACHE{EDG_SELF_COMPILE}) + add_subdirectory(cmake/cpfe-self) +endif() diff --git a/src/cmake/cpfe-self/CMakeLists.txt b/src/cmake/cpfe-self/CMakeLists.txt new file mode 100644 index 0000000000..ed28e0f6c7 --- /dev/null +++ b/src/cmake/cpfe-self/CMakeLists.txt @@ -0,0 +1,250 @@ +# Part of the EDG Compiler Project, under the Apache License v2.0 with LLVM +# Exceptions. +# See https://edgcpp.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +# Self-compilation of the front end. cpfe (whose back end generates C) +# translates the front end's own sources to C, and the C compiler builds that C +# into cpfe-self. This is the path a user takes to build C++ code for a system +# that has only a C compiler, so it checks that the front end can still be +# built that way. +# +# The cpfe-self-check target then has cpfe-self translate the same sources and +# compares the result with the C generated by cpfe. cpfe and cpfe-self are +# built from the same sources, so they must generate the same C, apart from +# the differences that are expected between any two runs (see +# edg-compare-gen-c). + +# The front end options are assembled from directory properties, which may +# contain per-configuration generator expressions in a multi-configuration +# build. +if(CMAKE_CONFIGURATION_TYPES) + message(FATAL_ERROR + "EDG_SELF_COMPILE requires a single-configuration generator (e.g., " + "Ninja).") +endif() +# With the daemon, cpfe is only the client. +if($CACHE{MULTI_THREADED_DAEMON}) + message(FATAL_ERROR + "EDG_SELF_COMPILE is not supported with MULTI_THREADED_DAEMON.") +endif() + +find_package(Python3 REQUIRED COMPONENTS Interpreter) + +# The generated C is compiled with the C compiler, so the front end emulates +# that compiler (which also allows it to process the compiler's headers). +if(MSVC) + # The front end must lay out structs as MSVC does, but the debug macro + # configurations turn off Microsoft bit-field allocation. + configure_definitions_as_values("base" "$CACHE{EDG_MACRO_CONF}") + if("TARG_MICROSOFT_BIT_FIELD_ALLOCATION=0" IN_LIST + configured_cmakedef_definitions) + message(FATAL_ERROR + "EDG_SELF_COMPILE with MSVC requires a macro configuration that " + "uses Microsoft bit-field allocation (e.g., that of the " + "windows-msvc-release preset).") + endif() + # As the C++ build does (see /wd4996), do not warn about the C library + # routines that Microsoft deprecates, or about the "#pragma warning" + # directives intended for MSVC. + set(dialect_options + --microsoft --microsoft_version=${MSVC_VERSION} + -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_WARNINGS + --diag_suppress=unrecognized_pragma) + # The C++/CLI support (ms_metadata.cpp) is implemented with the Microsoft + # C++ library, ATL, and COM, which have no C counterparts. Since it is not + # needed on systems without a C++ compiler, leave it out (along with the + # C++/CX support, which depends on it) by overriding the macro + # configuration with the settings from the cppcli/off feature. + set(macro_conf_root_path "${CMAKE_SOURCE_DIR}/cmake/macro-conf") + set(cppcli_off_path + "${macro_conf_root_path}/support/feature/cppcli/off.cmakedef") + execute_process(COMMAND "${TOOL_PATH_EDG_CMAKEDEF}" + "${macro_conf_root_path}" + "${cppcli_off_path}" + print-values + OUTPUT_VARIABLE front_end_definitions + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_VARIABLE cmakedef_messages + RESULTS_VARIABLE cmd_status_code) + if(NOT cmd_status_code EQUAL "0") + message(FATAL_ERROR "Failed to process cmakedef:\n${cmakedef_messages}") + endif() + string(REPLACE "\n" ";" front_end_definitions "${front_end_definitions}") + list(TRANSFORM front_end_definitions PREPEND "-D") + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS + "${cppcli_off_path}") + # The C compiler's include directories are in the INCLUDE environment + # variable (set by the Visual Studio developer environment). + file(TO_CMAKE_PATH "$ENV{INCLUDE}" host_include_dirs) +elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR + CMAKE_C_COMPILER_ID STREQUAL "Clang") + # Encode the C compiler version in the form the front end expects (e.g., + # 15.3.1 becomes 150301). + if(NOT CMAKE_C_COMPILER_VERSION MATCHES "^([0-9]+)\\.([0-9]+)\\.([0-9]+)") + message(FATAL_ERROR + "Unrecognized C compiler version " + "\"${CMAKE_C_COMPILER_VERSION}\".") + endif() + math(EXPR compiler_version + "${CMAKE_MATCH_1} * 10000 + ${CMAKE_MATCH_2} * 100 + ${CMAKE_MATCH_3}") + if(CMAKE_C_COMPILER_ID STREQUAL "GNU") + set(dialect_options --g++ --gnu_version=${compiler_version}) + else() + set(dialect_options --clang --clang_version=${compiler_version}) + endif() + set(front_end_definitions "") + set(host_include_dirs ${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}) +else() + message(FATAL_ERROR + "EDG_SELF_COMPILE does not support the C compiler " + "\"${CMAKE_C_COMPILER_ID}\".") +endif() +list(REMOVE_ITEM host_include_dirs "") + +# Options used for every translation. Each translation unit gets the template +# instantiations it uses, so no prelinker is needed (see the handling of the +# duplicates below). The EDG C++ headers come first so that, e.g., is +# EDG's. +set(common_options + ${dialect_options} + --no_exceptions --no_rtti + --instantiate=used + "--sys_include=${CMAKE_SOURCE_DIR}/include_c++") +list(TRANSFORM host_include_dirs PREPEND "--sys_include=" + OUTPUT_VARIABLE host_include_options) +list(APPEND common_options ${host_include_options}) +if(NOT "$CACHE{EDG_BASE}" STREQUAL "") + # The EDG base supplies the C compiler's predefined macros. + list(APPEND common_options "--edg_base_dir=$CACHE{EDG_BASE}") +endif() + +# The front end sources are translated with the same macros and include +# directories as when they are compiled into cpfe. +get_directory_property(cpfe_definitions + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../cpfe + COMPILE_DEFINITIONS) +list(TRANSFORM cpfe_definitions PREPEND "-D") +get_target_property(cpfe_include_dirs cpfe INCLUDE_DIRECTORIES) +list(TRANSFORM cpfe_include_dirs PREPEND "-I") +set(front_end_options + --c++${CMAKE_CXX_STANDARD} + ${common_options} + ${cpfe_definitions} ${front_end_definitions} + ${cpfe_include_dirs}) + +# The generated code calls routines from the EDG runtime library (e.g., the +# placement operator new and, with the ABI used on Windows, the allocation and +# static initialization routines). The runtime library is not built on every +# host, so translate the sources of those routines as well (cpfe-self links +# with only the ones it needs). runtime_vars.c stands in for lib_src/vars.c, +# which cannot be translated in Microsoft mode. +set(runtime_source_names + delete dtor_list error main memzero munch_ctors munch_dtors new + newhandler placenew sized_del static_init) +list(TRANSFORM runtime_source_names + REPLACE "(.+)" "${CMAKE_SOURCE_DIR}/lib_src/\\1.c" + OUTPUT_VARIABLE runtime_source_files) +list(APPEND runtime_source_files "${CMAKE_CURRENT_SOURCE_DIR}/runtime_vars.c") +# Exception handling is disabled in the runtime as it is in the front end. +set(runtime_options + --c++23 --building_runtime + ${common_options} + -DEXCEPTION_HANDLING=0 + "-I${CMAKE_SOURCE_DIR}/lib_src") + +# The C generated by cpfe (and compiled into cpfe-self) and by cpfe-self (for +# the comparison). +set(cpfe_c_dir "${CMAKE_CURRENT_BINARY_DIR}/cpfe-c") +set(cpfe_self_c_dir "${CMAKE_CURRENT_BINARY_DIR}/cpfe-self-c") +file(MAKE_DIRECTORY "${cpfe_c_dir}" "${cpfe_self_c_dir}") + +function(add_translation front_end_target source_file output_file) + # Add a command that translates source_file to C (in output_file) with the + # front end built by front_end_target, passing the options in ARGN. + # + # Both front ends run in the same working directory, which is part of the + # module id generated for a source file that defines no external entities. + get_filename_component(source_name "${source_file}" NAME) + add_custom_command( + OUTPUT "${output_file}" + COMMAND "$" ${ARGN} + "--gen_c_file_name=${output_file}" "${source_file}" + DEPENDS ${front_end_target} "${source_file}" + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + COMMENT "Translating ${source_name} to C with ${front_end_target}" + VERBATIM + ) +endfunction() + +set(cpfe_c_files "") +set(runtime_c_files "") +set(cpfe_self_c_files "") +set(gen_c_file_names "") +foreach(source_file IN LISTS CORE_FRONT_END_SOURCE_FILES runtime_source_files) + get_filename_component(name "${source_file}" NAME_WE) + if(source_file IN_LIST runtime_source_files) + # Some runtime source names (e.g., error.c) are also front end source + # names. + set(gen_c_file_name "rt_${name}.int.c") + set(options ${runtime_options}) + list(APPEND runtime_c_files "${cpfe_c_dir}/${gen_c_file_name}") + else() + set(gen_c_file_name "${name}.int.c") + set(options ${front_end_options}) + list(APPEND cpfe_c_files "${cpfe_c_dir}/${gen_c_file_name}") + endif() + list(APPEND gen_c_file_names "${gen_c_file_name}") + add_translation(cpfe "${source_file}" "${cpfe_c_dir}/${gen_c_file_name}" + ${options}) + add_translation(cpfe-self "${source_file}" + "${cpfe_self_c_dir}/${gen_c_file_name}" ${options}) + list(APPEND cpfe_self_c_files "${cpfe_self_c_dir}/${gen_c_file_name}") +endforeach() + +# Build cpfe-self from the generated C with only the options that C needs (not +# the C++ warning and code generation options inherited from the parent +# directory). +set_directory_properties(PROPERTIES + COMPILE_DEFINITIONS "" + COMPILE_OPTIONS "" + INCLUDE_DIRECTORIES "" + LINK_OPTIONS "") +if(MSVC) + # The generated C is not written to be warning-free, and the objects for + # the larger source files exceed the default limit on sections. + add_compile_options(/w /bigobj) +else() + # These match the options eccp uses to compile generated C (see the + # edg_eccp_config files in bases/). + add_compile_options(-w -falign-functions=4) +endif() +if($CACHE{32BIT_BUILD}) + add_compile_options(-m32 -msse2) + add_link_options(-m32 -msse2) +endif() +add_library(cpfe-self-runtime STATIC ${runtime_c_files}) +add_executable(cpfe-self ${cpfe_c_files}) +# This uses the plain signature, as add_softfloat_to_target does. +target_link_libraries(cpfe-self cpfe-self-runtime) +if(MSVC) + # For GCC and Clang, the template instantiations that each translation unit + # gets are marked weak, but MSVC has no way to do that in C, so the same + # instantiation is defined in several objects. The definitions are + # identical, so have the linker keep any one of them (as it would for a + # COMDAT) instead of diagnosing them. + target_link_options(cpfe-self PRIVATE /FORCE:MULTIPLE /IGNORE:4006,4088) +else() + # The math library is linked implicitly only for C++. + target_link_libraries(cpfe-self m) +endif() +add_softfloat_to_target(cpfe-self) + +add_custom_target(cpfe-self-check + COMMAND "${Python3_EXECUTABLE}" + "${CMAKE_SOURCE_DIR}/dev_tools/bin/edg-compare-gen-c" + "${cpfe_c_dir}" "${cpfe_self_c_dir}" ${gen_c_file_names} + DEPENDS ${cpfe_self_c_files} + COMMENT "Comparing the C generated by cpfe and cpfe-self" + VERBATIM +) diff --git a/src/cmake/cpfe-self/runtime_vars.c b/src/cmake/cpfe-self/runtime_vars.c new file mode 100644 index 0000000000..a7990816a4 --- /dev/null +++ b/src/cmake/cpfe-self/runtime_vars.c @@ -0,0 +1,24 @@ +/* +Part of the EDG Compiler Project, under the Apache License v2.0 with LLVM +Exceptions. +See https://edgcpp.org/LICENSE.txt for license information. +SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +*/ + +/* + +runtime_vars.c -- Definitions of the runtime library variables used by the + routines that cpfe-self takes from the runtime library. + +lib_src/vars.c defines all of the runtime library's variables, but it also +includes the exception handling declarations, and those (by way of ) +cannot be processed in Microsoft mode. This defines the variables declared in +runtime.h and main.h in the same way that vars.c does. + +*/ + +#define EXTERN /* empty */ +#define VAR_INITIALIZERS 1 +#include "basics.h" +#include "runtime.h" +#include "main.h"