Repository navigation
C++20 modules: support gnu emulation modes and extended floating-point types - #315
soroshsabz wants to merge 6 commits into
Conversation
d20dcf3 to
c23faa6
Compare
|
@daveedvdv-nvidia Could you please review this PR? |
wchilders-nvidia
left a comment
There was a problem hiding this comment.
Looks very promising!
| 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 |
There was a problem hiding this comment.
We probably want to do a bit more interrogation as this is a sort of safety check(?)
There was a problem hiding this comment.
Good point. Replaced the blanket except ValueError with an explicit check in 3d96ddb: the overlap check is now skipped only when the two paths are on genuinely different drives (os.path.splitdrive comparison), documented as "neither path can contain the other". Same-drive paths always get the full commonpath check, and any other unexpected condition now raises loudly instead of being silently skipped.
| # "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') | ||
| ) |
There was a problem hiding this comment.
I don't love this, I'd prefer people actual configure the environment (which dev-init.py should help with ... which also installs the script?). However, if we're only doing this for the pre-push.py script (as a sort of "balancing act" to handle weird edge cases), I think that's fine.
There was a problem hiding this comment.
Understood: it is deliberately only in pre-push.py, as the balancing act you describe. Every other tool is invoked from a configured environment, but git runs this hook with the ambient environment, and nothing surfaced that distinction to the user today (the failure was an ImportError inside a closing console window). And yes, dev-init.py installs the hook (it writes the .git/hooks/pre-push shim that executes .git-hooks/pre-push.py), so dev-init users get the self-locating version automatically.
| # in the user's current terminal. | ||
| try: | ||
| sys.stdin = open('CONIN$', 'r') | ||
| except OSError: |
There was a problem hiding this comment.
Confession: I wrote the original logic here, I'm not a "Windows guy" but it seemed to work and be reasonably correct; I'll defer to others that know Windows better as to if this is an improvement.
There was a problem hiding this comment.
Thanks for the context! The Windows rationale: CONIN$ is the console input device and works like /dev/tty: it can be opened regardless of what stdin is connected to (git feeds the hook the ref list on stdin, so stdin itself can't serve the prompts). With a console attached (cmd, PowerShell, Windows Terminal), everything now stays in the user's current terminal. Your relaunch is preserved as the fallback for the cases where CONIN$ cannot open (GUI git clients, mintty-style pseudo-terminals), so that intent is kept; that fallback window also now waits for Enter on failure so its output can't flash away.
There was a problem hiding this comment.
cmd and PowerShell are shells, and Windows Terminal is a terminal GUI. From the Windows perspective, none of them are consoles; only conhost.exe and openconsole.exe are.
There was a problem hiding this comment.
For performance, NtQueryVolumeInformationFile should be used, but since this is a Python script, the current approach is acceptable.
| except SystemExit as exit_request: | ||
| if exit_request.code != 0: | ||
| pause_delegate_window() | ||
| raise |
There was a problem hiding this comment.
This seems mostly reasonable, although, I think we should probably just handle this as:
returncode = checker.run()
if returncode != 0:
pause_delegate_window()
sys.exit(returncode)rather than doing the exception based mechanism.
There was a problem hiding this comment.
Done in 3d96ddb, exactly along those lines: checker.run() now returns an exit status (int), and the mid-run sys.exit call sites (policy failure in check_commits, the q answer, and the too-many-attempts case in confirm_okay) were converted to an abort flag, so every failure path flows through main():
def main():
checker = PrePushChecker()
returncode = checker.run()
if returncode != 0:
pause_delegate_window()
sys.exit(returncode)| console_mode = ctypes.c_uint(0) | ||
| return bool( | ||
| kernel32.GetConsoleMode(stdin_handle, ctypes.byref(console_mode)) | ||
| ) |
There was a problem hiding this comment.
We used this same sort of check in a few different tools. It would probably be good to generalize this in edgutil as something like edgutil.is_interactive_terminal().
There was a problem hiding this comment.
Done in 3d96ddb: the check now lives in edgutil as edgutil.is_interactive_terminal() (with the GetConsoleMode rationale in its docstring), and edgdocker calls it. Happy to switch other tools over to it in a follow-up if you point me at them.
| entity involving them to an IFC file is still rejected with the same | ||
| catastrophic error. | ||
|
|
||
|
|
There was a problem hiding this comment.
We historically haven't done changes entries for "incomplete" features like modules; that's something we might want to reconsider.
I'll also note that typically we try to keep changes entries brief and high level, this is a bit too long and thorough for that guideline.
There was a problem hiding this comment.
Shortened in 8289ec9 to a single brief paragraph (bug and fix, no examples). On the policy question: I'd leaned on the entry being about a fix (modes were broken) rather than the incomplete feature itself, but if you'd rather keep module entries out of the changelog entirely until the coverage gap is closed, I'm happy to drop it; your call.
| in sys_predef.c). */ | ||
| result = float_type((gnu_version_is(>= 130000) || | ||
| clang_version_is(>= 150000)) ? | ||
| fk_std_float16 : fk_float16); |
There was a problem hiding this comment.
I'd personally be a fan of adding a small INLINE function helper (or macro) to types.h so this logic is centralized between sys_predef.c and modules.
There was a problem hiding this comment.
Done in 8289ec9: INLINE a_float_kind float16_kind(void) now lives in types.h (next to skip_typerefs), and both sys_predef.c and the IFC reader use it. (lang_feat.h, which defines the version macros, is already pulled in ahead of types.h through basic_hdrs.h in the standard include order, so the inline definition is safe.)
| case fk_fp16: | ||
| case fk_std_float16: | ||
| set_ifc_basis(&fund_type, ifc_tbs_float); | ||
| set_ifc_precision(&fund_type, ifc_tps_bit16); |
There was a problem hiding this comment.
I'm a little worried about treating fk_float16 and fk_fp16 the same as they have different manglings so if this shows up somewhere mangling-relevant we could end up in a weird place. It's probably better to move fp16 to the list of cases we're going to have to handle separately.
There was a problem hiding this comment.
Good catch, and it's exactly a round-trip hazard: the reader maps float+bit16 back to the _Float16 kind, so an exported __fp16 would have come back with a different mangling. Moved fk_fp16 to the still-rejected group in 8289ec9, with a comment noting the distinct mangling.
| } | ||
|
|
||
| Remove-Variable -Name edgRepoRoot, edgToolDirs, edgPyLibs ` | ||
| -ErrorAction SilentlyContinue |
ITNOA
Hi,
Problem
Compiling any module interface unit in a gnu or clang emulation mode
(
--gnu_version/--clang_vtogether with--modules) failed:even for a trivial
export int f();. This blocks module support for gccdialects entirely — including EDG-based IntelliSense for g++ projects
(microsoft/vscode-cpptools#6302).
Root cause
Writing a module interface unit serializes the declarations of the scopes it
touches, and the failure is a four-step chain (each step verified by a gdb
breakpoint on
ifc_write_catastrophein a debug build):Predeclaration. In emulation modes, the predefined type entry
processing (
enter_predefined_typedefinsrc/sys_predef.c, aroundline 3091) enters typedefs such as
_Float16—fk_std_float16for--gnu_version >= 130000— plus__float80,__float128,_Float32x,_Float64x, etc. into the global scope. These are compiler-generateddeclarations; user code did not mention them.
Unconditional scope walk. When the IFC file is written,
ifc_modules_write_outrecursively dumps the scopes reachable from themodule (
dump_scope_recursively→dump_scope_typesinsrc/ifc_modules_write.c), which enters every type in those scopes —including the predeclared typedefs from step 1, whether or not the module
uses them. This is why even
export int f();failed: the walk reachesthe predeclared typedefs regardless of the module's contents.
Type entry. Entering the typedef enters its underlying type, landing
in
an_ifc_il_map::enter_float_type(src/ifc_modules_write.c:2691).Incomplete encoding table. That function's switch had IFC encodings
for only four float kinds —
fk_float,fk_double,fk_long_double,fk_float128. Every extended kind (fk_float16,fk_fp16,fk_std_float16,fk_std_float32/64/128,fk_float32x,fk_float64x,fk_float80,fk_std_bfloat16) calledifc_write_catastrophe(),terminating the compilation.
Evidence — the observed backtrace (frames elided between):
Why the existing tests never caught this: plain
--c++20mode predeclaresnone of these typedefs into the walked scope, so the entire in-tree modules
suite (which runs only in default mode) was unaffected — and, before this PR,
contained no emulation-mode module test at all.
Why the fix is correct
Two complementary changes, one per root cause:
dump_scope_types):these declarations are generated by the compiler itself, and any importing
translation unit runs the same predefined type entry processing with the
same dialect options before reading the IFC file — recreating them. They
are therefore not part of the module's interface and never need to be
serialized. The skip only stops the unconditional walk of
compiler-generated typedef declarations; types that are actually used
by exported entities are still entered through the encoding of those
entities.
enter_float_typewriter +ifc_tbs_floatreader):_Float16/__fp16/std::float16_t→float+bit16,_Float32→bit32,_Float64→bit64,_Float128/std::float128_t→bit128(joining the existing__float128mapping), so interfaces can legitimately export entitiesinvolving those types. The reader previously asserted on any precision
other than
default(even__float128could not be read back);bit16now selects the
_Float16kind matching the current dialect, mirroringthe predefined type entry processing.
Limitations (documented in the changelog)
_Float32x,_Float64x,__float80, andstd::bfloat16_tstill cannot bewritten: the IFC fundamental-type encoding has no representation that
distinguishes them from other floating-point types of the same size. An
attempt to export an entity involving them still produces the same
catastrophic error.
Testing
(
edg_x86_64,edg_x86_64_cp):modules/eifc/modules/import-function-gnu/(rp): gnu-mode interfacecompilation, import, link, and run.
modules/eifc/modules/export-float16-gnu/(cp):_Float16in anexported interface, round-tripping through the IFC file.
modulessuite: observed failures identical to expectations (45/45lines, zero diffs).
regressions/modules: unchanged.--c++20 --gnu=150100 --modules, interface units compile,_Float16-returning exports import and link, and the program runs.Addresses #303 — whose
accepted answer confirms the general gap ("There are plenty of entity types
that we cannot currently write out to IFC files"); this PR closes the part
of that gap that blocked emulation modes entirely.
Windows development environment fixes (bundled)
Commits:
Improve Windows dev,Add Windows environment bootstrap script,Keep pre-push interaction in the user terminal on WindowsProblem
A default Windows clone of the repository cannot build or test, and the
pre-push hook fails invisibly:
core.autocrlf=true, producing a CRLFcheckout. This breaks the build (
mk_errinfocannot parsesrc/error_msg.txt) and the Docker workflow (every shebang script indev_tools/binfails inside the container withenv: 'python3\r': No such file or directory).edg-execcrashed withValueError: Paths don't have the same drivewhenever
%APPDATA%and the checkout are on different drive letters.PYTHONPATHto be configured even thoughgit invokes it with the ambient environment, (b) relaunched itself in a
new console window that closes before any output can be read, and
(c) crashed with a bare
TypeErrorwhenedg-execwas not onPATH.edg-execmisdetected theNULdevice as a TTY on Windows (os.isattyreturns TRUE for it), requesting
-itfor containers run withstdin=DEVNULL; docker's TTY attach then failed and — because thedocker exit status was discarded — the failure was silently swallowed.
Changes
dev_tools/pylibs/edgdocker/__init__.py:_check_parents: treat paths on different drives as non-overlapping._stdin_is_interactive_console(): detect a real console viaGetConsoleModeinstead ofos.isatty(fixes theNULmisdetection).
os.execvpsemantics of the Linux path).
.git-hooks/pre-push.py:dev_tools/pylibsrelative to the script, so the hook workswithout
PYTHONPATHconfigured.CONIN$(the Windowscounterpart of
/dev/tty). The relaunch into a standalone console iskept as a fallback for environments with no attached console (GUI git
clients, mintty), and that fallback now pauses before closing on
failure so its output can be read.
dev-env.ps1(new): PowerShell counterpart of.envrc; dot-source toconfigure
PATH/PYTHONPATHfor a session.dev-init.py: on Windows, failure commentary now suggests. dev-env.ps1for the current session and a
SetEnvironmentVariableone-liner for apermanent setup, before the existing env-dialog instructions.
HACKING.md: documentdev-env.ps1and warn about CRLF checkouts.All changes are Windows-only paths; Linux behavior was verified unchanged
(import/compile checks in the dev container,
os.isattyequivalence,/dev/ttybranch untouched).