From 4ab83b74f01332ea756c7dee8fa7aaa6341280d6 Mon Sep 17 00:00:00 2001 From: soroshsabz Date: Fri, 16 Oct 2026 09:32:39 +0330 Subject: [PATCH 1/6] Improve Windows dev [GH #303] --- .git-hooks/pre-push.py | 10 ++++++++-- HACKING.md | 10 ++++++++++ dev_tools/pylibs/edgdocker/__init__.py | 7 ++++++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/.git-hooks/pre-push.py b/.git-hooks/pre-push.py index aba11c4a77..f637526e9d 100755 --- a/.git-hooks/pre-push.py +++ b/.git-hooks/pre-push.py @@ -41,10 +41,16 @@ import subprocess import sys import tempfile +from pathlib import Path -import edgutil +# Git invokes this hook with the ambient environment, which does not +# necessarily have PYTHONPATH configured (e.g., on Windows). Locate +# "dev_tools/pylibs" relative to this script so "edgutil" can be imported. +sys.path.insert( + 0, str(Path(__file__).resolve().parents[1] / 'dev_tools' / 'pylibs') +) -from pathlib import Path +import edgutil if platform.system() == 'Windows': # On Windows we need to use the kernel API to detect if this script is diff --git a/HACKING.md b/HACKING.md index 0cb7261fdd..8df13a9993 100644 --- a/HACKING.md +++ b/HACKING.md @@ -27,6 +27,16 @@ If direnv is not an option, manually add `/dev_tools/bin` (or variables respectively. This will make the EDG development tools and their supporting Python libraries available. +> [!WARNING] +> +> Windows users should ensure their clone uses LF line endings, e.g., by +> setting `git config core.autocrlf input` before cloning (an existing clone +> can be repaired by setting that configuration and re-checking out the +> tree). The default Windows Git configuration (`core.autocrlf true`) +> produces a CRLF checkout, which breaks both the build (e.g., `mk_errinfo` +> cannot parse `src/error_msg.txt`) and the Docker based workflow (scripts +> mounted into the container fail on CRLF shebangs). + ## Docker Based (Recommended) To get started with Docker, it is recommended that Windows and MacOS users diff --git a/dev_tools/pylibs/edgdocker/__init__.py b/dev_tools/pylibs/edgdocker/__init__.py index 36267a4b48..bc0c753872 100644 --- a/dev_tools/pylibs/edgdocker/__init__.py +++ b/dev_tools/pylibs/edgdocker/__init__.py @@ -242,7 +242,12 @@ def _check_parents(mapped_dirs: List[Path], workspace_dir: Path) -> None: and can be a dangerous mistake (so we don't permit it). ''' for mapped_dir in mapped_dirs: - common_dir = Path(os.path.commonpath([mapped_dir, workspace_dir])) + try: + common_dir = Path(os.path.commonpath([mapped_dir, workspace_dir])) + except ValueError: + # On Windows, paths on different drives have no common prefix (e.g., + # APPDATA on "C:" and the workspace on "E:"), so they cannot overlap. + continue if common_dir == workspace_dir: print('Current workspace is the parent of a mapped dir:') print(f" {mapped_dir}") From 2856df7bfa8899fb4aa58fc65a5bb9a00aac1f4f Mon Sep 17 00:00:00 2001 From: soroshsabz Date: Fri, 16 Oct 2026 17:45:18 +0330 Subject: [PATCH 2/6] Add Windows environment bootstrap script [GH #303] --- HACKING.md | 12 ++++++++++++ dev-env.ps1 | 40 ++++++++++++++++++++++++++++++++++++++++ dev-init.py | 39 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 dev-env.ps1 diff --git a/HACKING.md b/HACKING.md index 8df13a9993..b0fb73714a 100644 --- a/HACKING.md +++ b/HACKING.md @@ -27,6 +27,18 @@ If direnv is not an option, manually add `/dev_tools/bin` (or variables respectively. This will make the EDG development tools and their supporting Python libraries available. +For Windows users, a PowerShell counterpart of the direnv setup is provided +at the root of the repository; from a PowerShell prompt dot source it to +configure the current session: + +``` +. .\dev-env.ps1 +``` + +(If PowerShell script execution is disabled, first run +`Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`, or use the manual +environment variable configuration described above.) + > [!WARNING] > > Windows users should ensure their clone uses LF line endings, e.g., by diff --git a/dev-env.ps1 b/dev-env.ps1 new file mode 100644 index 0000000000..cc336e9f51 --- /dev/null +++ b/dev-env.ps1 @@ -0,0 +1,40 @@ +# 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 + +# Windows counterpart of the ".envrc" file used with direnv on POSIX systems: +# from a PowerShell prompt, dot source this script to make the EDG +# development tools and their supporting Python libraries available in the +# current session: +# +# . .\dev-env.ps1 +# +# This prepends "dev_tools/win-bin" and "dev_tools/bin" to the PATH and +# "dev_tools/pylibs" to the PYTHONPATH. The script may be dot sourced from +# any directory and repeated dot sourcing is harmless. + +$edgRepoRoot = $PSScriptRoot + +$edgToolDirs = @( + (Join-Path $edgRepoRoot 'dev_tools\win-bin'), + (Join-Path $edgRepoRoot 'dev_tools\bin') +) + +if (-not (@($env:Path -split ';' | + Where-Object { $edgToolDirs -icontains $_ }))) { + $env:Path = "$($edgToolDirs -join ';');$env:Path" +} + +$edgPyLibs = Join-Path $edgRepoRoot 'dev_tools\pylibs' +if (-not (@($env:PYTHONPATH -split ';' | + Where-Object { $_ -ieq $edgPyLibs }))) { + if ($env:PYTHONPATH) { + $env:PYTHONPATH = "$edgPyLibs;$env:PYTHONPATH" + } else { + $env:PYTHONPATH = $edgPyLibs + } +} + +Remove-Variable -Name edgRepoRoot, edgToolDirs, edgPyLibs ` + -ErrorAction SilentlyContinue diff --git a/dev-init.py b/dev-init.py index 0f30b9e0e9..03861d2d72 100755 --- a/dev-init.py +++ b/dev-init.py @@ -214,7 +214,25 @@ async def check_edg_python() -> InitResult: if platform.system() == 'Windows': find_edgy.add_failure_commentary( - 'Open the Windows environment variable settings:' + 'For the current PowerShell session only, run (from the repository ' + 'root):' + ) + find_edgy.add_failure_commentary( + ' . .\\dev-env.ps1', + allow_wrap = False + ) + find_edgy.add_failure_commentary( + 'and re-run this script; or, to configure new terminals ' + 'permanently, run:' + ) + find_edgy.add_failure_commentary( + ' [Environment]::SetEnvironmentVariable("Path", ' + '[Environment]::GetEnvironmentVariable("Path", "User") ' + f"+ ';{script_path}', 'User')", + allow_wrap = False + ) + find_edgy.add_failure_commentary( + 'Alternatively, open the Windows environment variable settings:' ) find_edgy.add_failure_commentary( ' "C:\\Windows\\system32\\rundll32.exe" ' @@ -271,7 +289,24 @@ async def check_edg_python() -> InitResult: if platform.system() == 'Windows': find_edg_tools.add_failure_commentary( - 'Open the Windows environment variable settings:' + 'For the current PowerShell session only, run (from the repository ' + 'root):' + ) + find_edg_tools.add_failure_commentary( + ' . .\\dev-env.ps1', + allow_wrap = False + ) + find_edg_tools.add_failure_commentary( + 'and re-run this script; or, to configure new terminals ' + 'permanently, run:' + ) + find_edg_tools.add_failure_commentary( + ' [Environment]::SetEnvironmentVariable("PYTHONPATH", ' + f"'{pylibs_path}', 'User')", + allow_wrap = False + ) + find_edg_tools.add_failure_commentary( + 'Alternatively, open the Windows environment variable settings:' ) find_edg_tools.add_failure_commentary( ' "C:\\Windows\\system32\\rundll32.exe" ' From d58d9f0cf6f40a3284cd1b03e3c334525a2007d3 Mon Sep 17 00:00:00 2001 From: soroshsabz Date: Fri, 16 Oct 2026 18:05:24 +0330 Subject: [PATCH 3/6] Keep pre-push interaction in the user terminal on Windows [GH #303] --- .git-hooks/pre-push.py | 44 ++++++++++++++++++-------- dev_tools/pylibs/edgdocker/__init__.py | 33 +++++++++++++++++-- 2 files changed, 60 insertions(+), 17 deletions(-) diff --git a/.git-hooks/pre-push.py b/.git-hooks/pre-push.py index f637526e9d..19b9e3e531 100755 --- a/.git-hooks/pre-push.py +++ b/.git-hooks/pre-push.py @@ -53,24 +53,24 @@ import edgutil if platform.system() == 'Windows': - # On Windows we need to use the kernel API to detect if this script is - # running in a standalone console window (if so, there will be only one - # processes at this point). - import ctypes - - kernel32 = ctypes.WinDLL('kernel32', use_last_error = True) - process_array = (ctypes.c_uint * 2)() - num_processes = kernel32.GetConsoleProcessList(process_array, 2) - if num_processes != 1: - # When not running in a standalone console window, the script relaunches - # itself to make sure the user has an interactive shell. Then it forwards - # the exit code from the delegate process. + # The standard input of this hook carries the push ref list supplied by + # git, so it cannot be used to prompt the user. When a console is + # attached, open the console input device directly ("CONIN$" is the + # Windows counterpart of "/dev/tty") so that all prompts and output stay + # in the user's current terminal. + try: + sys.stdin = open('CONIN$', 'r') + except OSError: + # No console is attached (e.g., a GUI git client or a pseudo terminal + # such as mintty). Relaunch in a standalone console window so that the + # user can still respond to interactive prompts. delegate_proc = subprocess.run( [ sys.executable, __file__ ], - creationflags = subprocess.CREATE_NEW_CONSOLE + creationflags = subprocess.CREATE_NEW_CONSOLE, + env = {**os.environ, 'EDG_PREPUSH_DELEGATE': '1'} ) sys.exit(delegate_proc.returncode) else: @@ -352,8 +352,24 @@ def run(self) -> bool: return self.errors == 0 +def pause_delegate_window() -> None: + '''When running as a relaunch delegate, wait for the user before the + standalone console window closes so that failure output can be read.''' + if os.environ.get('EDG_PREPUSH_DELEGATE') != '1': + return + + try: + input('Push checks failed; press Enter to close this window. ') + except EOFError: + pass + def main(): checker = PrePushChecker() - sys.exit(0 if checker.run() else 1) + try: + sys.exit(0 if checker.run() else 1) + except SystemExit as exit_request: + if exit_request.code != 0: + pause_delegate_window() + raise main() diff --git a/dev_tools/pylibs/edgdocker/__init__.py b/dev_tools/pylibs/edgdocker/__init__.py index bc0c753872..4992bb29b2 100644 --- a/dev_tools/pylibs/edgdocker/__init__.py +++ b/dev_tools/pylibs/edgdocker/__init__.py @@ -367,6 +367,31 @@ def _get_group_id() -> int: return 1000 return os.getgid() +def _stdin_is_interactive_console() -> bool: + '''Return TRUE if standard input is attached to an interactive console. + + On Windows, "os.isatty" (and "sys.stdin.isatty") returns TRUE for some + character devices that are not consoles (e.g., "NUL", which is used when + a caller passes "subprocess.DEVNULL" as standard input), which would + incorrectly request TTY attachment for the container. A real console + handle can be identified by "GetConsoleMode", which fails for all other + devices. + ''' + if platform.system() != 'Windows': + return os.isatty(sys.stdin.fileno()) + + import ctypes + import msvcrt + + kernel32 = ctypes.WinDLL('kernel32', use_last_error = True) + kernel32.GetConsoleMode.argtypes = [ctypes.c_void_p, ctypes.c_void_p] + kernel32.GetConsoleMode.restype = ctypes.c_int + stdin_handle = msvcrt.get_osfhandle(sys.stdin.fileno()) + console_mode = ctypes.c_uint(0) + return bool( + kernel32.GetConsoleMode(stdin_handle, ctypes.byref(console_mode)) + ) + def exec_docker_cmd(image_name: str, workspace_dir: Path, container_working_dir: PurePosixPath, command_string: str, *, enable_x11: bool = False, memory_limit: str = '', @@ -395,7 +420,7 @@ def exec_docker_cmd(image_name: str, workspace_dir: Path, f"EDG_USER_NAME={render_user_name()}" ] - if os.isatty(sys.stdin.fileno()): + if _stdin_is_interactive_console(): # Make the container interactive. docker_args.append('-it') else: @@ -478,13 +503,15 @@ def exec_docker_cmd(image_name: str, workspace_dir: Path, if platform.system() == 'Windows': # Windows does not properly handle the execvp. Instead, keep this process - # around, and simply forward on stdin, stdout, and stderr. - subprocess.run( + # around, and simply forward on stdin, stdout, and stderr. Forward the + # exit status of the container so that callers observe failures. + completed_proc = subprocess.run( docker_args, stdin = sys.stdin, stdout = sys.stdout, stderr = sys.stderr ) + sys.exit(completed_proc.returncode) else: # Execute the docker command, replacing the current process. os.execvp('docker', docker_args) From c23faa64f989ac6480f8bcf17bfc489ed080ffc2 Mon Sep 17 00:00:00 2001 From: soroshsabz Date: Fri, 16 Oct 2026 18:36:13 +0330 Subject: [PATCH 4/6] C++20 modules: gnu emulation modes and extended float types [GH #303] --- doc/source/testing.rst | 13 +++++++ src/Changes | 34 +++++++++++++++++++ src/ifc_modules_read.c | 31 +++++++++++++++-- src/ifc_modules_write.c | 27 ++++++++++++--- .../.test.rto/default.1.1.txt | 1 + .../.test.rto/default.1.2.txt | 2 ++ .../.test.rto/edg_x86_64_cp.1.2.txt | 3 ++ .../eifc/modules/export-float16-gnu/foo.ixx | 5 +++ .../modules/export-float16-gnu/test.mft.cpp | 10 ++++++ .../.test.rto/default.1.1.txt | 1 + .../.test.rto/default.1.2.txt | 2 ++ .../.test.rto/default.1.3.txt | 1 + .../.test.rto/edg_x86_64_cp.1.2.txt | 3 ++ .../eifc/modules/import-function-gnu/foo.ixx | 5 +++ .../modules/import-function-gnu/test.mft.cpp | 9 +++++ 15 files changed, 141 insertions(+), 6 deletions(-) create mode 100644 tests/tests/modules/eifc/modules/export-float16-gnu/.test.rto/default.1.1.txt create mode 100644 tests/tests/modules/eifc/modules/export-float16-gnu/.test.rto/default.1.2.txt create mode 100644 tests/tests/modules/eifc/modules/export-float16-gnu/.test.rto/edg_x86_64_cp.1.2.txt create mode 100644 tests/tests/modules/eifc/modules/export-float16-gnu/foo.ixx create mode 100644 tests/tests/modules/eifc/modules/export-float16-gnu/test.mft.cpp create mode 100644 tests/tests/modules/eifc/modules/import-function-gnu/.test.rto/default.1.1.txt create mode 100644 tests/tests/modules/eifc/modules/import-function-gnu/.test.rto/default.1.2.txt create mode 100644 tests/tests/modules/eifc/modules/import-function-gnu/.test.rto/default.1.3.txt create mode 100644 tests/tests/modules/eifc/modules/import-function-gnu/.test.rto/edg_x86_64_cp.1.2.txt create mode 100644 tests/tests/modules/eifc/modules/import-function-gnu/foo.ixx create mode 100644 tests/tests/modules/eifc/modules/import-function-gnu/test.mft.cpp diff --git a/doc/source/testing.rst b/doc/source/testing.rst index c8db4bcfeb..e781be5a1d 100644 --- a/doc/source/testing.rst +++ b/doc/source/testing.rst @@ -245,6 +245,19 @@ IFC file should be force-generated for (this is unusual). These module source files are compiled under the ``--module_internal_partition`` flag to generate both an IFC and a ``.obj`` file. +Note that when testing modules in a compiler emulation mode (e.g., with +``--gnu_version=150100`` added to ``//options_all:``), the ``--modules`` +option must also be specified: emulation modes disable the module keywords +by default to match the emulated compiler (no version of g++ enables them +without ``-fmodules-ts``). For example: + +.. code:: text + + //options_all:--c++20 --gnu_version=150100 --modules + +See the ``modules/eifc/modules/import-function-gnu`` and +``modules/eifc/modules/export-float16-gnu`` tests for examples. + If ``//script:`` is specified, that script is executed instead of any other commands. In general, the test type does not matter for script files except for checking the return code. For example, a ``cp`` test will expect a zero diff --git a/src/Changes b/src/Changes index acf160a341..d3f5767077 100644 --- a/src/Changes +++ b/src/Changes @@ -1,5 +1,39 @@ Version 7.1 +10/16/26 [GH #303] +C++20 modules: gnu emulation modes and extended floating-point types + +A module interface unit compiled in a gnu or clang emulation mode always +ended with the catastrophic error "an IFC file could not be produced for +the current translation unit" (with "one or more entities cannot currently +be written to an IFC file"). The predeclared typedefs of extended +floating-point types (e.g., "_Float16") entered into the global scope by +such modes were written to the IFC file, but the types of some of them had +no IFC encoding. Predeclared typedefs are now skipped when the members of +a scope are written to an IFC file: they are re-created by the predefined +type entry processing of an importing translation unit. For example, with +--c++20 --gnu=150100 --modules: + + export module m; + export int f(); + // Previously: catastrophic error when writing the IFC file. + +In addition, the floating-point types "_Float16", "__fp16", "_Float32", +"_Float64", and "_Float128" (and their C++23 standard counterparts) can now +be written to and read from an IFC file, so that module interfaces can +export entities involving those types: + + export module m; + export _Float16 half(_Float16 v) { return v / 2; } + // Previously: catastrophic error when writing the IFC file. + +The extended kinds "_Float32x", "_Float64x", "__float80", and +"std::bfloat16_t" still have no IFC encoding that distinguishes them from +other floating-point types of the same size, and an attempt to write an +entity involving them to an IFC file is still rejected with the same +catastrophic error. + + 10/16/26 [GH #229] Improper handling of "if constexpr" substatements diff --git a/src/ifc_modules_read.c b/src/ifc_modules_read.c index d48ace4d9f..b23cfd436d 100644 --- a/src/ifc_modules_read.c +++ b/src/ifc_modules_read.c @@ -16497,8 +16497,35 @@ no corresponding type, return an error type. } break; case ifc_tbs_float: - check_assertion(precision == ifc_tps_default); - result = float_type((a_float_kind)fk_float); + switch (precision) { + case ifc_tps_default: + result = float_type(fk_float); + break; + case ifc_tps_bit16: + /* Map to the "_Float16" kind used by the current dialect + (see the corresponding predefined type entry processing + in sys_predef.c). */ + result = float_type((gnu_version_is(>= 130000) || + clang_version_is(>= 150000)) ? + fk_std_float16 : fk_float16); + break; + case ifc_tps_bit32: + result = float_type(fk_std_float32); + break; + case ifc_tps_bit64: + result = float_type(fk_std_float64); + break; + case ifc_tps_bit128: + result = float_type(fk_float128); + break; + default: + { a_string err_msg("Unexpected ", str_for(precision), + " for ", str_for(basis)); + + ifc_unexpected(module_of(itf), err_msg); + } + goto invalid; + } /* switch */ break; case ifc_tbs_double: if (precision == ifc_tps_long) { diff --git a/src/ifc_modules_write.c b/src/ifc_modules_write.c index 1cf1e51aa5..690f298d52 100644 --- a/src/ifc_modules_write.c +++ b/src/ifc_modules_write.c @@ -2714,20 +2714,32 @@ for the float type. set_ifc_precision(&fund_type, ifc_tps_long); break; case fk_float128: + case fk_std_float128: set_ifc_basis(&fund_type, ifc_tbs_float); set_ifc_precision(&fund_type, ifc_tps_bit128); break; case fk_float16: case fk_fp16: + case fk_std_float16: + set_ifc_basis(&fund_type, ifc_tbs_float); + set_ifc_precision(&fund_type, ifc_tps_bit16); + break; + case fk_std_float32: + set_ifc_basis(&fund_type, ifc_tbs_float); + set_ifc_precision(&fund_type, ifc_tps_bit32); + break; + case fk_std_float64: + set_ifc_basis(&fund_type, ifc_tbs_float); + set_ifc_precision(&fund_type, ifc_tps_bit64); + break; case fk_float32x: case fk_float64x: case fk_float80: case fk_std_bfloat16: - case fk_std_float16: - case fk_std_float32: - case fk_std_float64: - case fk_std_float128: case fk_last: + /* These extended floating-point kinds have no IFC encoding that + distinguishes them from other floating-point types of the same + size. */ ifc_write_catastrophe(); break; default_is_unexpected(); @@ -6042,6 +6054,13 @@ Add all the types in the given scope to the given IL -> IFC mapping. information. */ continue; } /* if */ + if (type->kind == tk_typeref && type->variant.typeref.predeclared) { + /* Skip predeclared typedefs (e.g., "_Float16", which is entered in gnu + and clang emulation modes): they are re-created by the predefined + type entry processing of an importing translation unit, and the + types of some of them cannot currently be written to an IFC file. */ + continue; + } /* if */ (void)il_map->find_or_enter_type(type); } /* for */ } /* dump_scope_types */ diff --git a/tests/tests/modules/eifc/modules/export-float16-gnu/.test.rto/default.1.1.txt b/tests/tests/modules/eifc/modules/export-float16-gnu/.test.rto/default.1.1.txt new file mode 100644 index 0000000000..4882e1c979 --- /dev/null +++ b/tests/tests/modules/eifc/modules/export-float16-gnu/.test.rto/default.1.1.txt @@ -0,0 +1 @@ +cpfe -DTEST_NUMBER=1 --module_import_diagnostics -d-module_report --c++20 --gnu_version=150100 --modules --module_interface foo.ixx diff --git a/tests/tests/modules/eifc/modules/export-float16-gnu/.test.rto/default.1.2.txt b/tests/tests/modules/eifc/modules/export-float16-gnu/.test.rto/default.1.2.txt new file mode 100644 index 0000000000..45d0d4cfe4 --- /dev/null +++ b/tests/tests/modules/eifc/modules/export-float16-gnu/.test.rto/default.1.2.txt @@ -0,0 +1,2 @@ +cpfe -DTEST_NUMBER=1 --module_import_diagnostics -d-module_report --c++20 --gnu_version=150100 --modules Test_name.c +2 modules declarations processed (0 failed). diff --git a/tests/tests/modules/eifc/modules/export-float16-gnu/.test.rto/edg_x86_64_cp.1.2.txt b/tests/tests/modules/eifc/modules/export-float16-gnu/.test.rto/edg_x86_64_cp.1.2.txt new file mode 100644 index 0000000000..31335fc08b --- /dev/null +++ b/tests/tests/modules/eifc/modules/export-float16-gnu/.test.rto/edg_x86_64_cp.1.2.txt @@ -0,0 +1,3 @@ +cpfe -DTEST_NUMBER=1 --module_import_diagnostics -d-module_report --c++20 --gnu_version=150100 --modules Test_name.c +2 modules declarations processed (0 failed). +2 modules declarations processed (0 failed). diff --git a/tests/tests/modules/eifc/modules/export-float16-gnu/foo.ixx b/tests/tests/modules/eifc/modules/export-float16-gnu/foo.ixx new file mode 100644 index 0000000000..aabf552a96 --- /dev/null +++ b/tests/tests/modules/eifc/modules/export-float16-gnu/foo.ixx @@ -0,0 +1,5 @@ +export module foo; + +export _Float16 half(_Float16 v) { + return v / 2; +} diff --git a/tests/tests/modules/eifc/modules/export-float16-gnu/test.mft.cpp b/tests/tests/modules/eifc/modules/export-float16-gnu/test.mft.cpp new file mode 100644 index 0000000000..f91c3878c1 --- /dev/null +++ b/tests/tests/modules/eifc/modules/export-float16-gnu/test.mft.cpp @@ -0,0 +1,10 @@ +//type:cp +//module_files:foo.ixx +//options_all:--module_import_diagnostics -d-module_report --c++20 --gnu_version=150100 --modules + +import foo; + +int main() { + _Float16 x = half(8); + return 0; +} diff --git a/tests/tests/modules/eifc/modules/import-function-gnu/.test.rto/default.1.1.txt b/tests/tests/modules/eifc/modules/import-function-gnu/.test.rto/default.1.1.txt new file mode 100644 index 0000000000..1fe269d1e7 --- /dev/null +++ b/tests/tests/modules/eifc/modules/import-function-gnu/.test.rto/default.1.1.txt @@ -0,0 +1 @@ +eccp -DTEST_NUMBER=1 --module_import_diagnostics -d-module_report --c++20 --gnu_version=150100 --modules --module_interface foo.ixx diff --git a/tests/tests/modules/eifc/modules/import-function-gnu/.test.rto/default.1.2.txt b/tests/tests/modules/eifc/modules/import-function-gnu/.test.rto/default.1.2.txt new file mode 100644 index 0000000000..d9e7fd62c5 --- /dev/null +++ b/tests/tests/modules/eifc/modules/import-function-gnu/.test.rto/default.1.2.txt @@ -0,0 +1,2 @@ +eccp -DTEST_NUMBER=1 --module_import_diagnostics -d-module_report --c++20 --gnu_version=150100 --modules foo.o Test_name.c +1 modules declarations processed (0 failed). diff --git a/tests/tests/modules/eifc/modules/import-function-gnu/.test.rto/default.1.3.txt b/tests/tests/modules/eifc/modules/import-function-gnu/.test.rto/default.1.3.txt new file mode 100644 index 0000000000..ff2642c6d5 --- /dev/null +++ b/tests/tests/modules/eifc/modules/import-function-gnu/.test.rto/default.1.3.txt @@ -0,0 +1 @@ +./a.out diff --git a/tests/tests/modules/eifc/modules/import-function-gnu/.test.rto/edg_x86_64_cp.1.2.txt b/tests/tests/modules/eifc/modules/import-function-gnu/.test.rto/edg_x86_64_cp.1.2.txt new file mode 100644 index 0000000000..7e1abe9ab1 --- /dev/null +++ b/tests/tests/modules/eifc/modules/import-function-gnu/.test.rto/edg_x86_64_cp.1.2.txt @@ -0,0 +1,3 @@ +eccp -DTEST_NUMBER=1 --module_import_diagnostics -d-module_report --c++20 --gnu_version=150100 --modules foo.o Test_name.c +1 modules declarations processed (0 failed). +1 modules declarations processed (0 failed). diff --git a/tests/tests/modules/eifc/modules/import-function-gnu/foo.ixx b/tests/tests/modules/eifc/modules/import-function-gnu/foo.ixx new file mode 100644 index 0000000000..966a87da51 --- /dev/null +++ b/tests/tests/modules/eifc/modules/import-function-gnu/foo.ixx @@ -0,0 +1,5 @@ +export module foo; + +export int bar() { + return 0; +} diff --git a/tests/tests/modules/eifc/modules/import-function-gnu/test.mft.cpp b/tests/tests/modules/eifc/modules/import-function-gnu/test.mft.cpp new file mode 100644 index 0000000000..47af7310c7 --- /dev/null +++ b/tests/tests/modules/eifc/modules/import-function-gnu/test.mft.cpp @@ -0,0 +1,9 @@ +//type:rp +//module_files:foo.ixx +//options_all:--module_import_diagnostics -d-module_report --c++20 --gnu_version=150100 --modules + +import foo; + +int main() { + return bar(); +} From 8289ec988e51441de7bfd26d992847cf49c087c0 Mon Sep 17 00:00:00 2001 From: soroshsabz Date: Fri, 16 Oct 2026 19:39:57 +0330 Subject: [PATCH 5/6] Address review: centralize float16 kind handling and keep fp16 out of IFC encodings [GH #303] --- src/Changes | 37 ++++++++----------------------------- src/ifc_modules_read.c | 7 +------ src/ifc_modules_write.c | 5 +++-- src/sys_predef.c | 4 +--- src/types.h | 13 +++++++++++++ 5 files changed, 26 insertions(+), 40 deletions(-) diff --git a/src/Changes b/src/Changes index d3f5767077..5990867dc4 100644 --- a/src/Changes +++ b/src/Changes @@ -3,35 +3,14 @@ Version 7.1 10/16/26 [GH #303] C++20 modules: gnu emulation modes and extended floating-point types -A module interface unit compiled in a gnu or clang emulation mode always -ended with the catastrophic error "an IFC file could not be produced for -the current translation unit" (with "one or more entities cannot currently -be written to an IFC file"). The predeclared typedefs of extended -floating-point types (e.g., "_Float16") entered into the global scope by -such modes were written to the IFC file, but the types of some of them had -no IFC encoding. Predeclared typedefs are now skipped when the members of -a scope are written to an IFC file: they are re-created by the predefined -type entry processing of an importing translation unit. For example, with ---c++20 --gnu=150100 --modules: - - export module m; - export int f(); - // Previously: catastrophic error when writing the IFC file. - -In addition, the floating-point types "_Float16", "__fp16", "_Float32", -"_Float64", and "_Float128" (and their C++23 standard counterparts) can now -be written to and read from an IFC file, so that module interfaces can -export entities involving those types: - - export module m; - export _Float16 half(_Float16 v) { return v / 2; } - // Previously: catastrophic error when writing the IFC file. - -The extended kinds "_Float32x", "_Float64x", "__float80", and -"std::bfloat16_t" still have no IFC encoding that distinguishes them from -other floating-point types of the same size, and an attempt to write an -entity involving them to an IFC file is still rejected with the same -catastrophic error. +Module interface units compiled in a gnu or clang emulation mode always +failed with a catastrophic error indicating that an IFC file could not be +produced: the predeclared typedefs of extended floating-point types +(e.g., "_Float16") entered by those modes have no IFC encoding. +Predeclared typedefs are now skipped when the members of a scope are +written to an IFC file (they are re-created by any importing translation +unit), and the "_Float16", "_Float32", "_Float64", and "_Float128" type +families can now be written to and read from IFC files. 10/16/26 [GH #229] diff --git a/src/ifc_modules_read.c b/src/ifc_modules_read.c index b23cfd436d..096eecd0d7 100644 --- a/src/ifc_modules_read.c +++ b/src/ifc_modules_read.c @@ -16502,12 +16502,7 @@ no corresponding type, return an error type. result = float_type(fk_float); break; case ifc_tps_bit16: - /* Map to the "_Float16" kind used by the current dialect - (see the corresponding predefined type entry processing - in sys_predef.c). */ - result = float_type((gnu_version_is(>= 130000) || - clang_version_is(>= 150000)) ? - fk_std_float16 : fk_float16); + result = float_type(float16_kind()); break; case ifc_tps_bit32: result = float_type(fk_std_float32); diff --git a/src/ifc_modules_write.c b/src/ifc_modules_write.c index 690f298d52..6e468600b7 100644 --- a/src/ifc_modules_write.c +++ b/src/ifc_modules_write.c @@ -2719,7 +2719,6 @@ for the float type. set_ifc_precision(&fund_type, ifc_tps_bit128); break; case fk_float16: - case fk_fp16: case fk_std_float16: set_ifc_basis(&fund_type, ifc_tbs_float); set_ifc_precision(&fund_type, ifc_tps_bit16); @@ -2732,6 +2731,7 @@ for the float type. set_ifc_basis(&fund_type, ifc_tbs_float); set_ifc_precision(&fund_type, ifc_tps_bit64); break; + case fk_fp16: case fk_float32x: case fk_float64x: case fk_float80: @@ -2739,7 +2739,8 @@ for the float type. case fk_last: /* These extended floating-point kinds have no IFC encoding that distinguishes them from other floating-point types of the same - size. */ + size ("__fp16" in particular has a different mangling than + "_Float16"). */ ifc_write_catastrophe(); break; default_is_unexpected(); diff --git a/src/sys_predef.c b/src/sys_predef.c index f07b9d8d70..2c470b09a8 100644 --- a/src/sys_predef.c +++ b/src/sys_predef.c @@ -3091,9 +3091,7 @@ Enter predeclared symbols as required by the implementation. if (float16_enabled) { (void)enter_predefined_typedef( "_Float16", - ((gnu_version_is(>= 130000) || - clang_version_is(>= 150000)) ? float_type(fk_std_float16) - : float_type(fk_float16))); + float_type(float16_kind())); } /* if */ if (float80_enabled) { (void)enter_predefined_typedef( diff --git a/src/types.h b/src/types.h index 3b9bebbe91..b56b8aa700 100644 --- a/src/types.h +++ b/src/types.h @@ -54,6 +54,19 @@ this routine should not be used when checking type qualifiers. } /* skip_typerefs */ +INLINE a_float_kind float16_kind(void) +/* +Return the floating-point kind used for the type name "_Float16" in the +current mode: GNU versions 13 and later and Clang versions 15 and later +use the standard "_Float16" type, and other modes use the GNU extension +type. +*/ +{ + return (gnu_version_is(>= 130000) || clang_version_is(>= 150000)) ? + fk_std_float16 : fk_float16; +} /* float16_kind */ + + INLINE a_boolean is_lexical_typeref(a_type_ptr tp) /* Return TRUE if tp is a trk_template_arg_list or trk_name_qualifier typeref, From 3d96ddb76ce3b2ddf260af4c63bcd32c1838cdce Mon Sep 17 00:00:00 2001 From: soroshsabz Date: Fri, 16 Oct 2026 19:39:57 +0330 Subject: [PATCH 6/6] Address review: edgutil interactive terminal helper and pre-push exit handling [GH #303] --- .git-hooks/pre-push.py | 37 +++++++++++++++---------- dev_tools/pylibs/edgdocker/__init__.py | 38 +++++--------------------- dev_tools/pylibs/edgutil/__init__.py | 23 ++++++++++++++++ 3 files changed, 52 insertions(+), 46 deletions(-) diff --git a/.git-hooks/pre-push.py b/.git-hooks/pre-push.py index 19b9e3e531..da03e3c6c3 100755 --- a/.git-hooks/pre-push.py +++ b/.git-hooks/pre-push.py @@ -143,6 +143,7 @@ class PrePushChecker: def __init__(self): self.errors = 0 + self.abort = False def confirm_okay(self): '''Ask the user whether a policy warning is acceptable. @@ -165,7 +166,8 @@ def confirm_okay(self): return if response == 'q': self.errors = 1 - sys.exit(1) + self.abort = True + return print( "Please answer 'y' (to accept), 'n' (to continue check) or " @@ -174,15 +176,18 @@ def confirm_okay(self): tries += 1 if tries >= 10: print('Too many attempts.') - sys.exit(1) + self.errors = 1 + self.abort = True + return def check_commits(self): '''Walk commits until one already on a remote branch is found. As commits are walked, check to make sure they match formatting guidance. - Returns the oldest new commit hash, or None if there are no new commits. - Exits immediately if a commit subject fails the PR-number policy. + Returns the oldest new commit hash, or None if there are no new commits + (or if a commit subject fails the PR-number policy, in which case an + error is recorded and the check sequence is aborted). ''' first_new_commit = None @@ -206,7 +211,9 @@ def check_commits(self): print( " [POLICY] Commit message doesn't contain valid PR number (or [])." ) - sys.exit(1) + self.errors = 1 + self.abort = True + return None first_new_commit = commit @@ -291,6 +298,8 @@ def check_changed_files(self, first_new_commit): results = json.load(file_handle) for file_path_str, file_results in results.items(): + if self.abort: + break print(f" Checking file: {file_path_str}") misspellings = file_results['misspellings'] @@ -342,15 +351,15 @@ def check_changed_files(self, first_new_commit): print('=' * 79) self.confirm_okay() - def run(self) -> bool: - '''Execute the full pre-push check sequence.''' + def run(self) -> int: + '''Execute the full pre-push check sequence. Return the exit status.''' first_new_commit = self.check_commits() # Skip per file checks if we didn't actually detect a new commit. - if first_new_commit is not None: + if not self.abort and first_new_commit is not None: self.check_changed_files(first_new_commit) - return self.errors == 0 + return 0 if (self.errors == 0 and not self.abort) else 1 def pause_delegate_window() -> None: '''When running as a relaunch delegate, wait for the user before the @@ -365,11 +374,9 @@ def pause_delegate_window() -> None: def main(): checker = PrePushChecker() - try: - sys.exit(0 if checker.run() else 1) - except SystemExit as exit_request: - if exit_request.code != 0: - pause_delegate_window() - raise + returncode = checker.run() + if returncode != 0: + pause_delegate_window() + sys.exit(returncode) main() diff --git a/dev_tools/pylibs/edgdocker/__init__.py b/dev_tools/pylibs/edgdocker/__init__.py index 4992bb29b2..0bfeb4ab5b 100644 --- a/dev_tools/pylibs/edgdocker/__init__.py +++ b/dev_tools/pylibs/edgdocker/__init__.py @@ -241,13 +241,14 @@ def _check_parents(mapped_dirs: List[Path], workspace_dir: Path) -> None: This can be valid (from a Docker perspective), but it's an advanced use case and can be a dangerous mistake (so we don't permit it). ''' + workspace_drive = os.path.splitdrive(str(workspace_dir))[0] for mapped_dir in mapped_dirs: - try: - common_dir = Path(os.path.commonpath([mapped_dir, workspace_dir])) - except ValueError: - # On Windows, paths on different drives have no common prefix (e.g., - # APPDATA on "C:" and the workspace on "E:"), so they cannot overlap. + if os.path.splitdrive(str(mapped_dir))[0] != workspace_drive: + # The two paths are on different drives (which can only happen on + # Windows), so neither path can contain the other and the check below + # does not apply. continue + common_dir = Path(os.path.commonpath([mapped_dir, workspace_dir])) if common_dir == workspace_dir: print('Current workspace is the parent of a mapped dir:') print(f" {mapped_dir}") @@ -367,31 +368,6 @@ def _get_group_id() -> int: return 1000 return os.getgid() -def _stdin_is_interactive_console() -> bool: - '''Return TRUE if standard input is attached to an interactive console. - - On Windows, "os.isatty" (and "sys.stdin.isatty") returns TRUE for some - character devices that are not consoles (e.g., "NUL", which is used when - a caller passes "subprocess.DEVNULL" as standard input), which would - incorrectly request TTY attachment for the container. A real console - handle can be identified by "GetConsoleMode", which fails for all other - devices. - ''' - if platform.system() != 'Windows': - return os.isatty(sys.stdin.fileno()) - - import ctypes - import msvcrt - - kernel32 = ctypes.WinDLL('kernel32', use_last_error = True) - kernel32.GetConsoleMode.argtypes = [ctypes.c_void_p, ctypes.c_void_p] - kernel32.GetConsoleMode.restype = ctypes.c_int - stdin_handle = msvcrt.get_osfhandle(sys.stdin.fileno()) - console_mode = ctypes.c_uint(0) - return bool( - kernel32.GetConsoleMode(stdin_handle, ctypes.byref(console_mode)) - ) - def exec_docker_cmd(image_name: str, workspace_dir: Path, container_working_dir: PurePosixPath, command_string: str, *, enable_x11: bool = False, memory_limit: str = '', @@ -420,7 +396,7 @@ def exec_docker_cmd(image_name: str, workspace_dir: Path, f"EDG_USER_NAME={render_user_name()}" ] - if _stdin_is_interactive_console(): + if edgutil.is_interactive_terminal(): # Make the container interactive. docker_args.append('-it') else: diff --git a/dev_tools/pylibs/edgutil/__init__.py b/dev_tools/pylibs/edgutil/__init__.py index e198570ca7..d7168d68b1 100644 --- a/dev_tools/pylibs/edgutil/__init__.py +++ b/dev_tools/pylibs/edgutil/__init__.py @@ -203,6 +203,29 @@ def print_env_var(env_var: str) -> None: var_value = os.environ.get(env_var) or '' print(f"Using \"{env_var}\": \"{var_value}\"") +def is_interactive_terminal() -> bool: + '''Return TRUE if standard input is attached to an interactive terminal. + + On Windows, "os.isatty" (and "sys.stdin.isatty") returns TRUE for some + character devices that are not consoles (e.g., "NUL", which is used when + a caller passes "subprocess.DEVNULL" as standard input), which would + incorrectly request TTY attachment for a container. A real console + handle can be identified by "GetConsoleMode", which fails for all other + devices. + ''' + if platform.system() != 'Windows': + return os.isatty(sys.stdin.fileno()) + + import ctypes + import msvcrt + + kernel32 = ctypes.WinDLL('kernel32', use_last_error = True) + kernel32.GetConsoleMode.argtypes = [ctypes.c_void_p, ctypes.c_void_p] + kernel32.GetConsoleMode.restype = ctypes.c_int + stdin_handle = msvcrt.get_osfhandle(sys.stdin.fileno()) + console_mode = ctypes.c_uint(0) + return bool(kernel32.GetConsoleMode(stdin_handle, ctypes.byref(console_mode))) + def get_application_files_dir(app_name: str) -> Path: platform_name = platform.system() if platform_name == 'Linux':