diff --git a/espresso/CHANGELOG.md b/espresso/CHANGELOG.md index c0699a192986..a7b33fe60166 100644 --- a/espresso/CHANGELOG.md +++ b/espresso/CHANGELOG.md @@ -4,6 +4,7 @@ ### User-visible changes * Added experimental support for JVMCI. It can be enabled with the `java.EnableJVMCI` option. * Added experimentation support for `-javaagent`. It can also be enabled from the polyglot API with `java.JavaAgent.$i` option set to `/path/to/jar=agent-options` where `$i` starts at 0 and increments by 1 for each extra java agent. +* Added the `org.graalvm.continuations.IdentityHashCodes` class, providing utilities for restoring identity hashcodes. This may be used for more properly deserializing continuations. ## Version 24.2.0 ### User-visible changes diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/EspressoLanguage.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/EspressoLanguage.java index a6f272e29ae4..f3a7a2609a8d 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/EspressoLanguage.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/EspressoLanguage.java @@ -119,6 +119,7 @@ public final class EspressoLanguage extends TruffleLanguage imp @CompilationFinal private SignatureSymbols signatureSymbols; private final StaticProperty arrayProperty = new DefaultStaticProperty("array"); + private final StaticProperty arrayHashCodeProperty = new DefaultStaticProperty("ihashcode"); // This field should be final, but creating a shape requires a fully-initialized instance of // TruffleLanguage. @CompilationFinal // @@ -144,6 +145,7 @@ public final class EspressoLanguage extends TruffleLanguage imp @CompilationFinal private boolean eagerFrameAnalysis; @CompilationFinal private boolean internalJvmciEnabled; @CompilationFinal private boolean useEspressoLibs; + @CompilationFinal private boolean continuum; // endregion Options // region Allocation @@ -242,6 +244,7 @@ private void initializeOptions(final TruffleLanguage.Env env) { previewEnabled = env.getOptions().get(EspressoOptions.EnablePreview); whiteBoxEnabled = env.getOptions().get(EspressoOptions.WhiteBoxAPI); internalJvmciEnabled = env.getOptions().get(EspressoOptions.EnableJVMCI); + continuum = env.getOptions().get(EspressoOptions.Continuum); EspressoOptions.GuestFieldOffsetStrategyEnum strategy = env.getOptions().get(EspressoOptions.GuestFieldOffsetStrategy); guestFieldOffsetStrategy = switch (strategy) { @@ -339,6 +342,7 @@ protected boolean areOptionsCompatible(OptionValues oldOptions, OptionValues new isOptionCompatible(newOptions, oldOptions, EspressoOptions.EnablePreview) && isOptionCompatible(newOptions, oldOptions, EspressoOptions.WhiteBoxAPI) && isOptionCompatible(newOptions, oldOptions, EspressoOptions.EnableJVMCI) && + isOptionCompatible(newOptions, oldOptions, EspressoOptions.Continuum) && isOptionCompatible(newOptions, oldOptions, EspressoOptions.GuestFieldOffsetStrategy) && isOptionCompatible(newOptions, oldOptions, EspressoOptions.UseEspressoLibs); } @@ -481,6 +485,14 @@ public StaticProperty getArrayProperty() { return arrayProperty; } + public StaticProperty getArrayHashCodeProperty() { + if (!continuum) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw EspressoError.shouldNotReachHere("Accessing array hash code property without continuum set up."); + } + return arrayHashCodeProperty; + } + public StaticShape getArrayShape() { assert fullyInitialized : "Array shape accessed before language is fully initialized"; return arrayShape; @@ -489,7 +501,11 @@ public StaticShape getArrayShape() { @TruffleBoundary private StaticShape createArrayShape() { assert arrayShape == null; - return StaticShape.newBuilder(this).property(arrayProperty, Object.class, true).build(StaticObject.class, StaticObjectFactory.class); + StaticShape.Builder builder = StaticShape.newBuilder(this).property(arrayProperty, Object.class, true); + if (continuum) { + builder.property(arrayHashCodeProperty, int.class, false); + } + return builder.build(StaticObject.class, StaticObjectFactory.class); } public StaticProperty getForeignProperty() { @@ -570,6 +586,10 @@ public boolean useEspressoLibs() { return useEspressoLibs; } + public boolean isContinuumEnabled() { + return continuum; + } + public EspressoLanguageCache getLanguageCache() { return languageCache; } diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/descriptors/EspressoSymbols.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/descriptors/EspressoSymbols.java index b0da0d90786b..593cba2df4cf 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/descriptors/EspressoSymbols.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/descriptors/EspressoSymbols.java @@ -928,6 +928,8 @@ public static class Names { public static final Symbol entrySet = SYMBOLS.putName("entrySet"); public static final Symbol hasNext = SYMBOLS.putName("hasNext"); public static final Symbol toArray = SYMBOLS.putName("toArray"); + // j.l.Object + public static final Symbol HIDDEN_SYSTEM_IHASHCODE = SYMBOLS.putName("0HIDDEN_SYSTEM_IHASHCODE"); // MemberName public static final Symbol HIDDEN_VMINDEX = SYMBOLS.putName("0HIDDEN_VMINDEX"); public static final Symbol HIDDEN_VMTARGET = SYMBOLS.putName("0HIDDEN_VMTARGET"); diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/Klass.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/Klass.java index 7ebd57d3e127..e7a80b5aa820 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/Klass.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/Klass.java @@ -83,6 +83,7 @@ import com.oracle.truffle.espresso.meta.InteropKlassesDispatch; import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.meta.MetaUtil; +import com.oracle.truffle.espresso.nodes.interop.IHashCodeNode; import com.oracle.truffle.espresso.nodes.interop.InteropUnwrapNode; import com.oracle.truffle.espresso.nodes.interop.InteropUnwrapNodeGen; import com.oracle.truffle.espresso.nodes.interop.LookupDeclaredMethod; @@ -106,7 +107,6 @@ import com.oracle.truffle.espresso.shared.meta.TypeAccess; import com.oracle.truffle.espresso.substitutions.JavaType; import com.oracle.truffle.espresso.vm.InterpreterToVM; -import com.oracle.truffle.espresso.vm.VM; @ExportLibrary(InteropLibrary.class) public abstract class Klass extends ContextAccessImpl implements KlassRef, TruffleObject, EspressoType, TypeAccess { @@ -607,11 +607,11 @@ static TriState doOther(@SuppressWarnings("unused") Klass receiver, @SuppressWar } @ExportMessage - int identityHashCode() { + int identityHashCode(@Cached IHashCodeNode iHashCodeNode) { // In unit tests, Truffle performs additional sanity checks, this assert causes stack // overflow. // assert InteropLibrary.getUncached().hasIdentity(this); - return VM.JVM_IHashCode(mirror(), null /*- path where language is needed is never reached through here. */); + return iHashCodeNode.execute(mirror()); } // endregion ### Identity/hashCode diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LinkedKlassFieldLayout.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LinkedKlassFieldLayout.java index 6cb153a2302a..b088488985e5 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LinkedKlassFieldLayout.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/impl/LinkedKlassFieldLayout.java @@ -24,6 +24,7 @@ import static com.oracle.truffle.espresso.classfile.Constants.ACC_FINAL; import static com.oracle.truffle.espresso.classfile.Constants.ACC_HIDDEN; +import static com.oracle.truffle.espresso.classfile.Constants.ACC_VOLATILE; import static java.util.Map.entry; import java.util.HashSet; @@ -178,6 +179,9 @@ private static class HiddenField { private static final int NO_ADDITIONAL_FLAGS = 0; private static final HiddenField[] EMPTY = new HiddenField[0]; private static final Map, HiddenField[]> REGISTRY = Map.ofEntries( + entry(Types.java_lang_Object, new HiddenField[]{ + new HiddenField(Names.HIDDEN_SYSTEM_IHASHCODE, Types._int, EspressoLanguage::isContinuumEnabled, ACC_VOLATILE), + }), entry(Types.java_lang_invoke_MemberName, new HiddenField[]{ new HiddenField(Names.HIDDEN_VMTARGET), new HiddenField(Names.HIDDEN_VMINDEX) @@ -197,7 +201,6 @@ private static class HiddenField { // All references (including strong) get an extra hidden field, this // simplifies the code for weak/soft/phantom/final references. entry(Types.java_lang_ref_Reference, new HiddenField[]{ - new HiddenField(Names.HIDDEN_HOST_REFERENCE) }), entry(Types.java_lang_Throwable, new HiddenField[]{ diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/meta/Meta.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/meta/Meta.java index 95d7c8d5c378..dec53fefabe9 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/meta/Meta.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/meta/Meta.java @@ -99,6 +99,7 @@ public Meta(EspressoContext context) { // Object and Class (+ Class fields) must be initialized before all other classes in order // to eagerly create the guest Class instances. java_lang_Object = knownKlass(Types.java_lang_Object); + HIDDEN_SYSTEM_IHASHCODE = context.getLanguage().isContinuumEnabled() ? java_lang_Object.requireHiddenField(Names.HIDDEN_SYSTEM_IHASHCODE) : null; // Cloneable must be loaded before Serializable. java_lang_Cloneable = knownKlass(Types.java_lang_Cloneable); java_lang_Class = knownKlass(Types.java_lang_Class); @@ -1342,7 +1343,7 @@ public void postSystemInit() { } // Continuations - boolean continuumSupport = getContext().getEspressoEnv().Continuum; + boolean continuumSupport = getLanguage().isContinuumEnabled(); this.continuum = continuumSupport ? new ContinuumSupport() : null; } @@ -1375,6 +1376,11 @@ private DiffVersionLoadHelper diff() { public final ObjectKlass java_lang_Object; public final ArrayKlass java_lang_Object_array; + /* + * Though only used when Continuum is enabled, the hashcode is used during VM initialization, so + * it cannot be put in the ContinuumSupport object. + */ + public final Field HIDDEN_SYSTEM_IHASHCODE; public final ObjectKlass java_lang_String; public final ArrayKlass java_lang_String_array; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/IHashCodeNode.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/IHashCodeNode.java new file mode 100644 index 000000000000..e0276eda9758 --- /dev/null +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/IHashCodeNode.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2025, 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. + * + * 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 com.oracle.truffle.espresso.nodes.interop; + +import com.oracle.truffle.api.dsl.Bind; +import com.oracle.truffle.api.dsl.GenerateUncached; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.espresso.EspressoLanguage; +import com.oracle.truffle.espresso.nodes.EspressoNode; +import com.oracle.truffle.espresso.runtime.EspressoContext; +import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; +import com.oracle.truffle.espresso.vm.VM; + +@GenerateUncached +public abstract class IHashCodeNode extends EspressoNode { + public abstract int execute(StaticObject obj); + + @Specialization + public static int doCached(StaticObject obj, + @Bind("getLanguage()") EspressoLanguage lang, + @Bind("getContext()") EspressoContext ctx) { + assert !obj.isForeignObject(); + return VM.JVM_IHashCode(obj, ctx.getMeta(), lang); + } +} diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/EspressoEnv.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/EspressoEnv.java index fd8797b58e0b..b22fb0b7f1f1 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/EspressoEnv.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/EspressoEnv.java @@ -92,7 +92,6 @@ public final class EspressoEnv { public final boolean SoftExit; public final boolean AllowHostExit; public final boolean Polyglot; - public final boolean Continuum; public final boolean BuiltInPolyglotCollections; public final boolean HotSwapAPI; public final boolean UseBindingsLoader; @@ -169,7 +168,6 @@ public EspressoEnv(EspressoContext context, TruffleLanguage.Env env) { this.multiThreadingDisabled = multiThreadingDisabledReason; this.NativeAccessAllowed = env.isNativeAccessAllowed(); this.Polyglot = env.getOptions().get(EspressoOptions.Polyglot); - this.Continuum = env.getOptions().get(EspressoOptions.Continuum); this.HotSwapAPI = env.getOptions().get(EspressoOptions.HotSwapAPI); this.BuiltInPolyglotCollections = env.getOptions().get(EspressoOptions.BuiltInPolyglotCollections); this.polyglotTypeMappings = new PolyglotTypeMappings(env.getOptions().get(EspressoOptions.PolyglotInterfaceMappings), env.getOptions().get(EspressoOptions.PolyglotTypeConverters), diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/BaseInterop.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/BaseInterop.java index 3b3f5902bc7a..7d0bca7454a8 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/BaseInterop.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/BaseInterop.java @@ -41,10 +41,10 @@ import com.oracle.truffle.espresso.impl.Klass; import com.oracle.truffle.espresso.meta.EspressoError; import com.oracle.truffle.espresso.meta.Meta; +import com.oracle.truffle.espresso.nodes.interop.IHashCodeNode; import com.oracle.truffle.espresso.runtime.dispatch.messages.GenerateInteropNodes; import com.oracle.truffle.espresso.runtime.dispatch.messages.Shareable; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; -import com.oracle.truffle.espresso.vm.VM; /** * BaseInterop (isNull, is/asString, meta-instance, identity, exceptions, toDisplayString) Support @@ -209,10 +209,14 @@ public static TriState isIdenticalOrUndefined(StaticObject receiver, Object othe } @ExportMessage - public static int identityHashCode(StaticObject object) { + public static int identityHashCode(StaticObject object, + @Cached IHashCodeNode iHashCodeNode) { object.checkNotForeign(); + if (StaticObject.isNull(object)) { + return 0; + } // Working with espresso objects here, guaranteed to have identity. - return VM.JVM_IHashCode(object, null /*- path where language is needed is never reached through here. */); + return iHashCodeNode.execute(object); } // endregion ### Identity/hashCode diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/ModuleExtension.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/ModuleExtension.java index 0eeac49dcc88..2845d79d52f5 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/ModuleExtension.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/ModuleExtension.java @@ -37,7 +37,7 @@ */ public final class ModuleExtension { private static final ModuleExtension[] ESPRESSO_EXTENSION_MODULES = { - new Builder("org.graalvm.continuations", "continuations.jar", (context) -> context.getEspressoEnv().Continuum).build(), + new Builder("org.graalvm.continuations", "continuations.jar", (context) -> context.getLanguage().isContinuumEnabled()).build(), new Builder("espresso.hotswap", "hotswap.jar", (context) -> context.getEspressoEnv().JDWPOptions != null).build(), new Builder("espresso.polyglot", "espresso-polyglot.jar", (context) -> context.getEspressoEnv().Polyglot).build(), new Builder("jdk.graal.compiler.espresso", "espresso-graal.jar", (context) -> context.getLanguage().isInternalJVMCIEnabled()) // diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/continuations/Target_java_lang_invoke_LambdaMetafactory.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/continuations/Target_java_lang_invoke_LambdaMetafactory.java index 460cb09fe142..8b454b9610ce 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/continuations/Target_java_lang_invoke_LambdaMetafactory.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/continuations/Target_java_lang_invoke_LambdaMetafactory.java @@ -32,6 +32,7 @@ import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.nodes.DirectCallNode; +import com.oracle.truffle.espresso.EspressoLanguage; import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.runtime.EspressoContext; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; @@ -71,12 +72,12 @@ StaticObject doCached( @Bind("getMeta()") Meta meta, @Cached("create(meta.java_lang_invoke_LambdaMetafactory_altMetafactory.getCallTargetNoSubstitution())") DirectCallNode altMetafactory, @Cached("create(meta.java_lang_invoke_LambdaMetafactory_metafactory.getCallTargetNoSubstitution())") DirectCallNode original, - @Bind("getContext()") EspressoContext context) { - if (context.getEspressoEnv().Continuum) { + @Bind("getLanguage()") EspressoLanguage lang) { + if (lang.isContinuumEnabled()) { // altMetafactory has a curious calling convention, apparently designed for // extensibility. - StaticObject extraArgsRef = context.getAllocator().createNewReferenceArray(meta.java_lang_Object, 4); - StaticObject[] extraArgs = extraArgsRef.unwrap(context.getLanguage()); + StaticObject extraArgsRef = lang.getAllocator().createNewReferenceArray(meta.java_lang_Object, 4); + StaticObject[] extraArgs = extraArgsRef.unwrap(lang); extraArgs[0] = interfaceMethodType; extraArgs[1] = implementation; extraArgs[2] = dynamicMethodType; @@ -106,7 +107,7 @@ StaticObject doCached( @Bind("getMeta()") Meta meta, @Cached("create(meta.java_lang_invoke_LambdaMetafactory_altMetafactory.getCallTargetNoSubstitution())") DirectCallNode original, @Bind("getContext()") EspressoContext context) { - if (context.getEspressoEnv().Continuum) { + if (context.getLanguage().isContinuumEnabled()) { StaticObject[] extraArgs = args.unwrap(context.getLanguage()); extraArgs[3] = meta.boxInteger(meta.unboxInteger(extraArgs[3]) | LambdaMetafactory.FLAG_SERIALIZABLE); return (StaticObject) original.call(caller, interfaceMethodName, factoryType, StaticObject.wrap(extraArgs, meta)); diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/continuations/Target_org_graalvm_continuations_IdentityHashCodes.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/continuations/Target_org_graalvm_continuations_IdentityHashCodes.java new file mode 100644 index 000000000000..d8ca90c0ca39 --- /dev/null +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/continuations/Target_org_graalvm_continuations_IdentityHashCodes.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2025, 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. + * + * 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 com.oracle.truffle.espresso.substitutions.continuations; + +import com.oracle.truffle.espresso.EspressoLanguage; +import com.oracle.truffle.espresso.meta.EspressoError; +import com.oracle.truffle.espresso.meta.Meta; +import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; +import com.oracle.truffle.espresso.substitutions.EspressoSubstitutions; +import com.oracle.truffle.espresso.substitutions.Inject; +import com.oracle.truffle.espresso.substitutions.JavaType; +import com.oracle.truffle.espresso.substitutions.Substitution; + +@EspressoSubstitutions +public final class Target_org_graalvm_continuations_IdentityHashCodes { + public static int getIHashCode(@JavaType(Object.class) StaticObject o, Meta meta, EspressoLanguage lang) { + assert lang.isContinuumEnabled(); + if (StaticObject.isNull(o)) { + return 0; + } + int hashcode = getHashCode(o, meta, lang); + if (hashcode > 0) { + return hashcode; + } + hashcode = System.identityHashCode(o); + assert hashcode > 0; + // Atomic update, will return the actual value if it is already set. + return setHashCode(o, hashcode, meta, lang); + } + + @Substitution + public static boolean isInitialized0(@JavaType(Object.class) StaticObject o, + @Inject Meta meta, + @Inject EspressoLanguage lang) { + if (!lang.isContinuumEnabled()) { + throw meta.throwExceptionWithMessage(meta.java_lang_UnsupportedOperationException, "Continuations was not enabled."); + } + if (StaticObject.isNull(o)) { + throw meta.throwNullPointerException(); + } + assert meta.continuum != null; + return getHashCode(o, meta, lang) > 0; + } + + @Substitution + public static boolean setIHashcode0(@JavaType(Object.class) StaticObject o, int hashcode, + @Inject Meta meta, + @Inject EspressoLanguage lang) { + if (!meta.getLanguage().isContinuumEnabled()) { + throw meta.throwExceptionWithMessage(meta.java_lang_UnsupportedOperationException, "Continuations was not enabled."); + } + if (StaticObject.isNull(o)) { + throw meta.throwNullPointerException(); + } + if (hashcode <= 0) { + throw meta.throwExceptionWithMessage(meta.java_lang_IllegalArgumentException, EspressoError.cat("Setting hashcode <= 0: ", hashcode)); + } + assert meta.continuum != null; + return setHashCode(o, hashcode, meta, lang) == hashcode; + } + + private static int setHashCode(StaticObject o, int hashcode, Meta meta, EspressoLanguage language) { + assert !StaticObject.isNull(o) && hashcode > 0; + if (o.isArray()) { + language.getArrayHashCodeProperty().compareAndExchangeInt(o, 0, hashcode); + } else { + meta.HIDDEN_SYSTEM_IHASHCODE.compareAndExchangeInt(o, 0, hashcode); + } + return getHashCode(o, meta, language); + } + + private static int getHashCode(StaticObject o, Meta meta, EspressoLanguage language) { + return o.isArray() + ? language.getArrayHashCodeProperty().getInt(o) + : meta.HIDDEN_SYSTEM_IHASHCODE.getInt(o); + } +} diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/standard/Target_java_lang_Object.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/standard/Target_java_lang_Object.java index d6c9509ba01c..d2ba872c255f 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/standard/Target_java_lang_Object.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/standard/Target_java_lang_Object.java @@ -52,8 +52,8 @@ @EspressoSubstitutions public final class Target_java_lang_Object { @Substitution(hasReceiver = true, flags = {IsTrivial}) - public static int hashCode(@JavaType(Object.class) StaticObject self, @Inject EspressoLanguage lang) { - return VM.JVM_IHashCode(self, lang); + public static int hashCode(@JavaType(Object.class) StaticObject self, @Inject Meta meta, @Inject EspressoLanguage lang) { + return VM.JVM_IHashCode(self, meta, lang); } @Substitution(hasReceiver = true, flags = {IsTrivial}) diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/standard/Target_java_lang_System.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/standard/Target_java_lang_System.java index 614e83a8af6e..8435a715efc0 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/standard/Target_java_lang_System.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/standard/Target_java_lang_System.java @@ -79,9 +79,9 @@ public final class Target_java_lang_System { // endregion Profile values @Substitution(flags = {IsTrivial}) - public static int identityHashCode(@JavaType(Object.class) StaticObject self, @Inject EspressoLanguage lang) { + public static int identityHashCode(@JavaType(Object.class) StaticObject self, @Inject Meta meta, @Inject EspressoLanguage lang) { SYSTEM_IDENTITY_HASH_CODE_COUNT.inc(); - return VM.JVM_IHashCode(self, lang); + return VM.JVM_IHashCode(self, meta, lang); } @ReportPolymorphism diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/VM.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/VM.java index 853bfab3ac72..fde576b0abf3 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/VM.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/vm/VM.java @@ -154,6 +154,7 @@ import com.oracle.truffle.espresso.substitutions.JavaType; import com.oracle.truffle.espresso.substitutions.ModuleExtension; import com.oracle.truffle.espresso.substitutions.SubstitutionProfiler; +import com.oracle.truffle.espresso.substitutions.continuations.Target_org_graalvm_continuations_IdentityHashCodes; import com.oracle.truffle.espresso.substitutions.standard.Target_java_lang_System; import com.oracle.truffle.espresso.substitutions.standard.Target_java_lang_Thread; import com.oracle.truffle.espresso.substitutions.standard.Target_java_lang_ref_Reference; @@ -486,13 +487,14 @@ public static long JVM_NanoTime(@SuppressWarnings("unused") @JavaType(Class/* + * This may be used during continuation deserialization to restore the original hashcode of objects + * that have been recorded. + */ +public final class IdentityHashCodes { + /** + * Whether the identity hashcode of the given object is initialized. + *

+ * This can happen if: + *

    + *
  • {@code o.hashCode()} has been called and resolved to {@link Object#hashCode()}.
  • + *
  • {@link System#identityHashCode(Object) System.identityHashCode(o)} has been called.
  • + *
  • {@link #set(Object, int) IdentityHashCodes.set(o, _)} has been called.
  • + *
+ * + * @throws UnsupportedOperationException If this VM does not support continuations. + * @throws NullPointerException if {@code o} is {@code null}. + */ + public static boolean has(Object o) { + checkIsSupported(); + return isInitialized0(Objects.requireNonNull(o)); + } + + /** + * Attempts to set the identity hashcode of the given object, returning {@code true} if it + * succeeds, and {@code false} if the identity hashcode of the object was already initialized. + * + * @throws UnsupportedOperationException If this VM does not support continuations. + * @throws NullPointerException if {@code o} is {@code null}. + * @throws IllegalArgumentException if {@code hashcode <= 0}. + */ + public static boolean trySet(Object o, int hashcode) { + checkIsSupported(); + return setIHashcode0(o, hashcode); + } + + /** + * Sets the identity hashcode of the given object if it is not already initialized, and throws + * {@link IllegalStateException} otherwise. + * + * @throws UnsupportedOperationException If this VM does not support continuations. + * @throws NullPointerException if {@code o} is {@code null}. + * @throws IllegalArgumentException if {@code hashcode <= 0}. + * @throws IllegalStateException if the identity hashcode of the given object is already + * initialized. + */ + public static void set(Object o, int hashcode) { + if (!trySet(o, hashcode)) { + throw new IllegalStateException("Setting ihashcode of an object whose ihashcode is already initialized."); + } + } + + private static void checkIsSupported() { + if (!Continuation.isSupported()) { + throw new UnsupportedOperationException("This VM does not support identity hashcode preservation."); + } + } + + private static native boolean isInitialized0(Object o); + + private static native boolean setIHashcode0(Object o, int hashcode); + + private IdentityHashCodes() { + } +}