diff --git a/graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/compiler/SuperInstructionsTests.java b/graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/compiler/SuperInstructionsTests.java new file mode 100644 index 0000000000..0185aa2257 --- /dev/null +++ b/graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/compiler/SuperInstructionsTests.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.graal.python.test.compiler; + +import com.oracle.graal.python.test.PythonTests; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Value; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class SuperInstructionsTests extends PythonTests { + @Test + public void testIsNone() { + assertCompilesAndRuns( + "x = None\n" + + "if x is None:\n" + + " return True\n" + + "return False", + "c.IsNone", + true); + } + + @Test + public void testIsNotNone() { + assertCompilesAndRuns( + "x = object()\n" + + "if x is not None:\n" + + " return False\n" + + "return True", + "c.IsNotNone", + false); + } + + @Test + public void testChainedComparisonsIsNone() { + assertCompilesAndRuns( + "x = None\n" + + "return (42 == 42) and (x is None) and (1 < 2)", + "c.IsNone", + true); + } + + @Test + public void testChainedComparisonsIsNotNone() { + assertCompilesAndRuns( + "x = object()\n" + + "return (42 == 42) and (x is not None) and (1 < 2)", + "c.IsNotNone", + true); + } + + @Test + public void testIsNoneWithForeignNull() { + assertCompilesAndRuns( + "if x is None:\n" + + " return True\n" + + "return False", + "c.IsNone", + true, + (Object) null); + } + + @Test + public void testIsNotNoneWithForeignNull() { + assertCompilesAndRuns( + "if x is not None:\n" + + " return False\n" + + "return True", + "c.IsNotNone", + true, + (Object) null); + } + + private static void assertCompilesAndRuns(String functionBody, String expectedInstruction, boolean expectedResult) { + assertCompilesAndRuns(functionBody, expectedInstruction, expectedResult, new Object[0]); + } + + private static void assertCompilesAndRuns(String functionBody, String expectedInstruction, boolean expectedResult, Object... optionalArgValue) { + assert optionalArgValue.length <= 1; + String sourceText = "def f(" + (optionalArgValue.length == 0 ? "" : "x") + "):\n" + " " + functionBody.replace("\n", "\n "); + try (Context context = Context.newBuilder("python").allowExperimentalOptions(true).build()) { + context.eval("python", sourceText); + Value function = context.getBindings("python").getMember("f"); + Value result = function.execute(optionalArgValue); + assertEquals(expectedResult, result.asBoolean()); + String disassembly = context.eval("python", "import __graalpython__\n__graalpython__.dis(f)").asString(); + assertTrue(disassembly, disassembly.contains(expectedInstruction)); + } + } + +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/compiler/bytecode_dsl/RootNodeCompiler.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/compiler/bytecode_dsl/RootNodeCompiler.java index d0b1808997..34df6c3035 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/compiler/bytecode_dsl/RootNodeCompiler.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/compiler/bytecode_dsl/RootNodeCompiler.java @@ -2540,7 +2540,18 @@ public Void visit(ExprTy.Compare node) { for (int i = 0; i < node.comparators.length; i++) { beginTraceLineChecked(b); - beginComparison(node.ops[i]); + + boolean comparesWithNone = node.comparators[i] instanceof ExprTy.Constant constant && constant.value.kind == ConstantValue.Kind.NONE; + boolean isNoneComparison = comparesWithNone && (node.ops[i] == CmpOpTy.Is); + boolean isNotNoneComparison = comparesWithNone && (node.ops[i] == CmpOpTy.IsNot); + + if (isNoneComparison) { + b.beginIsNone(); + } else if (isNotNoneComparison) { + b.beginIsNotNone(); + } else { + beginComparison(node.ops[i]); + } if (i == 0) { node.left.accept(this); @@ -2550,15 +2561,21 @@ public Void visit(ExprTy.Compare node) { loadAndEndTemporaryLocal(tmp); } - if (i != node.comparators.length - 1) { - b.beginTeeLocal(tmp); - } - node.comparators[i].accept(this); - if (i != node.comparators.length - 1) { - b.endTeeLocal(); + if (isNoneComparison) { + b.endIsNone(); + } else if (isNotNoneComparison) { + b.endIsNotNone(); + } else { + if (i != node.comparators.length - 1) { + b.beginTeeLocal(tmp); + } + node.comparators[i].accept(this); + if (i != node.comparators.length - 1) { + b.endTeeLocal(); + } + endComparison(node.ops[i]); } - endComparison(node.ops[i]); endTraceLineChecked(node, b); } @@ -4684,6 +4701,10 @@ private boolean producesBoolean(ExprTy node) { } private void visitCondition(ExprTy node) { + if (tryVisitIsNoneCondition(node)) { + return; + } + boolean mayNeedCoercion = !producesBoolean(node); if (mayNeedCoercion) { b.beginYes(); @@ -4696,6 +4717,27 @@ private void visitCondition(ExprTy node) { } } + private boolean tryVisitIsNoneCondition(ExprTy node) { + if (!(node instanceof ExprTy.Compare cmp) || cmp.comparators == null || cmp.comparators.length != 1 || !(cmp.comparators[0] instanceof ExprTy.Constant constant) || + constant.value.kind != Kind.NONE) { + return false; + } + + if (cmp.ops[0] == CmpOpTy.Is) { + b.beginIsNone(); + cmp.left.accept(this); + b.endIsNone(); + } else if (cmp.ops[0] == CmpOpTy.IsNot) { + b.beginIsNotNone(); + cmp.left.accept(this); + b.endIsNotNone(); + } else { + return false; + } + + return true; + } + @Override public Void visit(StmtTy.Import node) { boolean newStatement = beginSourceSection(node, b); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java index 528a599194..45cd66eda1 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java @@ -234,10 +234,12 @@ import com.oracle.graal.python.nodes.object.BuiltinClassProfiles.IsBuiltinObjectProfile; import com.oracle.graal.python.nodes.object.GetClassNode; import com.oracle.graal.python.nodes.object.GetClassNode.GetPythonObjectClassNode; +import com.oracle.graal.python.nodes.object.IsForeignObjectNode; import com.oracle.graal.python.nodes.object.IsNode; import com.oracle.graal.python.nodes.util.CannotCastException; import com.oracle.graal.python.nodes.util.CastToTruffleStringNode; import com.oracle.graal.python.nodes.util.ExceptionStateNodes; +import com.oracle.graal.python.nodes.util.LazyInteropLibrary; import com.oracle.graal.python.runtime.ExecutionContext.CalleeContext; import com.oracle.graal.python.runtime.IndirectCallData.BoundaryCallData; import com.oracle.graal.python.runtime.PythonContext; @@ -4305,19 +4307,41 @@ static Object makeGeneric(VirtualFrame frame, PTuple params, } /** - * Returns false, if provided argument is PNone, else returns true. + * Returns false if the provided argument is PNone, else returns true. */ - @Operation + @Operation(storeBytecodeIndex = false) public static final class IsNotNone { @Specialization - static boolean makeNone(PNone none) { + static boolean doNone(PNone none) { return false; } @Fallback - static boolean makeOther(Object o) { + static boolean doOther(Object o, + @Bind Node inliningTarget, + @Cached IsForeignObjectNode isForeignObjectNode, + @Cached LazyInteropLibrary lib) { + return !(isForeignObjectNode.execute(inliningTarget, o) && lib.get(inliningTarget).isNull(o)); + } + } + + /** + * Returns true if the provided argument is PNone, else returns false. + */ + @Operation(storeBytecodeIndex = false) + public static final class IsNone { + @Specialization + static boolean doNone(PNone none) { return true; } + + @Fallback + static boolean doOther(Object o, + @Bind Node inliningTarget, + @Cached IsForeignObjectNode isForeignObjectNode, + @Cached LazyInteropLibrary lib) { + return isForeignObjectNode.execute(inliningTarget, o) && lib.get(inliningTarget).isNull(o); + } } /**