From 59ff18f06821ca6a67f5f794593bad690ef9f927 Mon Sep 17 00:00:00 2001 From: Doug Simon Date: Tue, 17 Dec 2024 19:33:57 +0100 Subject: [PATCH] expand operations supported by org.graalvm.word.Word --- .../jdk/graal/compiler/test/WordTests.java | 184 ++++++++++++ .../src/jdk/graal/compiler/word/Word.java | 87 +++--- sdk/mx.sdk/suite.py | 15 + .../src/org/graalvm/word/test/WordTests.java | 268 ++++++++++++++++++ .../src/org/graalvm/word/Word.java | 69 +++++ .../src/org/graalvm/word/WordFactory.java | 92 +++--- 6 files changed, 634 insertions(+), 81 deletions(-) create mode 100644 compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/test/WordTests.java create mode 100644 sdk/src/org.graalvm.word.test/src/org/graalvm/word/test/WordTests.java create mode 100644 sdk/src/org.graalvm.word/src/org/graalvm/word/Word.java diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/test/WordTests.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/test/WordTests.java new file mode 100644 index 000000000000..39c0614ca9e2 --- /dev/null +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/test/WordTests.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.graal.compiler.test; + +import static jdk.graal.compiler.core.common.calc.UnsignedMath.aboveOrEqual; +import static jdk.graal.compiler.core.common.calc.UnsignedMath.aboveThan; +import static jdk.graal.compiler.core.common.calc.UnsignedMath.belowOrEqual; +import static jdk.graal.compiler.core.common.calc.UnsignedMath.belowThan; + +import org.graalvm.word.Pointer; +import org.graalvm.word.SignedWord; +import org.graalvm.word.UnsignedWord; +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +/** + * Tests word operations on boxed values produced by {@link org.graalvm.word.WordFactory} and + * {@link jdk.graal.compiler.word.WordFactory}. + */ +public class WordTests { + + static long[] words = { + Long.MIN_VALUE, + Long.MIN_VALUE + 1, + -1L, + 0L, + 1L, + Long.MAX_VALUE - 1, + Long.MAX_VALUE, + Integer.MAX_VALUE - 1L, + Integer.MAX_VALUE, + Integer.MAX_VALUE + 1L, + Integer.MIN_VALUE - 1L, + Integer.MIN_VALUE, + Integer.MIN_VALUE + 1L + }; + + static SignedWord graalSigned(long val) { + return jdk.graal.compiler.word.WordFactory.signed(val); + } + + static UnsignedWord graalUnsigned(long val) { + return jdk.graal.compiler.word.WordFactory.unsigned(val); + } + + static Pointer graalPointer(long val) { + return jdk.graal.compiler.word.WordFactory.pointer(val); + } + + static List signedWords = Stream.concat( + LongStream.of(words).mapToObj(org.graalvm.word.WordFactory::signed), + LongStream.of(words).mapToObj(WordTests::graalSigned)).toList(); + static List unsignedWords = Stream.concat( + LongStream.of(words).mapToObj(org.graalvm.word.WordFactory::unsigned), + LongStream.of(words).mapToObj(WordTests::graalUnsigned)).toList(); + static List pointers = Stream.concat( + LongStream.of(words).mapToObj(org.graalvm.word.WordFactory::pointer), + LongStream.of(words).mapToObj(WordTests::graalPointer)).toList(); + + @Test + public void testSigned() { + for (var x : signedWords) { + Assert.assertEquals(x.not().rawValue(), ~x.rawValue()); + + for (var y : signedWords) { + Assert.assertEquals(x.equal(y), x.rawValue() == y.rawValue()); + Assert.assertEquals(x.notEqual(y), x.rawValue() != y.rawValue()); + + Assert.assertEquals(x.add(y).rawValue(), x.rawValue() + y.rawValue()); + Assert.assertEquals(x.add(y).rawValue(), x.rawValue() + y.rawValue()); + Assert.assertEquals(x.subtract(y).rawValue(), x.rawValue() - y.rawValue()); + Assert.assertEquals(x.multiply(y).rawValue(), x.rawValue() * y.rawValue()); + + if (y.rawValue() != 0) { + Assert.assertEquals(x.signedDivide(y).rawValue(), x.rawValue() / y.rawValue()); + Assert.assertEquals(x.signedRemainder(y).rawValue(), x.rawValue() % y.rawValue()); + } + Assert.assertEquals(x.and(y).rawValue(), x.rawValue() & y.rawValue()); + Assert.assertEquals(x.or(y).rawValue(), x.rawValue() | y.rawValue()); + Assert.assertEquals(x.xor(y).rawValue(), x.rawValue() ^ y.rawValue()); + + Assert.assertEquals(x.greaterThan(y), x.rawValue() > y.rawValue()); + Assert.assertEquals(x.greaterOrEqual(y), x.rawValue() >= y.rawValue()); + Assert.assertEquals(x.lessThan(y), x.rawValue() < y.rawValue()); + Assert.assertEquals(x.lessOrEqual(y), x.rawValue() <= y.rawValue()); + + Assert.assertEquals(x.shiftLeft((UnsignedWord) y).rawValue(), x.rawValue() << y.rawValue()); + Assert.assertEquals(x.signedShiftRight((UnsignedWord) y).rawValue(), x.rawValue() >> y.rawValue()); + } + } + } + + @Test + public void testUnsigned() { + for (var x : unsignedWords) { + Assert.assertEquals(x.not().rawValue(), ~x.rawValue()); + + for (var y : unsignedWords) { + Assert.assertEquals(x.equal(y), x.rawValue() == y.rawValue()); + Assert.assertEquals(x.notEqual(y), x.rawValue() != y.rawValue()); + + Assert.assertEquals(x.add(y).rawValue(), x.rawValue() + y.rawValue()); + Assert.assertEquals(x.subtract(y).rawValue(), x.rawValue() - y.rawValue()); + Assert.assertEquals(x.multiply(y).rawValue(), x.rawValue() * y.rawValue()); + + if (y.rawValue() != 0) { + Assert.assertEquals(x.unsignedDivide(y).rawValue(), Long.divideUnsigned(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.unsignedRemainder(y).rawValue(), Long.remainderUnsigned(x.rawValue(), y.rawValue())); + } + Assert.assertEquals(x.and(y).rawValue(), x.rawValue() & y.rawValue()); + Assert.assertEquals(x.or(y).rawValue(), x.rawValue() | y.rawValue()); + Assert.assertEquals(x.xor(y).rawValue(), x.rawValue() ^ y.rawValue()); + + Assert.assertEquals(x.aboveThan(y), aboveThan(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.aboveOrEqual(y), aboveOrEqual(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.belowThan(y), belowThan(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.belowOrEqual(y), belowOrEqual(x.rawValue(), y.rawValue())); + + Assert.assertEquals(x.shiftLeft(y).rawValue(), x.rawValue() << y.rawValue()); + Assert.assertEquals(x.unsignedShiftRight(y).rawValue(), x.rawValue() >>> y.rawValue()); + } + } + } + + @Test + public void testPointer() { + for (var x : pointers) { + Assert.assertEquals(x.not().rawValue(), ~x.rawValue()); + Assert.assertEquals(x.isNull(), x.rawValue() == 0); + Assert.assertEquals(x.isNonNull(), x.rawValue() != 0); + + for (var y : pointers) { + Assert.assertEquals(x.equal(y), x.rawValue() == y.rawValue()); + Assert.assertEquals(x.notEqual(y), x.rawValue() != y.rawValue()); + + Assert.assertEquals(x.add(y).rawValue(), x.rawValue() + y.rawValue()); + Assert.assertEquals(x.subtract(y).rawValue(), x.rawValue() - y.rawValue()); + Assert.assertEquals(x.multiply(y).rawValue(), x.rawValue() * y.rawValue()); + + if (y.rawValue() != 0) { + Assert.assertEquals(x.unsignedDivide(y).rawValue(), Long.divideUnsigned(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.unsignedRemainder(y).rawValue(), Long.remainderUnsigned(x.rawValue(), y.rawValue())); + } + Assert.assertEquals(x.and(y).rawValue(), x.rawValue() & y.rawValue()); + Assert.assertEquals(x.or(y).rawValue(), x.rawValue() | y.rawValue()); + Assert.assertEquals(x.xor(y).rawValue(), x.rawValue() ^ y.rawValue()); + + Assert.assertEquals(x.aboveThan(y), aboveThan(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.aboveOrEqual(y), aboveOrEqual(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.belowThan(y), belowThan(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.belowOrEqual(y), belowOrEqual(x.rawValue(), y.rawValue())); + + Assert.assertEquals(x.shiftLeft(y).rawValue(), x.rawValue() << y.rawValue()); + Assert.assertEquals(x.unsignedShiftRight(y).rawValue(), x.rawValue() >>> y.rawValue()); + } + } + } +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/word/Word.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/word/Word.java index 5c791adf39d7..ee3fb36c7db8 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/word/Word.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/word/Word.java @@ -118,6 +118,13 @@ private static T box(long val) { return (T) HostedWord.boxLong(val); } + private static Word cast(WordBase val) { + if (val instanceof Word word) { + return word; + } + return HostedWord.boxLong(val.rawValue()); + } + protected abstract long unbox(); private static Word intParam(int val) { @@ -172,13 +179,13 @@ public long rawValue() { @Override @Operation(node = AddNode.class) public Word add(SignedWord val) { - return add((Word) val); + return add(cast(val)); } @Override @Operation(node = AddNode.class) public Word add(UnsignedWord val) { - return add((Word) val); + return add(cast(val)); } @Override @@ -195,13 +202,13 @@ public Word add(Word val) { @Override @Operation(node = SubNode.class) public Word subtract(SignedWord val) { - return subtract((Word) val); + return subtract(cast(val)); } @Override @Operation(node = SubNode.class) public Word subtract(UnsignedWord val) { - return subtract((Word) val); + return subtract(cast(val)); } @Override @@ -218,13 +225,13 @@ public Word subtract(Word val) { @Override @Operation(node = MulNode.class) public Word multiply(SignedWord val) { - return multiply((Word) val); + return multiply(cast(val)); } @Override @Operation(node = MulNode.class) public Word multiply(UnsignedWord val) { - return multiply((Word) val); + return multiply(cast(val)); } @Override @@ -241,7 +248,7 @@ public Word multiply(Word val) { @Override @Operation(opcode = Opcode.INTEGER_DIVISION_NODE_CLASS, node = SignedDivNode.class) public Word signedDivide(SignedWord val) { - return signedDivide((Word) val); + return signedDivide(cast(val)); } @Override @@ -258,13 +265,13 @@ public Word signedDivide(Word val) { @Override @Operation(opcode = Opcode.INTEGER_DIVISION_NODE_CLASS, node = UnsignedDivNode.class) public Word unsignedDivide(UnsignedWord val) { - return unsignedDivide((Word) val); + return unsignedDivide(cast(val)); } @Override @Operation(opcode = Opcode.INTEGER_DIVISION_NODE_CLASS, node = UnsignedDivNode.class) public Word unsignedDivide(int val) { - return signedDivide(intParam(val)); + return unsignedDivide(intParam(val)); } @Operation(opcode = Opcode.INTEGER_DIVISION_NODE_CLASS, node = UnsignedDivNode.class) @@ -275,7 +282,7 @@ public Word unsignedDivide(Word val) { @Override @Operation(opcode = Opcode.INTEGER_DIVISION_NODE_CLASS, node = SignedRemNode.class) public Word signedRemainder(SignedWord val) { - return signedRemainder((Word) val); + return signedRemainder(cast(val)); } @Override @@ -292,7 +299,7 @@ public Word signedRemainder(Word val) { @Override @Operation(opcode = Opcode.INTEGER_DIVISION_NODE_CLASS, node = UnsignedRemNode.class) public Word unsignedRemainder(UnsignedWord val) { - return unsignedRemainder((Word) val); + return unsignedRemainder(cast(val)); } @Override @@ -309,7 +316,7 @@ public Word unsignedRemainder(Word val) { @Override @Operation(node = LeftShiftNode.class, rightOperandIsInt = true) public Word shiftLeft(UnsignedWord val) { - return shiftLeft((Word) val); + return shiftLeft(cast(val)); } @Override @@ -326,7 +333,7 @@ public Word shiftLeft(Word val) { @Override @Operation(node = RightShiftNode.class, rightOperandIsInt = true) public Word signedShiftRight(UnsignedWord val) { - return signedShiftRight((Word) val); + return signedShiftRight(cast(val)); } @Override @@ -343,7 +350,7 @@ public Word signedShiftRight(Word val) { @Override @Operation(node = UnsignedRightShiftNode.class, rightOperandIsInt = true) public Word unsignedShiftRight(UnsignedWord val) { - return unsignedShiftRight((Word) val); + return unsignedShiftRight(cast(val)); } @Override @@ -360,13 +367,13 @@ public Word unsignedShiftRight(Word val) { @Override @Operation(node = AndNode.class) public Word and(SignedWord val) { - return and((Word) val); + return and(cast(val)); } @Override @Operation(node = AndNode.class) public Word and(UnsignedWord val) { - return and((Word) val); + return and(cast(val)); } @Override @@ -383,13 +390,13 @@ public Word and(Word val) { @Override @Operation(node = OrNode.class) public Word or(SignedWord val) { - return or((Word) val); + return or(cast(val)); } @Override @Operation(node = OrNode.class) public Word or(UnsignedWord val) { - return or((Word) val); + return or(cast(val)); } @Override @@ -406,13 +413,13 @@ public Word or(Word val) { @Override @Operation(node = XorNode.class) public Word xor(SignedWord val) { - return xor((Word) val); + return xor(cast(val)); } @Override @Operation(node = XorNode.class) public Word xor(UnsignedWord val) { - return xor((Word) val); + return xor(cast(val)); } @Override @@ -447,19 +454,19 @@ public boolean isNonNull() { @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.EQ) public boolean equal(ComparableWord val) { - return equal((Word) val); + return equal(cast(val)); } @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.EQ) public boolean equal(SignedWord val) { - return equal((Word) val); + return rawValue() == val.rawValue(); } @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.EQ) public boolean equal(UnsignedWord val) { - return equal((Word) val); + return equal(cast(val)); } @Override @@ -470,25 +477,25 @@ public boolean equal(int val) { @Operation(opcode = Opcode.COMPARISON, condition = Condition.EQ) public boolean equal(Word val) { - return unbox() == val.unbox(); + return rawValue() == val.unbox(); } @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.NE) public boolean notEqual(ComparableWord val) { - return notEqual((Word) val); + return notEqual(cast(val)); } @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.NE) public boolean notEqual(SignedWord val) { - return notEqual((Word) val); + return notEqual(cast(val)); } @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.NE) public boolean notEqual(UnsignedWord val) { - return notEqual((Word) val); + return notEqual(cast(val)); } @Override @@ -505,7 +512,7 @@ public boolean notEqual(Word val) { @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.LT) public boolean lessThan(SignedWord val) { - return lessThan((Word) val); + return lessThan(cast(val)); } @Override @@ -522,7 +529,7 @@ public boolean lessThan(Word val) { @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.LE) public boolean lessOrEqual(SignedWord val) { - return lessOrEqual((Word) val); + return lessOrEqual(cast(val)); } @Override @@ -539,7 +546,7 @@ public boolean lessOrEqual(Word val) { @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.GT) public boolean greaterThan(SignedWord val) { - return greaterThan((Word) val); + return greaterThan(cast(val)); } @Override @@ -556,7 +563,7 @@ public boolean greaterThan(Word val) { @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.GE) public boolean greaterOrEqual(SignedWord val) { - return greaterOrEqual((Word) val); + return greaterOrEqual(cast(val)); } @Override @@ -573,7 +580,7 @@ public boolean greaterOrEqual(Word val) { @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.BT) public boolean belowThan(UnsignedWord val) { - return belowThan((Word) val); + return belowThan(cast(val)); } @Override @@ -590,7 +597,7 @@ public boolean belowThan(Word val) { @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.BE) public boolean belowOrEqual(UnsignedWord val) { - return belowOrEqual((Word) val); + return belowOrEqual(cast(val)); } @Override @@ -607,7 +614,7 @@ public boolean belowOrEqual(Word val) { @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.AT) public boolean aboveThan(UnsignedWord val) { - return aboveThan((Word) val); + return aboveThan(cast(val)); } @Override @@ -624,7 +631,7 @@ public boolean aboveThan(Word val) { @Override @Operation(opcode = Opcode.COMPARISON, condition = Condition.AE) public boolean aboveOrEqual(UnsignedWord val) { - return aboveOrEqual((Word) val); + return aboveOrEqual(cast(val)); } @Override @@ -793,7 +800,7 @@ public void writeDouble(WordBase offset, double val, LocationIdentity locationId @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeWord(WordBase offset, WordBase val, LocationIdentity locationIdentity) { - UNSAFE.putAddress(add((Word) offset).unbox(), ((Word) val).unbox()); + UNSAFE.putAddress(add((Word) offset).unbox(), (cast(val)).unbox()); } @Override @@ -1071,7 +1078,7 @@ public boolean logicCompareAndSwapObject(WordBase offset, Object expectedValue, @Override @Operation(opcode = Opcode.WRITE_POINTER) public void writeWord(WordBase offset, WordBase val) { - UNSAFE.putAddress(add((Word) offset).unbox(), ((Word) val).unbox()); + UNSAFE.putAddress(add((Word) offset).unbox(), (cast(val)).unbox()); } @Override @@ -1185,8 +1192,8 @@ public boolean logicCompareAndSwapObject(int offset, Object expectedValue, Objec } /** - * This is deprecated because of the easy to mistype name collision between {@link #equals} and - * the other equals routines like {@link #equal(Word)}. In general you should never be + * This is deprecated because of the easy to mistype name collision between {@code equals} and + * the other equals routines like {@link #equal(Word)}. In general, you should never be * statically calling this method for Word types. */ @SuppressWarnings("deprecation") @@ -1226,7 +1233,7 @@ private HostedWord(long rawValue) { this.rawValue = rawValue; } - protected static Word boxLong(long val) { + static Word boxLong(long val) { if (val >= SMALL_FROM && val <= SMALL_TO) { return smallCache[(int) val - SMALL_FROM]; } diff --git a/sdk/mx.sdk/suite.py b/sdk/mx.sdk/suite.py index 9b211fbb5684..47c3323c02fa 100644 --- a/sdk/mx.sdk/suite.py +++ b/sdk/mx.sdk/suite.py @@ -413,6 +413,19 @@ "workingSets" : "API,SDK", }, + "org.graalvm.word.test" : { + "subDir" : "src", + "sourceDirs" : ["src"], + "dependencies" : [ + "mx:JUNIT", + "org.graalvm.word" + ], + "javaCompliance" : "21+", + "workingSets" : "SDK", + "checkstyle" : "org.graalvm.word", + "graalCompilerSourceEdition": "ignore", + }, + "org.graalvm.nativeimage" : { "subDir" : "src", "sourceDirs" : ["src"], @@ -947,6 +960,7 @@ class UniversalDetector { "SDK_TEST" : { "subDir" : "src", "dependencies" : [ + "org.graalvm.word.test", "org.graalvm.collections.test", "org.graalvm.nativeimage.test", "org.graalvm.launcher.test", @@ -957,6 +971,7 @@ class UniversalDetector { "sdk:POLYGLOT", "sdk:NATIVEIMAGE", "sdk:COLLECTIONS", + "sdk:WORD", "sdk:LAUNCHER_COMMON" ], "maven" : False, diff --git a/sdk/src/org.graalvm.word.test/src/org/graalvm/word/test/WordTests.java b/sdk/src/org.graalvm.word.test/src/org/graalvm/word/test/WordTests.java new file mode 100644 index 000000000000..4c64e3424cd2 --- /dev/null +++ b/sdk/src/org.graalvm.word.test/src/org/graalvm/word/test/WordTests.java @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2024, 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 org.graalvm.word.test; + +import static org.graalvm.word.WordFactory.unsigned; +import static org.graalvm.word.WordFactory.signed; +import static org.graalvm.word.WordFactory.pointer; + +import org.graalvm.word.Pointer; +import org.graalvm.word.SignedWord; +import org.graalvm.word.UnsignedWord; +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; +import java.util.stream.LongStream; + +public class WordTests { + + static long[] words = { + Long.MIN_VALUE, + Long.MIN_VALUE + 1, + -1L, + 0L, + 1L, + Long.MAX_VALUE - 1, + Long.MAX_VALUE, + Integer.MAX_VALUE - 1L, + Integer.MAX_VALUE, + Integer.MAX_VALUE + 1L, + Integer.MIN_VALUE - 1L, + Integer.MIN_VALUE, + Integer.MIN_VALUE + 1L + }; + + static SignedWord signedWord(long val) { + return signed(val); + } + + static UnsignedWord unsignedWord(long val) { + return unsigned(val); + } + + static Pointer asPointer(long val) { + return pointer(val); + } + + static List signedWords = LongStream.of(words).mapToObj(WordTests::signedWord).toList(); + static List unsignedWords = LongStream.of(words).mapToObj(WordTests::unsignedWord).toList(); + static List pointers = LongStream.of(words).mapToObj(WordTests::asPointer).toList(); + + @Test + public void testSigned() { + for (var x : signedWords) { + Assert.assertEquals(x.not().rawValue(), ~x.rawValue()); + + for (var y : signedWords) { + Assert.assertEquals(x.equal(y), x == y); + Assert.assertEquals(x.notEqual(y), x != y); + + Assert.assertEquals(x.add(y).rawValue(), x.rawValue() + y.rawValue()); + Assert.assertEquals(x.subtract(y).rawValue(), x.rawValue() - y.rawValue()); + Assert.assertEquals(x.multiply(y).rawValue(), x.rawValue() * y.rawValue()); + + if (y.rawValue() != 0) { + Assert.assertEquals(x.signedDivide(y).rawValue(), x.rawValue() / y.rawValue()); + Assert.assertEquals(x.signedRemainder(y).rawValue(), x.rawValue() % y.rawValue()); + } + Assert.assertEquals(x.and(y).rawValue(), x.rawValue() & y.rawValue()); + Assert.assertEquals(x.or(y).rawValue(), x.rawValue() | y.rawValue()); + Assert.assertEquals(x.xor(y).rawValue(), x.rawValue() ^ y.rawValue()); + + Assert.assertEquals(x.equal(y), x == y); + Assert.assertEquals(x.notEqual(y), x != y); + + Assert.assertEquals(x.greaterThan(y), x.rawValue() > y.rawValue()); + Assert.assertEquals(x.greaterOrEqual(y), x.rawValue() >= y.rawValue()); + Assert.assertEquals(x.lessThan(y), x.rawValue() < y.rawValue()); + Assert.assertEquals(x.lessOrEqual(y), x.rawValue() <= y.rawValue()); + + Assert.assertEquals(x.shiftLeft((UnsignedWord) y).rawValue(), x.rawValue() << y.rawValue()); + Assert.assertEquals(x.signedShiftRight((UnsignedWord) y).rawValue(), x.rawValue() >> y.rawValue()); + } + } + } + + @Test + public void testUnsigned() { + for (var x : unsignedWords) { + Assert.assertEquals(x.not().rawValue(), ~x.rawValue()); + + for (var y : unsignedWords) { + Assert.assertEquals(x.equal(y), x == y); + Assert.assertEquals(x.notEqual(y), x != y); + + Assert.assertEquals(x.add(y).rawValue(), x.rawValue() + y.rawValue()); + Assert.assertEquals(x.subtract(y).rawValue(), x.rawValue() - y.rawValue()); + Assert.assertEquals(x.multiply(y).rawValue(), x.rawValue() * y.rawValue()); + + if (y.rawValue() != 0) { + Assert.assertEquals(x.unsignedDivide(y).rawValue(), Long.divideUnsigned(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.unsignedRemainder(y).rawValue(), Long.remainderUnsigned(x.rawValue(), y.rawValue())); + } + Assert.assertEquals(x.and(y).rawValue(), x.rawValue() & y.rawValue()); + Assert.assertEquals(x.or(y).rawValue(), x.rawValue() | y.rawValue()); + Assert.assertEquals(x.xor(y).rawValue(), x.rawValue() ^ y.rawValue()); + + Assert.assertEquals(x.equal(y), x == y); + Assert.assertEquals(x.notEqual(y), x != y); + + Assert.assertEquals(x.aboveThan(y), longAboveThan(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.aboveOrEqual(y), longAboveOrEqual(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.belowThan(y), longBelowThan(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.belowOrEqual(y), longBelowOrEqual(x.rawValue(), y.rawValue())); + + Assert.assertEquals(x.shiftLeft(y).rawValue(), x.rawValue() << y.rawValue()); + Assert.assertEquals(x.unsignedShiftRight(y).rawValue(), x.rawValue() >>> y.rawValue()); + } + } + } + + @Test + public void testPointer() { + for (var x : pointers) { + Assert.assertEquals(x.not().rawValue(), ~x.rawValue()); + Assert.assertEquals(x.isNull(), x.rawValue() == 0); + Assert.assertEquals(x.isNonNull(), x.rawValue() != 0); + + for (var y : pointers) { + Assert.assertEquals(x.equal(y), x == y); + Assert.assertEquals(x.notEqual(y), x != y); + + Assert.assertEquals(x.add(y).rawValue(), x.rawValue() + y.rawValue()); + Assert.assertEquals(x.subtract(y).rawValue(), x.rawValue() - y.rawValue()); + Assert.assertEquals(x.multiply(y).rawValue(), x.rawValue() * y.rawValue()); + + if (y.rawValue() != 0) { + Assert.assertEquals(x.unsignedDivide(y).rawValue(), Long.divideUnsigned(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.unsignedRemainder(y).rawValue(), Long.remainderUnsigned(x.rawValue(), y.rawValue())); + } + Assert.assertEquals(x.and(y).rawValue(), x.rawValue() & y.rawValue()); + Assert.assertEquals(x.or(y).rawValue(), x.rawValue() | y.rawValue()); + Assert.assertEquals(x.xor(y).rawValue(), x.rawValue() ^ y.rawValue()); + + Assert.assertEquals(x.equal(y), x == y); + Assert.assertEquals(x.notEqual(y), x != y); + + Assert.assertEquals(x.aboveThan(y), longAboveThan(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.aboveOrEqual(y), longAboveOrEqual(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.belowThan(y), longBelowThan(x.rawValue(), y.rawValue())); + Assert.assertEquals(x.belowOrEqual(y), longBelowOrEqual(x.rawValue(), y.rawValue())); + + Assert.assertEquals(x.shiftLeft(y).rawValue(), x.rawValue() << y.rawValue()); + Assert.assertEquals(x.unsignedShiftRight(y).rawValue(), x.rawValue() >>> y.rawValue()); + } + } + } + + @Test + public void testWords() { + for (long x : words) { + Assert.assertEquals(signed(x).not().rawValue(), ~x); + Assert.assertEquals(pointer(x).isNull(), x == 0); + Assert.assertEquals(pointer(x).isNonNull(), x != 0); + + for (long y : words) { + Assert.assertEquals(signed(x).equal(signed(y)), x == y); + Assert.assertEquals(signed(x).notEqual(signed(y)), x != y); + + Assert.assertEquals(signed(x).add(signed(y)).rawValue(), x + y); + Assert.assertEquals(signed(x).subtract(signed(y)).rawValue(), x - y); + Assert.assertEquals(signed(x).multiply(signed(y)).rawValue(), x * y); + + Assert.assertEquals(unsigned(x).add(unsigned(y)).rawValue(), x + y); + Assert.assertEquals(unsigned(x).subtract(unsigned(y)).rawValue(), x - y); + Assert.assertEquals(unsigned(x).multiply(unsigned(y)).rawValue(), x * y); + + if (y != 0) { + Assert.assertEquals(unsigned(x).unsignedDivide(unsigned(y)).rawValue(), Long.divideUnsigned(x, y)); + Assert.assertEquals(unsigned(x).unsignedRemainder(unsigned(y)).rawValue(), Long.remainderUnsigned(x, y)); + Assert.assertEquals(signed(x).signedDivide(signed(y)).rawValue(), x / y); + Assert.assertEquals(signed(x).signedRemainder(signed(y)).rawValue(), x % y); + } + Assert.assertEquals(signed(x).and(signed(y)).rawValue(), x & y); + Assert.assertEquals(signed(x).or(signed(y)).rawValue(), x | y); + Assert.assertEquals(signed(x).xor(signed(y)).rawValue(), x ^ y); + + Assert.assertEquals(unsigned(x).and(unsigned(y)).rawValue(), x & y); + Assert.assertEquals(unsigned(x).or(unsigned(y)).rawValue(), x | y); + Assert.assertEquals(unsigned(x).xor(unsigned(y)).rawValue(), x ^ y); + + Assert.assertEquals(signed(x).equal(signed(y)), x == y); + Assert.assertEquals(signed(x).notEqual(signed(y)), x != y); + Assert.assertEquals(unsigned(x).equal(unsigned(y)), x == y); + Assert.assertEquals(unsigned(x).notEqual(unsigned(y)), x != y); + + Assert.assertEquals(signed(x).greaterThan(signed(y)), x > y); + Assert.assertEquals(signed(x).greaterOrEqual(signed(y)), x >= y); + Assert.assertEquals(signed(x).lessThan(signed(y)), x < y); + Assert.assertEquals(signed(x).lessOrEqual(signed(y)), x <= y); + + Assert.assertEquals(unsigned(x).aboveThan(unsigned(y)), longAboveThan(x, y)); + Assert.assertEquals(unsigned(x).aboveOrEqual(unsigned(y)), longAboveOrEqual(x, y)); + Assert.assertEquals(unsigned(x).belowThan(unsigned(y)), longBelowThan(x, y)); + Assert.assertEquals(unsigned(x).belowOrEqual(unsigned(y)), longBelowOrEqual(x, y)); + + Assert.assertEquals(signed(x).shiftLeft(signed(y)).rawValue(), x << y); + Assert.assertEquals(signed(x).signedShiftRight(signed(y)).rawValue(), x >> y); + Assert.assertEquals(unsigned(x).shiftLeft(unsigned(y)).rawValue(), x << y); + Assert.assertEquals(unsigned(x).unsignedShiftRight(unsigned(y)).rawValue(), x >>> y); + } + } + } + + static boolean longAboveThan(long a, long b) { + return Long.compareUnsigned(a, b) > 0; + } + + static boolean longAboveOrEqual(long a, long b) { + return Long.compareUnsigned(a, b) >= 0; + } + + static boolean longBelowThan(long a, long b) { + return Long.compareUnsigned(a, b) < 0; + } + + static boolean longBelowOrEqual(long a, long b) { + return Long.compareUnsigned(a, b) <= 0; + } +} diff --git a/sdk/src/org.graalvm.word/src/org/graalvm/word/Word.java b/sdk/src/org.graalvm.word/src/org/graalvm/word/Word.java new file mode 100644 index 000000000000..48778892e26c --- /dev/null +++ b/sdk/src/org.graalvm.word/src/org/graalvm/word/Word.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024, 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 org.graalvm.word; + +/** + * Unifies the {@link SignedWord}, {@link UnsignedWord} and {@link Pointer} interfaces so that + * methods with the same signature but different return types (e.g. + * {@link UnsignedWord#shiftLeft(UnsignedWord)} and {@link SignedWord#shiftLeft(UnsignedWord)}) are + * overridden by a common method that merges the return types. This a requirement for creating a + * proxy type in {@link WordFactory#box(long)}. + */ +interface Word extends SignedWord, UnsignedWord, Pointer { + + Word add(int p1); + + Word shiftLeft(int p1); + + Word shiftLeft(UnsignedWord p1); + + Word multiply(int p1); + + Word or(int p1); + + Word and(int p1); + + Word not(); + + Word subtract(int p1); + + Word xor(int p1); +} diff --git a/sdk/src/org.graalvm.word/src/org/graalvm/word/WordFactory.java b/sdk/src/org.graalvm.word/src/org/graalvm/word/WordFactory.java index e4871b9c8510..a0772f091f00 100644 --- a/sdk/src/org.graalvm.word/src/org/graalvm/word/WordFactory.java +++ b/sdk/src/org.graalvm.word/src/org/graalvm/word/WordFactory.java @@ -40,6 +40,8 @@ */ package org.graalvm.word; +import static java.lang.Long.compareUnsigned; + import org.graalvm.word.impl.WordFactoryOpcode; import org.graalvm.word.impl.WordFactoryOperation; @@ -99,6 +101,10 @@ public static T unsigned(long val) { * Unsafe conversion from a Java long value to a {@link PointerBase pointer}. The parameter is * treated as an unsigned 64-bit value (in contrast to the semantics of a Java long). * + * In an execution environment where this method returns a boxed value (e.g. not in Native + * Image), the returned value will throw {@link UnsupportedOperationException} if any of the + * {@link Pointer} memory access operations are invoked on it. + * * @param val a 64 bit unsigned value * @return the value cast to PointerBase * @@ -152,46 +158,16 @@ public static T signed(int val) { } /** - * Unifies the {@link SignedWord}, {@link UnsignedWord} and {@link Pointer} interfaces so that - * methods with the same signature but different return types (e.g. - * {@link UnsignedWord#shiftLeft(UnsignedWord)} and {@link SignedWord#shiftLeft(UnsignedWord)}) - * are overridden by a common method that merges the return types. This a requirement for - * creating a proxy type in {@link WordFactory#box(long)}. - */ - interface Word extends SignedWord, UnsignedWord, Pointer { - - Word add(int p1); - - Word shiftLeft(int p1); - - Word shiftLeft(UnsignedWord p1); - - Word multiply(int p1); - - Word or(int p1); - - Word and(int p1); - - Word not(); - - Word subtract(int p1); - - Word xor(int p1); - } - - /** - * Creates a box for {@code val} that supports the most basic operations defined by {@link Word} - * needed for passing around word values and printing them. - * - * Clients of the Word API that need more functional boxes must implement them themselves. + * Creates a box for {@code val} that all word operations except memory access operations (see + * {@link #pointer(long)}). */ @SuppressWarnings("unchecked") - private static T box(long val) { + static T box(long val) { Class[] interfaces = {Word.class}; return (T) Proxy.newProxyInstance(WordFactory.class.getClassLoader(), interfaces, (proxy, method, args) -> { switch (method.getName()) { case "toString": { - return "WordImpl<" + val + ">"; + return Word.class.getName() + "<" + val + ">"; } case "equals": { if (args[0] instanceof WordBase) { @@ -200,14 +176,48 @@ private static T box(long val) { } return false; } - case "hashCode": { - return Long.hashCode(val); - } - case "rawValue": { - return val; - } + // @formatter:off + case "aboveOrEqual": return compareUnsigned(val, unbox(args[0])) >= 0; + case "aboveThan": return compareUnsigned(val, unbox(args[0])) > 0; + case "add": return box(val + unbox(args[0])); + case "and": return box(val & unbox(args[0])); + case "belowOrEqual": return compareUnsigned(val, unbox(args[0])) <= 0; + case "belowThan": return compareUnsigned(val, unbox(args[0])) < 0; + case "equal": return val == unbox(args[0]); + case "greaterOrEqual": return val >= unbox(args[0]); + case "greaterThan": return val > unbox(args[0]); + case "hashCode": return Long.hashCode(val); + case "isNonNull": return val != 0; + case "isNull": return val == 0; + case "lessOrEqual": return val <= unbox(args[0]); + case "lessThan": return val < unbox(args[0]); + case "multiply": return box(val * unbox(args[0])); + case "not": return box(~val); + case "notEqual": return val != unbox(args[0]); + case "or": return box(val | unbox(args[0])); + case "rawValue": return val; + case "shiftLeft": return box(val << unbox(args[0])); + case "signedDivide": return box(val / unbox(args[0])); + case "signedRemainder": return box(val % unbox(args[0])); + case "signedShiftRight": return box(val >> unbox(args[0])); + case "subtract": return box(val - unbox(args[0])); + case "unsignedDivide": return box(Long.divideUnsigned(val, unbox(args[0]))); + case "unsignedRemainder": return box(Long.remainderUnsigned(val, unbox(args[0]))); + case "unsignedShiftRight": return box(val >>> unbox(args[0])); + case "xor": return box(val ^ unbox(args[0])); + // @formatter:on } - throw new UnsupportedOperationException("operation " + method.getName() + " not supported"); + throw new UnsupportedOperationException("operation `" + method.getName() + "` not supported"); }); } + + static long unbox(Object arg) { + if (arg instanceof WordBase) { + return ((WordBase) arg).rawValue(); + } + if (arg instanceof Number) { + return ((Number) arg).longValue(); + } + throw new IllegalArgumentException(); + } }