Skip to content

[GR-61283] API for restoring ihashcode in continuations package. #11160

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions espresso/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -119,6 +119,7 @@ public final class EspressoLanguage extends TruffleLanguage<EspressoContext> 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<EspressoContext> 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<StaticObjectFactory> getArrayShape() {
assert fullyInitialized : "Array shape accessed before language is fully initialized";
return arrayShape;
@@ -489,7 +501,11 @@ public StaticShape<StaticObjectFactory> getArrayShape() {
@TruffleBoundary
private StaticShape<StaticObjectFactory> 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;
}
Original file line number Diff line number Diff line change
@@ -928,6 +928,8 @@ public static class Names {
public static final Symbol<Name> entrySet = SYMBOLS.putName("entrySet");
public static final Symbol<Name> hasNext = SYMBOLS.putName("hasNext");
public static final Symbol<Name> toArray = SYMBOLS.putName("toArray");
// j.l.Object
public static final Symbol<Name> HIDDEN_SYSTEM_IHASHCODE = SYMBOLS.putName("0HIDDEN_SYSTEM_IHASHCODE");
// MemberName
public static final Symbol<Name> HIDDEN_VMINDEX = SYMBOLS.putName("0HIDDEN_VMINDEX");
public static final Symbol<Name> HIDDEN_VMTARGET = SYMBOLS.putName("0HIDDEN_VMTARGET");
Original file line number Diff line number Diff line change
@@ -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<Klass, Method, Field> {
@@ -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
Original file line number Diff line number Diff line change
@@ -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<Symbol<Type>, 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[]{
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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),
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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()) //
Original file line number Diff line number Diff line change
@@ -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));
Loading