Skip to content

Commit d6fbfa2

Browse files
committed
[GR-71679] Fix generator frame sync
PullRequest: graalpython/4127
2 parents b011695 + e96effc commit d6fbfa2

File tree

17 files changed

+328
-97
lines changed

17 files changed

+328
-97
lines changed

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

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# Copyright (C) 1996-2017 Python Software Foundation
33
#
44
# Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
5+
import itertools
56
import sys
67
import os
78
import unittest
@@ -506,4 +507,130 @@ def b():
506507
assert gen_b.gi_yieldfrom.gi_code.co_name == 'q'
507508

508509
gen_b.send(None)
509-
assert gen_b.gi_yieldfrom is None
510+
assert gen_b.gi_yieldfrom is None
511+
512+
513+
def _test_generator_frame(checks):
514+
def gen():
515+
a = 1
516+
yield
517+
b = 2
518+
yield
519+
c = 3
520+
521+
g = gen()
522+
f = None
523+
if checks[0]:
524+
f = g.gi_frame
525+
assert f
526+
assert f.f_back is None
527+
assert f.f_globals == globals()
528+
assert f.f_locals == {}
529+
assert f.f_lineno == gen.__code__.co_firstlineno
530+
next(g)
531+
if checks[1]:
532+
if not f:
533+
f = g.gi_frame
534+
assert f
535+
assert f.f_back is None
536+
assert f.f_globals == globals()
537+
assert f.f_locals == {'a': 1}
538+
assert f.f_lineno == gen.__code__.co_firstlineno + 2
539+
assert f is g.gi_frame
540+
next(g)
541+
if checks[2]:
542+
if not f:
543+
f = g.gi_frame
544+
assert f
545+
assert f.f_back is None
546+
assert f.f_globals == globals()
547+
assert f.f_locals == {'a': 1, 'b': 2}
548+
assert f.f_lineno == gen.__code__.co_firstlineno + 4
549+
assert f is g.gi_frame
550+
try:
551+
next(g)
552+
assert False
553+
except StopIteration:
554+
pass
555+
if f:
556+
assert f.f_back is sys._getframe()
557+
assert f.f_globals is globals()
558+
assert f.f_locals == {'a': 1, 'b': 2, 'c': 3}
559+
# TODO GR-61955
560+
if sys.implementation.name != "graalpy" or not __graalpython__.is_bytecode_dsl_interpreter:
561+
assert f.f_lineno == gen.__code__.co_firstlineno + 5
562+
assert not g.gi_frame
563+
564+
565+
def test_generator_frame():
566+
# Generte a whole matrix of possibilities of where the frame gets created vs re-synced
567+
for checks in itertools.product([False, True], repeat=3):
568+
_test_generator_frame(checks)
569+
570+
571+
def test_generator_frame_from_getframe():
572+
def gen():
573+
a = 1
574+
yield sys._getframe()
575+
b = 2
576+
577+
g = gen()
578+
f = next(g)
579+
assert f
580+
assert f.f_back is None
581+
assert f.f_globals == globals()
582+
assert f.f_locals == {'a': 1}
583+
assert f.f_lineno == gen.__code__.co_firstlineno + 2
584+
assert f is g.gi_frame
585+
try:
586+
next(g)
587+
assert False
588+
except StopIteration:
589+
pass
590+
assert f.f_back is sys._getframe()
591+
assert f.f_globals is globals()
592+
assert f.f_locals == {'a': 1, 'b': 2}
593+
# TODO GR-61955
594+
if sys.implementation.name != "graalpy" or not __graalpython__.is_bytecode_dsl_interpreter:
595+
assert f.f_lineno == gen.__code__.co_firstlineno + 3
596+
597+
598+
def test_generator_frame_in_running_generator():
599+
def gen():
600+
self = yield
601+
assert self.gi_running
602+
assert not self.gi_suspended
603+
assert self.gi_frame.f_lineno == gen.__code__.co_firstlineno + 4
604+
assert self.gi_frame.f_globals == globals()
605+
assert self.gi_frame.f_locals.get('self') is self
606+
assert self.gi_frame is sys._getframe()
607+
608+
g = gen()
609+
next(g)
610+
try:
611+
g.send(g)
612+
assert False
613+
except StopIteration:
614+
pass
615+
616+
617+
def test_generator_frame_in_generator_up_stack():
618+
def validate(self):
619+
assert self.gi_running
620+
assert not self.gi_suspended
621+
assert self.gi_frame.f_lineno == gen.__code__.co_firstlineno + 2
622+
assert self.gi_frame.f_globals == globals()
623+
assert self.gi_frame.f_locals.get('self') is self
624+
assert self.gi_frame is sys._getframe(1)
625+
626+
def gen():
627+
self = yield
628+
validate(self)
629+
630+
g = gen()
631+
next(g)
632+
try:
633+
g.send(g)
634+
assert False
635+
except StopIteration:
636+
pass

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,18 @@
3636
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
3737
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3838
# SOFTWARE.
39+
import sys
40+
import unittest
3941

4042

4143
def test_sysconfig():
4244
import os, sysconfig
43-
os.environ["PATH"] += os.pathsep + r" : \ " + os.pathsep;
45+
os.environ["PATH"] += os.pathsep + r" : \ " + os.pathsep
4446
sysconfig.get_config_vars()
4547
# must not fail
48+
49+
50+
@unittest.skipIf(sys.implementation.name != 'graalpy', "GraalPy-only test")
51+
def test_sysconfigdata():
52+
# Maturin loads this directly, make sure the import works
53+
import _sysconfigdata

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@
111111
import com.oracle.graal.python.builtins.modules.StringModuleBuiltins;
112112
import com.oracle.graal.python.builtins.modules.StructModuleBuiltins;
113113
import com.oracle.graal.python.builtins.modules.SysModuleBuiltins;
114+
import com.oracle.graal.python.builtins.modules.SysconfigModuleBuiltins;
114115
import com.oracle.graal.python.builtins.modules.ThreadModuleBuiltins;
115116
import com.oracle.graal.python.builtins.modules.TimeModuleBuiltins;
116117
import com.oracle.graal.python.builtins.modules.TokenizeModuleBuiltins;
@@ -566,6 +567,7 @@ private static PythonBuiltins[] initializeBuiltins(TruffleLanguage.Env env) {
566567
new WeakRefModuleBuiltins(),
567568
new ReferenceTypeBuiltins(),
568569
new TracemallocModuleBuiltins(),
570+
new SysconfigModuleBuiltins(),
569571
// contextvars
570572
new ContextVarBuiltins(),
571573
new ContextBuiltins(),
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright (c) 2025, 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 static com.oracle.graal.python.nodes.BuiltinNames.J__SYSCONFIG;
44+
45+
import java.util.Collections;
46+
import java.util.List;
47+
48+
import com.oracle.graal.python.builtins.CoreFunctions;
49+
import com.oracle.graal.python.builtins.PythonBuiltins;
50+
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
51+
import com.oracle.truffle.api.dsl.NodeFactory;
52+
53+
@CoreFunctions(defineModule = J__SYSCONFIG, isEager = true)
54+
public final class SysconfigModuleBuiltins extends PythonBuiltins {
55+
@Override
56+
protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFactories() {
57+
return Collections.emptyList();
58+
}
59+
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/frame/PFrame.java

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ public static final class Reference {
163163
// by a callee frame to inform the caller that it should materialize itself when it returns.
164164
private boolean escaped = false;
165165

166-
private final Reference callerInfo;
166+
private Reference callerInfo;
167167

168168
public Reference(RootNode rootNode, Reference callerInfo) {
169169
this.rootNode = rootNode;
@@ -174,9 +174,8 @@ public RootNode getRootNode() {
174174
return rootNode;
175175
}
176176

177-
public void setBackref(PFrame.Reference backref) {
178-
assert pyFrame != null : "setBackref should only be called when the PFrame escaped";
179-
pyFrame.setBackref(backref);
177+
public void setCallerInfo(Reference callerInfo) {
178+
this.callerInfo = callerInfo;
180179
}
181180

182181
public void markAsEscaped() {
@@ -307,25 +306,20 @@ public Thread getThread() {
307306
return thread;
308307
}
309308

310-
public PFrame.Reference getBackref() {
311-
return backref;
312-
}
313-
314-
public void setBackref(PFrame.Reference backref) {
315-
// GR-41914
316-
// @formatter:off
317-
// assert this.backref == null || this.backref == backref : "setBackref tried to set a backref different to the one that was previously attached";
318-
// @formatter:on
319-
this.backref = backref;
320-
}
321-
322309
public void setLine(int line) {
323310
if (lockLine) {
324311
return;
325312
}
326313
this.line = line;
327314
}
328315

316+
public void resetLine() {
317+
if (lockLine) {
318+
return;
319+
}
320+
this.line = UNINITIALIZED_LINE;
321+
}
322+
329323
public void setLineLock(int line) {
330324
this.line = line;
331325
this.lockLine = true;

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/generator/CommonGeneratorBuiltins.java

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import com.oracle.graal.python.builtins.objects.exception.PBaseException;
6262
import com.oracle.graal.python.builtins.objects.exception.PrepareExceptionNode;
6363
import com.oracle.graal.python.builtins.objects.frame.PFrame;
64+
import com.oracle.graal.python.builtins.objects.function.PArguments;
6465
import com.oracle.graal.python.builtins.objects.traceback.PTraceback;
6566
import com.oracle.graal.python.builtins.objects.type.slots.TpSlotIterNext.TpIterNextBuiltin;
6667
import com.oracle.graal.python.lib.IteratorExhausted;
@@ -103,6 +104,7 @@
103104
import com.oracle.truffle.api.nodes.DirectCallNode;
104105
import com.oracle.truffle.api.nodes.IndirectCallNode;
105106
import com.oracle.truffle.api.nodes.Node;
107+
import com.oracle.truffle.api.nodes.RootNode;
106108
import com.oracle.truffle.api.profiles.InlinedBranchProfile;
107109
import com.oracle.truffle.api.profiles.InlinedConditionProfile;
108110

@@ -183,19 +185,24 @@ static Object cachedBytecodeDSL(VirtualFrame frame, Node inliningTarget, PGenera
183185
* generator root.
184186
*/
185187
MaterializedFrame generatorFrame = self.getGeneratorFrame();
186-
Object[] arguments = new Object[]{generatorFrame, sendValue};
188+
Object[] callArguments = new Object[]{generatorFrame, sendValue};
189+
Object[] generatorArguments = generatorFrame.getArguments();
187190
if (frame == null) {
188191
PythonContext context = PythonContext.get(inliningTarget);
189192
PythonThreadState threadState = context.getThreadState(context.getLanguage(inliningTarget));
190-
Object state = IndirectCalleeContext.enter(threadState, generatorFrame.getArguments());
193+
Object state = IndirectCalleeContext.enter(threadState, generatorArguments);
194+
PFrame.Reference ref = PArguments.getCurrentFrameInfo(generatorArguments);
195+
ref.setCallerInfo(PArguments.getCallerFrameInfo(generatorArguments));
191196
try {
192-
generatorResult = callNode.call(arguments);
197+
generatorResult = callNode.call(callArguments);
193198
} finally {
194199
IndirectCalleeContext.exit(threadState, state);
195200
}
196201
} else {
197-
callContext.executePrepareCall(frame, generatorFrame.getArguments(), rootNode.getCallerFlags());
198-
generatorResult = callNode.call(arguments);
202+
callContext.executePrepareCall(frame, generatorArguments, rootNode.getCallerFlags());
203+
PFrame.Reference ref = PArguments.getCurrentFrameInfo(generatorArguments);
204+
ref.setCallerInfo(PArguments.getCallerFrameInfo(generatorArguments));
205+
generatorResult = callNode.call(callArguments);
199206
}
200207
} catch (PException e) {
201208
throw handleException(self, inliningTarget, errorProfile, raiseNode, e);
@@ -250,19 +257,24 @@ static Object genericBytecodeDSL(VirtualFrame frame, Node inliningTarget, PGener
250257
// See the cached specialization for notes about the arguments handling
251258
PRootNode rootNode = PGenerator.unwrapContinuationRoot((ContinuationRootNode) callTarget.getRootNode());
252259
MaterializedFrame generatorFrame = self.getGeneratorFrame();
253-
Object[] arguments = new Object[]{generatorFrame, sendValue};
260+
Object[] callArguments = new Object[]{generatorFrame, sendValue};
261+
Object[] generatorArguments = generatorFrame.getArguments();
254262
if (frame == null) {
255263
PythonContext context = PythonContext.get(inliningTarget);
256264
PythonThreadState threadState = context.getThreadState(context.getLanguage(inliningTarget));
257-
Object state = IndirectCalleeContext.enter(threadState, generatorFrame.getArguments());
265+
Object state = IndirectCalleeContext.enter(threadState, generatorArguments);
266+
PFrame.Reference ref = PArguments.getCurrentFrameInfo(generatorArguments);
267+
ref.setCallerInfo(PArguments.getCallerFrameInfo(generatorArguments));
258268
try {
259-
generatorResult = callNode.call(callTarget, arguments);
269+
generatorResult = callNode.call(callTarget, callArguments);
260270
} finally {
261271
IndirectCalleeContext.exit(threadState, state);
262272
}
263273
} else {
264-
callContext.executePrepareCall(frame, generatorFrame.getArguments(), rootNode.getCallerFlags());
265-
generatorResult = callNode.call(callTarget, arguments);
274+
callContext.executePrepareCall(frame, generatorArguments, rootNode.getCallerFlags());
275+
PFrame.Reference ref = PArguments.getCurrentFrameInfo(generatorArguments);
276+
ref.setCallerInfo(PArguments.getCallerFrameInfo(generatorArguments));
277+
generatorResult = callNode.call(callTarget, callArguments);
266278
}
267279
} catch (PException e) {
268280
throw handleException(self, inliningTarget, errorProfile, raiseNode, e);
@@ -388,9 +400,16 @@ static Object sendThrow(VirtualFrame frame, PGenerator self, Object typ, Object
388400
// Instead, we throw the exception here and fake entering the generator by adding
389401
// its frame to the traceback manually.
390402
self.markAsFinished();
391-
Node location = self.getCurrentCallTarget().getRootNode();
403+
Node location;
404+
RootNode rootNode = self.getCurrentCallTarget().getRootNode();
405+
if (PythonOptions.ENABLE_BYTECODE_DSL_INTERPRETER) {
406+
location = self.getBytecodeNode();
407+
} else {
408+
location = rootNode;
409+
}
392410
MaterializedFrame generatorFrame = self.getGeneratorFrame();
393-
PFrame pFrame = MaterializeFrameNode.materializeGeneratorFrame(location, generatorFrame, PFrame.Reference.EMPTY);
411+
PFrame.Reference ref = new PFrame.Reference(rootNode, PFrame.Reference.EMPTY);
412+
PFrame pFrame = MaterializeFrameNode.materializeGeneratorFrame(location, generatorFrame, self.getGlobals(), ref);
394413
FrameInfo info = (FrameInfo) generatorFrame.getFrameDescriptor().getInfo();
395414
pFrame.setLine(info.getFirstLineNumber());
396415
Object existingTracebackObj = getTracebackNode.execute(inliningTarget, instance);

0 commit comments

Comments
 (0)