Skip to content

Commit 2497003

Browse files
committed
[GR-52900] Enable cpyext async slots on Windows
PullRequest: graalpython/4645
2 parents 628d067 + 42ff8c5 commit 2497003

16 files changed

Lines changed: 1945 additions & 33 deletions

File tree

graalpython/com.oracle.graal.python.frozen/freeze_modules.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@ def add_graalpython_core():
124124
"_sysconfig",
125125
"java",
126126
"pip_hook",
127-
"_nt",
128127
]:
129128
modname = f"graalpy.{os.path.basename(name)}"
130129
modpath = os.path.join(lib_graalpython, f"{name}.py")

graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_object.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1551,7 +1551,6 @@ def test_take_ownership(self):
15511551
obj.clear_value()
15521552
assert value == (1, 2, 3, "hello", "world", dummy)
15531553

1554-
@skipIf(is_windows, reason="GR-52900")
15551554
def test_async_slots(self):
15561555
import asyncio, types, functools
15571556
TestTpAsync = CPyExtType("TestTpAsync",

graalpython/com.oracle.graal.python.test/src/tests/test_startup.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,19 @@
4444
import platform
4545

4646
# Both lists should remain as small as possible to avoid adding overhead to startup
47+
IS_WINDOWS = platform.system() == 'Windows'
48+
WINDOWS_CORE_MODULES = ['_nt', '_winapi', '_overlapped', 'winreg', '_winreg'] if IS_WINDOWS else []
49+
WINDOWS_FULL_STARTUP_MODULES = [
50+
'_datetime',
51+
'datetime',
52+
'_ctypes',
53+
'_struct',
54+
'struct',
55+
'ctypes._endian',
56+
'ctypes',
57+
'ctypes.wintypes',
58+
] if IS_WINDOWS else []
59+
4760
expected_nosite_startup_modules = [
4861
'_frozen_importlib',
4962
'_frozen_importlib_external',
@@ -52,7 +65,7 @@
5265
'_sysconfig',
5366
'java',
5467
'pip_hook',
55-
] + (['_nt'] if platform.system() == 'Windows' else [])
68+
] + WINDOWS_CORE_MODULES
5669

5770
expected_full_startup_modules = expected_nosite_startup_modules + [
5871
'_abc',
@@ -63,11 +76,12 @@
6376
'stat',
6477
'_collections_abc',
6578
'genericpath',
66-
*(['_winapi', 'ntpath'] if platform.system() == 'Windows' else ['posixpath']),
79+
*(['ntpath'] if IS_WINDOWS else ['posixpath']),
6780
'os',
6881
'_sitebuiltins',
6982
'_io',
7083
'io',
84+
*WINDOWS_FULL_STARTUP_MODULES,
7185
'site',
7286
]
7387

@@ -76,12 +90,12 @@ class StartupTests(unittest.TestCase):
7690
def test_startup_nosite(self):
7791
result = subprocess.check_output([sys.executable, '--log.level=FINE', '-S', '-v', '-c', 'print("Hello")'], stderr=subprocess.STDOUT, text=True)
7892
assert 'Hello' in result
79-
imports = re.findall("import '(\S+)'", result)
93+
imports = re.findall(r"import '(\S+)'", result)
8094
self.assertEqual(expected_nosite_startup_modules, imports)
8195

8296
@unittest.skipUnless(sys.implementation.name == 'graalpy', "GraalPy-specific test")
8397
def test_startup_full(self):
8498
result = subprocess.check_output([sys.executable, '--log.level=FINE', '-s', '-v', '-c', 'print("Hello")'], stderr=subprocess.STDOUT, text=True)
8599
assert 'Hello' in result
86-
imports = re.findall("import '(\S+)'", result)
100+
imports = re.findall(r"import '(\S+)'", result)
87101
self.assertEqual(expected_full_startup_modules, imports)

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
import com.oracle.graal.python.builtins.modules.MsvcrtModuleBuiltins;
9595
import com.oracle.graal.python.builtins.modules.NtModuleBuiltins;
9696
import com.oracle.graal.python.builtins.modules.OperatorModuleBuiltins;
97+
import com.oracle.graal.python.builtins.modules.OverlappedModuleBuiltins;
9798
import com.oracle.graal.python.builtins.modules.PolyglotModuleBuiltins;
9899
import com.oracle.graal.python.builtins.modules.PosixModuleBuiltins;
99100
import com.oracle.graal.python.builtins.modules.PosixShMemModuleBuiltins;
@@ -122,6 +123,7 @@
122123
import com.oracle.graal.python.builtins.modules.WarningsModuleBuiltins;
123124
import com.oracle.graal.python.builtins.modules.WeakRefModuleBuiltins;
124125
import com.oracle.graal.python.builtins.modules.WinapiModuleBuiltins;
126+
import com.oracle.graal.python.builtins.modules.WinregLegacyModuleBuiltins;
125127
import com.oracle.graal.python.builtins.modules.WinregModuleBuiltins;
126128
import com.oracle.graal.python.builtins.modules.ast.AstBuiltins;
127129
import com.oracle.graal.python.builtins.modules.ast.AstModuleBuiltins;
@@ -418,6 +420,7 @@
418420
import com.oracle.truffle.api.object.Shape;
419421
import com.oracle.truffle.api.source.Source;
420422
import com.oracle.truffle.api.strings.TruffleString;
423+
import org.graalvm.nativeimage.ImageInfo;
421424

422425
/**
423426
* The core is a historical artifact and PythonContext and Python3Core should be merged.
@@ -435,18 +438,13 @@ public abstract class Python3Core {
435438

436439
private static TruffleString[] getCoreFiles() {
437440
// Order matters!
438-
List<TruffleString> coreFiles = List.of(
441+
return new TruffleString[]{
439442
T___GRAALPYTHON__,
440443
T__SRE,
441444
T__SYSCONFIG,
442445
T_JAVA,
443-
toTruffleStringUncached("pip_hook"));
444-
if (PythonLanguage.getPythonOS() == PythonOS.PLATFORM_WIN32) {
445-
coreFiles = new ArrayList<>(coreFiles);
446-
coreFiles.add(toTruffleStringUncached("_nt"));
447-
}
448-
449-
return coreFiles.toArray(new TruffleString[0]);
446+
toTruffleStringUncached("pip_hook")
447+
};
450448
}
451449

452450
private PythonBuiltins[] builtins;
@@ -561,6 +559,8 @@ private static PythonBuiltins[] initializeBuiltins(TruffleLanguage.Env env) {
561559
new WinregModuleBuiltins(),
562560
new MsvcrtModuleBuiltins(),
563561
new WinapiModuleBuiltins(),
562+
new OverlappedModuleBuiltins(),
563+
new WinregLegacyModuleBuiltins(),
564564
new CryptModuleBuiltins(),
565565
new ScandirIteratorBuiltins(),
566566
new DirEntryBuiltins(),
@@ -1051,6 +1051,17 @@ private void initializePython3Core(TruffleString coreHome) {
10511051
initialized = true;
10521052
}
10531053

1054+
private void initializeWindowsCoreFiles(TruffleString coreHome) {
1055+
if (PythonLanguage.getPythonOS() == PythonOS.PLATFORM_WIN32) {
1056+
assert !ImageInfo.inImageBuildtimeCode();
1057+
loadFile(toTruffleStringUncached("_nt"), coreHome);
1058+
loadFile(toTruffleStringUncached("_winapi"), toTruffleStringUncached("modules/_winapi"), coreHome);
1059+
loadFile(toTruffleStringUncached("_overlapped"), toTruffleStringUncached("modules/_overlapped"), coreHome);
1060+
loadFile(toTruffleStringUncached("winreg"), toTruffleStringUncached("modules/winreg"), coreHome);
1061+
loadFile(toTruffleStringUncached("_winreg"), toTruffleStringUncached("modules/_winreg"), coreHome);
1062+
}
1063+
}
1064+
10541065
/**
10551066
* Run post-initialization code that needs a fully working Python environment. This will be run
10561067
* eagerly when the context is initialized on the JVM or a new context is created on SVM, but is
@@ -1059,6 +1070,7 @@ private void initializePython3Core(TruffleString coreHome) {
10591070
public final void postInitialize(Env env) {
10601071
if (!env.isPreInitialization()) {
10611072
initialized = false;
1073+
initializeWindowsCoreFiles(getContext().getCoreHomeOrFail());
10621074

10631075
for (PythonBuiltins builtin : builtins) {
10641076
if (builtin.needsPostInitialize()) {
@@ -1319,26 +1331,34 @@ private Source getInternalSource(TruffleString basename, TruffleString prefix) {
13191331
}
13201332

13211333
private void loadFile(TruffleString s, TruffleString prefix) {
1334+
loadFile(s, s, prefix);
1335+
}
1336+
1337+
private void loadFile(TruffleString s, TruffleString sourceName, TruffleString prefix) {
13221338
PythonModule mod = lookupBuiltinModule(s);
13231339
if (mod == null) {
13241340
// use an anonymous module for the side-effects
13251341
mod = PFactory.createPythonModule(T___ANONYMOUS__);
13261342
}
1327-
loadFile(s, prefix, mod);
1343+
loadFile(s, sourceName, prefix, mod);
13281344
}
13291345

13301346
private void loadFile(TruffleString s, TruffleString prefix, PythonModule mod) {
1347+
loadFile(s, s, prefix, mod);
1348+
}
1349+
1350+
private void loadFile(TruffleString s, TruffleString sourceName, TruffleString prefix, PythonModule mod) {
13311351
if (ImpModuleBuiltins.importFrozenModuleObjectNoRaise(this, cat(T_GRAALPYTHON, T_DOT, s), mod) != null) {
13321352
LOGGER.log(Level.FINE, () -> "import '" + s + "' # <frozen>");
13331353
return;
13341354
}
13351355

13361356
LOGGER.log(Level.FINE, () -> "import '" + s + "'");
13371357
Supplier<CallTarget> getCode = () -> {
1338-
Source source = getInternalSource(s, prefix);
1358+
Source source = getInternalSource(sourceName, prefix);
13391359
return getLanguage().parse(getContext(), source, InputType.FILE, false, 0, false, null, EnumSet.noneOf(FutureFeature.class));
13401360
};
1341-
RootCallTarget callTarget = (RootCallTarget) getLanguage().cacheCode(s, getCode);
1361+
RootCallTarget callTarget = (RootCallTarget) getLanguage().cacheCode(sourceName, getCode);
13421362
PCode code = PFactory.createCode(language, callTarget);
13431363
CallDispatchers.SimpleIndirectInvokeNode.executeUncached(callTarget, PArguments.withGlobals(code, mod));
13441364
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/GraalPythonModuleBuiltins.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -563,8 +563,11 @@ private static Object doLoadBytecodeFile(Object bytecodePath, Object sourcePath,
563563
cacheKey = ARRAY_ACCESSOR_LE.getLong(bytes, 8);
564564
}
565565
if (context.getOption(PythonOptions.VerboseFlag)) {
566-
Object message = PyObjectCallMethodObjArgs.executeUncached(MESSAGE, T_FORMAT, bytecodePath, sourcePath);
567-
CallNode.executeUncached(context.lookupBuiltinModule(T__BOOTSTRAP).getAttribute(T__VERBOSE_MESSAGE), message);
566+
PythonModule bootstrap = context.lookupBuiltinModule(T__BOOTSTRAP);
567+
if (bootstrap != null) {
568+
Object message = PyObjectCallMethodObjArgs.executeUncached(MESSAGE, T_FORMAT, bytecodePath, sourcePath);
569+
CallNode.executeUncached(bootstrap.getAttribute(T__VERBOSE_MESSAGE), message);
570+
}
568571
}
569572
TruffleFile sourceFile = null;
570573
if (sourcePath != PNone.NONE) {
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package com.oracle.graal.python.builtins.modules;
42+
43+
import java.util.List;
44+
45+
import com.oracle.graal.python.annotations.PythonOS;
46+
import com.oracle.graal.python.builtins.CoreFunctions;
47+
import com.oracle.graal.python.builtins.PythonBuiltins;
48+
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
49+
import com.oracle.truffle.api.dsl.NodeFactory;
50+
51+
@CoreFunctions(defineModule = "_overlapped", os = PythonOS.PLATFORM_WIN32)
52+
public final class OverlappedModuleBuiltins extends PythonBuiltins {
53+
@Override
54+
protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFactories() {
55+
return List.of();
56+
}
57+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package com.oracle.graal.python.builtins.modules;
42+
43+
import java.util.List;
44+
45+
import com.oracle.graal.python.annotations.PythonOS;
46+
import com.oracle.graal.python.builtins.CoreFunctions;
47+
import com.oracle.graal.python.builtins.PythonBuiltins;
48+
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
49+
import com.oracle.truffle.api.dsl.NodeFactory;
50+
51+
@CoreFunctions(defineModule = "_winreg", os = PythonOS.PLATFORM_WIN32)
52+
public final class WinregLegacyModuleBuiltins extends PythonBuiltins {
53+
@Override
54+
protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFactories() {
55+
return List.of();
56+
}
57+
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/FrozenModules.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ private static final class Map {
103103
private static final PythonFrozenModule GRAALPY__SYSCONFIG = new PythonFrozenModule("GRAALPY__SYSCONFIG", null, false);
104104
private static final PythonFrozenModule GRAALPY_JAVA = new PythonFrozenModule("GRAALPY_JAVA", null, false);
105105
private static final PythonFrozenModule GRAALPY_PIP_HOOK = new PythonFrozenModule("GRAALPY_PIP_HOOK", null, false);
106-
private static final PythonFrozenModule GRAALPY__NT = new PythonFrozenModule("GRAALPY__NT", null, false);
107106
}
108107

109108
public static final PythonFrozenModule lookup(String name) {
@@ -236,8 +235,6 @@ public static final PythonFrozenModule lookup(String name) {
236235
return Map.GRAALPY_JAVA;
237236
case "graalpy.pip_hook":
238237
return Map.GRAALPY_PIP_HOOK;
239-
case "graalpy._nt":
240-
return Map.GRAALPY__NT;
241238
default:
242239
return null;
243240
}

graalpython/lib-graalpython/_nt.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
22
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
33
#
44
# The Universal Permissive License (UPL), Version 1.0
@@ -42,23 +42,29 @@
4242

4343
def _add_dll_directory(path):
4444
import ctypes, os
45-
AddDllDirectory = ctypes.windll.kernel32['AddDllDirectory']
45+
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
46+
kernel32.GetLastError.argtypes = []
47+
kernel32.GetLastError.restype = ctypes.c_ulong
48+
AddDllDirectory = kernel32.AddDllDirectory
4649
AddDllDirectory.argtypes = [ctypes.c_wchar_p]
4750
AddDllDirectory.restype = ctypes.c_void_p
4851
result = AddDllDirectory(os.fspath(path))
4952
if result == 0:
50-
raise OSError(f"add_dll_directory: {ctypes.windll.kernel32.GetLastError()}")
53+
raise OSError(f"add_dll_directory: {kernel32.GetLastError()}")
5154
return result
5255

5356

5457
def _remove_dll_directory(cookie):
5558
import ctypes
56-
RemoveDllDirectory = ctypes.windll.kernel32['RemoveDllDirectory']
59+
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
60+
kernel32.GetLastError.argtypes = []
61+
kernel32.GetLastError.restype = ctypes.c_ulong
62+
RemoveDllDirectory = kernel32.RemoveDllDirectory
5763
RemoveDllDirectory.argtypes = [ctypes.c_void_p]
5864
RemoveDllDirectory.restype = ctypes.c_int
5965
result = RemoveDllDirectory(cookie)
6066
if result == 0:
61-
raise OSError(f"remove_dll_directory: {ctypes.windll.kernel32.GetLastError()}")
67+
raise OSError(f"remove_dll_directory: {kernel32.GetLastError()}")
6268

6369

6470
nt._add_dll_directory = _add_dll_directory

0 commit comments

Comments
 (0)