8280473: CI: Support unresolved JVM_CONSTANT_Dynamic constant pool entries

Reviewed-by: dlong, redestad, neliasso
This commit is contained in:
Vladimir Ivanov 2022-02-14 18:46:46 +00:00
parent f07b816523
commit 88fc3bfdff
12 changed files with 418 additions and 103 deletions

@ -34,6 +34,7 @@
#include "ci/ciMemberName.hpp"
#include "ci/ciSymbols.hpp"
#include "ci/ciUtilities.inline.hpp"
#include "classfile/javaClasses.hpp"
#include "compiler/compilationPolicy.hpp"
#include "compiler/compileBroker.hpp"
#include "compiler/compilerEvent.hpp"
@ -916,45 +917,39 @@ void GraphBuilder::ScopeData::incr_num_returns() {
void GraphBuilder::load_constant() {
ciConstant con = stream()->get_constant();
if (con.basic_type() == T_ILLEGAL) {
// FIXME: an unresolved Dynamic constant can get here,
// and that should not terminate the whole compilation.
BAILOUT("could not resolve a constant");
} else {
if (con.is_valid()) {
ValueType* t = illegalType;
ValueStack* patch_state = NULL;
switch (con.basic_type()) {
case T_BOOLEAN: t = new IntConstant (con.as_boolean()); break;
case T_BYTE : t = new IntConstant (con.as_byte ()); break;
case T_CHAR : t = new IntConstant (con.as_char ()); break;
case T_SHORT : t = new IntConstant (con.as_short ()); break;
case T_INT : t = new IntConstant (con.as_int ()); break;
case T_LONG : t = new LongConstant (con.as_long ()); break;
case T_FLOAT : t = new FloatConstant (con.as_float ()); break;
case T_DOUBLE : t = new DoubleConstant (con.as_double ()); break;
case T_ARRAY : t = new ArrayConstant (con.as_object ()->as_array ()); break;
case T_OBJECT :
{
case T_BOOLEAN: t = new IntConstant (con.as_boolean()); break;
case T_BYTE : t = new IntConstant (con.as_byte ()); break;
case T_CHAR : t = new IntConstant (con.as_char ()); break;
case T_SHORT : t = new IntConstant (con.as_short ()); break;
case T_INT : t = new IntConstant (con.as_int ()); break;
case T_LONG : t = new LongConstant (con.as_long ()); break;
case T_FLOAT : t = new FloatConstant (con.as_float ()); break;
case T_DOUBLE : t = new DoubleConstant(con.as_double ()); break;
case T_ARRAY : // fall-through
case T_OBJECT : {
ciObject* obj = con.as_object();
if (!obj->is_loaded()
|| (PatchALot && obj->klass() != ciEnv::current()->String_klass())) {
// A Class, MethodType, MethodHandle, or String.
// Unloaded condy nodes show up as T_ILLEGAL, above.
if (!obj->is_loaded() || (PatchALot && (obj->is_null_object() || obj->klass() != ciEnv::current()->String_klass()))) {
// A Class, MethodType, MethodHandle, Dynamic, or String.
patch_state = copy_state_before();
t = new ObjectConstant(obj);
} else {
// Might be a Class, MethodType, MethodHandle, or Dynamic constant
// result, which might turn out to be an array.
if (obj->is_null_object())
if (obj->is_null_object()) {
t = objectNull;
else if (obj->is_array())
} else if (obj->is_array()) {
t = new ArrayConstant(obj->as_array());
else
} else {
t = new InstanceConstant(obj->as_instance());
}
}
break;
}
default : ShouldNotReachHere();
}
default: ShouldNotReachHere();
}
Value x;
if (patch_state != NULL) {
@ -962,7 +957,27 @@ void GraphBuilder::load_constant() {
} else {
x = new Constant(t);
}
// Unbox the value at runtime, if needed.
// ConstantDynamic entry can be of a primitive type, but it is cached in boxed form.
if (patch_state != NULL) {
int index = stream()->get_constant_pool_index();
BasicType type = stream()->get_basic_type_for_constant_at(index);
if (is_java_primitive(type)) {
ciInstanceKlass* box_klass = ciEnv::current()->get_box_klass_for_primitive_type(type);
assert(box_klass->is_loaded(), "sanity");
int offset = java_lang_boxing_object::value_offset(type);
ciField* value_field = box_klass->get_field_by_offset(offset, false /*is_static*/);
x = new LoadField(append(x), offset, value_field, false /*is_static*/, patch_state, false /*needs_patching*/);
t = as_ValueType(type);
} else {
assert(is_reference_type(type), "not a reference: %s", type2name(type));
}
}
push(t, append(x));
} else {
BAILOUT("could not resolve a constant");
}
}

@ -262,7 +262,7 @@ void InstructionPrinter::print_inline_level(BlockBegin* block) {
void InstructionPrinter::print_unsafe_op(UnsafeOp* op, const char* name) {
output()->print("%s", name);
output()->print("%s(", name);
print_value(op->object());
output()->print(", ");
print_value(op->offset());

@ -76,6 +76,7 @@ void LIR_Assembler::patching_epilog(PatchingStub* patch, LIR_PatchCode patch_cod
case Bytecodes::_getstatic:
case Bytecodes::_ldc:
case Bytecodes::_ldc_w:
case Bytecodes::_ldc2_w:
break;
default:
ShouldNotReachHere();

@ -1009,6 +1009,7 @@ JRT_ENTRY(void, Runtime1::patch_code(JavaThread* current, Runtime1::StubID stub_
break;
case Bytecodes::_ldc:
case Bytecodes::_ldc_w:
case Bytecodes::_ldc2_w:
{
Bytecode_loadconstant cc(caller_method, bci);
oop m = cc.resolve_constant(CHECK);
@ -1153,7 +1154,6 @@ JRT_ENTRY(void, Runtime1::patch_code(JavaThread* current, Runtime1::StubID stub_
assert(load_klass != NULL, "klass not set");
n_copy->set_data((intx) (load_klass));
} else {
assert(mirror() != NULL, "klass not set");
// Don't need a G1 pre-barrier here since we assert above that data isn't an oop.
n_copy->set_data(cast_from_oop<intx>(mirror()));
}

@ -392,6 +392,23 @@ ciInstance* ciEnv::get_or_create_exception(jobject& handle, Symbol* name) {
return obj == NULL? NULL: get_object(obj)->as_instance();
}
ciInstanceKlass* ciEnv::get_box_klass_for_primitive_type(BasicType type) {
switch (type) {
case T_BOOLEAN: return Boolean_klass();
case T_BYTE : return Byte_klass();
case T_CHAR : return Character_klass();
case T_SHORT : return Short_klass();
case T_INT : return Integer_klass();
case T_LONG : return Long_klass();
case T_FLOAT : return Float_klass();
case T_DOUBLE : return Double_klass();
default:
assert(false, "not a primitive: %s", type2name(type));
return NULL;
}
}
ciInstance* ciEnv::ArrayIndexOutOfBoundsException_instance() {
if (_ArrayIndexOutOfBoundsException_instance == NULL) {
_ArrayIndexOutOfBoundsException_instance
@ -651,55 +668,76 @@ ciKlass* ciEnv::get_klass_by_index(const constantPoolHandle& cpool,
}
// ------------------------------------------------------------------
// ciEnv::get_constant_by_index_impl
// ciEnv::unbox_primitive_value
//
// Implementation of get_constant_by_index().
ciConstant ciEnv::get_constant_by_index_impl(const constantPoolHandle& cpool,
int pool_index, int cache_index,
ciInstanceKlass* accessor) {
bool ignore_will_link;
int index = pool_index;
if (cache_index >= 0) {
assert(index < 0, "only one kind of index at a time");
index = cpool->object_to_cp_index(cache_index);
oop obj = cpool->resolved_references()->obj_at(cache_index);
if (obj != NULL) {
if (obj == Universe::the_null_sentinel()) {
return ciConstant(T_OBJECT, get_object(NULL));
}
BasicType bt = T_OBJECT;
if (cpool->tag_at(index).is_dynamic_constant()) {
bt = Signature::basic_type(cpool->uncached_signature_ref_at(index));
}
if (!is_reference_type(bt)) {
// we have to unbox the primitive value
if (!is_java_primitive(bt)) {
return ciConstant();
}
jvalue value;
BasicType bt2 = java_lang_boxing_object::get_value(obj, &value);
assert(bt2 == bt, "");
switch (bt2) {
case T_DOUBLE: return ciConstant(value.d);
case T_FLOAT: return ciConstant(value.f);
case T_LONG: return ciConstant(value.j);
case T_INT: return ciConstant(bt2, value.i);
case T_SHORT: return ciConstant(bt2, value.s);
case T_BYTE: return ciConstant(bt2, value.b);
case T_CHAR: return ciConstant(bt2, value.c);
case T_BOOLEAN: return ciConstant(bt2, value.z);
default: return ciConstant();
}
}
ciObject* ciobj = get_object(obj);
if (ciobj->is_array()) {
return ciConstant(T_ARRAY, ciobj);
// Unbox a primitive and return it as a ciConstant.
ciConstant ciEnv::unbox_primitive_value(ciObject* cibox, BasicType expected_bt) {
jvalue value;
BasicType bt = java_lang_boxing_object::get_value(cibox->get_oop(), &value);
if (bt != expected_bt && expected_bt != T_ILLEGAL) {
assert(false, "type mismatch: %s vs %s", type2name(expected_bt), cibox->klass()->name()->as_klass_external_name());
return ciConstant();
}
switch (bt) {
case T_BOOLEAN: return ciConstant(bt, value.z);
case T_BYTE: return ciConstant(bt, value.b);
case T_SHORT: return ciConstant(bt, value.s);
case T_CHAR: return ciConstant(bt, value.c);
case T_INT: return ciConstant(bt, value.i);
case T_LONG: return ciConstant(value.j);
case T_FLOAT: return ciConstant(value.f);
case T_DOUBLE: return ciConstant(value.d);
default:
assert(false, "not a primitive type: %s", type2name(bt));
return ciConstant();
}
}
// ------------------------------------------------------------------
// ciEnv::get_resolved_constant
//
ciConstant ciEnv::get_resolved_constant(const constantPoolHandle& cpool, int obj_index) {
assert(obj_index >= 0, "");
oop obj = cpool->resolved_references()->obj_at(obj_index);
if (obj == NULL) {
// Unresolved constant. It is resolved when the corresponding slot contains a non-null reference.
// Null constant is represented as a sentinel (non-null) value.
return ciConstant();
} else if (obj == Universe::the_null_sentinel()) {
return ciConstant(T_OBJECT, get_object(NULL));
} else {
ciObject* ciobj = get_object(obj);
if (ciobj->is_array()) {
return ciConstant(T_ARRAY, ciobj);
} else {
int cp_index = cpool->object_to_cp_index(obj_index);
BasicType bt = cpool->basic_type_for_constant_at(cp_index);
if (is_java_primitive(bt)) {
assert(cpool->tag_at(cp_index).is_dynamic_constant(), "sanity");
return unbox_primitive_value(ciobj, bt);
} else {
assert(ciobj->is_instance(), "should be an instance");
return ciConstant(T_OBJECT, ciobj);
}
}
}
}
// ------------------------------------------------------------------
// ciEnv::get_constant_by_index_impl
//
// Implementation of get_constant_by_index().
ciConstant ciEnv::get_constant_by_index_impl(const constantPoolHandle& cpool,
int index, int obj_index,
ciInstanceKlass* accessor) {
bool ignore_will_link;
if (obj_index >= 0) {
ciConstant con = get_resolved_constant(cpool, obj_index);
if (con.is_valid()) {
return con;
}
}
constantTag tag = cpool->tag_at(index);
if (tag.is_int()) {
return ciConstant(T_INT, (jint)cpool->int_at(index));
@ -711,35 +749,29 @@ ciConstant ciEnv::get_constant_by_index_impl(const constantPoolHandle& cpool,
return ciConstant((jdouble)cpool->double_at(index));
} else if (tag.is_string()) {
EXCEPTION_CONTEXT;
oop string = NULL;
assert(cache_index >= 0, "should have a cache index");
string = cpool->string_at(index, cache_index, THREAD);
assert(obj_index >= 0, "should have an object index");
oop string = cpool->string_at(index, obj_index, THREAD);
if (HAS_PENDING_EXCEPTION) {
CLEAR_PENDING_EXCEPTION;
record_out_of_memory_failure();
return ciConstant();
}
ciObject* constant = get_object(string);
if (constant->is_array()) {
return ciConstant(T_ARRAY, constant);
} else {
assert (constant->is_instance(), "must be an instance, or not? ");
return ciConstant(T_OBJECT, constant);
}
ciInstance* constant = get_object(string)->as_instance();
return ciConstant(T_OBJECT, constant);
} else if (tag.is_unresolved_klass_in_error()) {
return ciConstant(T_OBJECT, get_unloaded_klass_mirror(NULL));
} else if (tag.is_klass() || tag.is_unresolved_klass()) {
ciKlass* klass = get_klass_by_index_impl(cpool, index, ignore_will_link, accessor);
assert (klass->is_instance_klass() || klass->is_array_klass(),
"must be an instance or array klass ");
return ciConstant(T_OBJECT, klass->java_mirror());
} else if (tag.is_method_type() || tag.is_method_type_in_error()) {
// must execute Java code to link this CP entry into cache[i].f1
assert(obj_index >= 0, "should have an object index");
ciSymbol* signature = get_symbol(cpool->method_type_signature_at(index));
ciObject* ciobj = get_unloaded_method_type_constant(signature);
return ciConstant(T_OBJECT, ciobj);
} else if (tag.is_method_handle() || tag.is_method_handle_in_error()) {
// must execute Java code to link this CP entry into cache[i].f1
assert(obj_index >= 0, "should have an object index");
int ref_kind = cpool->method_handle_ref_kind_at(index);
int callee_index = cpool->method_handle_klass_index_at(index);
ciKlass* callee = get_klass_by_index_impl(cpool, callee_index, ignore_will_link, accessor);
@ -748,7 +780,8 @@ ciConstant ciEnv::get_constant_by_index_impl(const constantPoolHandle& cpool,
ciObject* ciobj = get_unloaded_method_handle_constant(callee, name, signature, ref_kind);
return ciConstant(T_OBJECT, ciobj);
} else if (tag.is_dynamic_constant() || tag.is_dynamic_constant_in_error()) {
return ciConstant(); // not supported
assert(obj_index >= 0, "should have an object index");
return ciConstant(T_OBJECT, unloaded_ciinstance()); // unresolved dynamic constant
} else {
assert(false, "unknown tag: %d (%s)", tag.value(), tag.internal_name());
return ciConstant();

@ -165,6 +165,9 @@ private:
Bytecodes::Code bc,
constantTag tag);
ciConstant unbox_primitive_value(ciObject* cibox, BasicType expected_bt = T_ILLEGAL);
ciConstant get_resolved_constant(const constantPoolHandle& cpool, int obj_index);
// Get a ciObject from the object factory. Ensures uniqueness
// of ciObjects.
ciObject* get_object(oop o) {
@ -429,6 +432,8 @@ public:
}
ciInstance* unloaded_ciinstance();
ciInstanceKlass* get_box_klass_for_primitive_type(BasicType type);
// Note: To find a class from its name string, use ciSymbol::make,
// but consider adding to vmSymbols.hpp instead.

@ -555,7 +555,7 @@ ciInstance* ciObjectFactory::get_unloaded_instance(ciInstanceKlass* instance_kla
// Get a ciInstance representing an unresolved klass mirror.
//
// Currently, this ignores the parameters and returns a unique unloaded instance.
ciInstance* ciObjectFactory::get_unloaded_klass_mirror(ciKlass* type) {
ciInstance* ciObjectFactory::get_unloaded_klass_mirror(ciKlass* type) {
assert(ciEnv::_Class_klass != NULL, "");
return get_unloaded_instance(ciEnv::_Class_klass->as_instance_klass());
}
@ -570,7 +570,7 @@ ciInstance* ciObjectFactory::get_unloaded_method_handle_constant(ciKlass* holde
ciSymbol* name,
ciSymbol* signature,
int ref_kind) {
if (ciEnv::_MethodHandle_klass == NULL) return NULL;
assert(ciEnv::_MethodHandle_klass != NULL, "");
return get_unloaded_instance(ciEnv::_MethodHandle_klass->as_instance_klass());
}
@ -581,12 +581,12 @@ ciInstance* ciObjectFactory::get_unloaded_method_handle_constant(ciKlass* holde
//
// Currently, this ignores the parameters and returns a unique unloaded instance.
ciInstance* ciObjectFactory::get_unloaded_method_type_constant(ciSymbol* signature) {
if (ciEnv::_MethodType_klass == NULL) return NULL;
assert(ciEnv::_MethodType_klass != NULL, "");
return get_unloaded_instance(ciEnv::_MethodType_klass->as_instance_klass());
}
ciInstance* ciObjectFactory::get_unloaded_object_constant() {
if (ciEnv::_Object_klass == NULL) return NULL;
assert(ciEnv::_Object_klass != NULL, "");
return get_unloaded_instance(ciEnv::_Object_klass->as_instance_klass());
}

@ -230,14 +230,19 @@ int ciBytecodeStream::get_constant_pool_index() const {
// If this bytecode is one of the ldc variants, get the referenced
// constant.
ciConstant ciBytecodeStream::get_constant() {
VM_ENTRY_MARK;
constantPoolHandle cpool(THREAD, _method->get_Method()->constants());
int pool_index = get_constant_raw_index();
int cache_index = -1;
if (has_cache_index()) {
cache_index = pool_index;
pool_index = -1;
pool_index = cpool->object_to_cp_index(cache_index);
} else if (cpool->tag_at(pool_index).is_dynamic_constant() ||
cpool->tag_at(pool_index).is_dynamic_constant_in_error()) {
// Condy with primitive type is not quickened, so the index into resolved reference cache should be reconstructed.
assert(is_java_primitive(cpool->basic_type_for_constant_at(pool_index)), "not quickened");
cache_index = cpool->cp_to_object_index(pool_index);
}
VM_ENTRY_MARK;
constantPoolHandle cpool(THREAD, _method->get_Method()->constants());
return CURRENT_ENV->get_constant_by_index(cpool, pool_index, cache_index, _holder);
}

@ -727,7 +727,8 @@ void ciTypeFlow::StateVector::do_ldc(ciBytecodeStream* str) {
}
ciConstant con = str->get_constant();
if (con.is_valid()) {
BasicType basic_type = con.basic_type();
int index = str->get_constant_pool_index();
BasicType basic_type = str->get_basic_type_for_constant_at(index);
if (is_reference_type(basic_type)) {
ciObject* obj = con.as_object();
if (obj->is_null_object()) {
@ -737,11 +738,12 @@ void ciTypeFlow::StateVector::do_ldc(ciBytecodeStream* str) {
push_object(obj->klass());
}
} else {
assert(basic_type == con.basic_type() || con.basic_type() == T_OBJECT,
"not a boxed form: %s vs %s", type2name(basic_type), type2name(con.basic_type()));
push_translate(ciType::make(basic_type));
}
} else {
// OutOfMemoryError in the CI while loading constant.
// Unresolved condy also lands here (not yet supported).
// OutOfMemoryError in the CI while loading a String constant.
push_null();
outer()->record_failure("ldc did not link");
}

@ -1871,8 +1871,6 @@ void Parse::do_one_bytecode() {
case Bytecodes::_ldc2_w: {
ciConstant constant = iter().get_constant();
if (constant.is_loaded()) {
assert(constant.basic_type() != T_OBJECT || constant.as_object()->is_instance(),
"must be java_mirror of klass");
const Type* con_type = Type::make_from_constant(constant);
if (con_type != NULL) {
push_node(con_type->basic_type(), makecon(con_type));

@ -0,0 +1,254 @@
/*
* Copyright (c) 2022, 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.
*/
/*
* @test
* @bug 8280473
* @library /test/lib
* @modules java.base/jdk.internal.org.objectweb.asm
*
* @run main/othervm -XX:+TieredCompilation -XX:TieredStopAtLevel=1
* -Xbatch -XX:CompileThreshold=100 -XX:CompileCommand=compileonly,*::test
* -XX:CompileCommand=quiet -XX:+PrintCompilation
* compiler.runtime.TestConstantDynamic
* @run main/othervm -XX:-TieredCompilation
* -Xbatch -XX:CompileThreshold=100 -XX:CompileCommand=compileonly,*::test
* -XX:CompileCommand=quiet -XX:+PrintCompilation
* compiler.runtime.TestConstantDynamic
*/
package compiler.runtime;
import jdk.internal.org.objectweb.asm.*;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.lang.constant.ConstantDescs;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandleProxies;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import static jdk.internal.org.objectweb.asm.ClassWriter.*;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
public class TestConstantDynamic {
static final Class<TestConstantDynamic> THIS_CLASS = TestConstantDynamic.class;
static final String THIS_CLASS_NAME = THIS_CLASS.getName().replace('.', '/');
static final String CLASS_NAME = THIS_CLASS_NAME + "$Test";
public interface Test {
Object run(boolean b);
}
public static final String PATH = System.getProperty("test.classes", ".") + java.io.File.separator;
private static int ID = 0;
/* =================================================================================================== */
static final String BSM_DESC = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;";
static final Handle BSM = new Handle(H_INVOKESTATIC, THIS_CLASS_NAME, "bsm", BSM_DESC, false);
static Object bsm(MethodHandles.Lookup lookup, String name, Class c) throws IllegalAccessException {
Object[] classData = MethodHandles.classData(lookup, ConstantDescs.DEFAULT_NAME, Object[].class);
Object value = classData[0];
System.out.printf("BSM: lookup=%s name=\"%s\" class=%s => \"%s\"\n", lookup, name, c, classData[0]);
return value;
}
static final Handle THROWING_BSM = new Handle(H_INVOKESTATIC, THIS_CLASS_NAME, "throwingBSM", BSM_DESC, false);
static Object throwingBSM(MethodHandles.Lookup lookup, String name, Class c) throws IllegalAccessException {
Object[] classData = (Object[])MethodHandles.classData(lookup, ConstantDescs.DEFAULT_NAME, Object[].class);
Object value = classData[0];
System.out.printf("BSM: lookup=%s name=\"%s\" class=%s value=\"%s\" => Exception\n", lookup, name, c, value);
throw new IllegalArgumentException(lookup.lookupClass().getName() + ": " + c.getName() + " " + name + " " + value);
}
/* =================================================================================================== */
static byte[] generateClassFile(String suffix, String desc, int retOpcode, Handle bsm) throws IOException {
var cw = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES);
String name = CLASS_NAME + "_" + suffix + "_" + (++ID);
cw.visit(V19, ACC_PUBLIC | ACC_SUPER, name, null, "java/lang/Object", null);
Handle localBSM = new Handle(H_INVOKESTATIC, name, "bsm", BSM_DESC, false);
{
var mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "bsm", BSM_DESC, null, null);
mv.visitLdcInsn(bsm);
mv.visitIntInsn(ALOAD, 0);
mv.visitIntInsn(ALOAD, 1);
mv.visitIntInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact", BSM_DESC, false);
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
}
{
var mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "test", "(Z)" + desc, null, null);
mv.visitCode();
Label endL = new Label();
Label falseL = new Label();
mv.visitIntInsn(ILOAD, 0);
mv.visitJumpInsn(Opcodes.IFNE, falseL);
mv.visitLdcInsn(new ConstantDynamic("first", desc, localBSM)); // is resolved on b = false
mv.visitJumpInsn(GOTO, endL);
mv.visitLabel(falseL);
mv.visitLdcInsn(new ConstantDynamic("second", desc, localBSM)); // is resolved on b = true
mv.visitLabel(endL);
mv.visitInsn(retOpcode);
mv.visitMaxs(0, 0);
}
byte[] classFile = cw.toByteArray();
try (FileOutputStream fos = new FileOutputStream(PATH + name + ".class")) {
fos.write(classFile);
}
return classFile;
}
static Test generate(String desc, int retOpcode, Object value, Handle bsm, boolean shouldThrow) {
try {
byte[] classFile = generateClassFile("CD", desc, retOpcode, bsm);
Object[] classData = new Object[] { value };
MethodHandles.Lookup testLookup = MethodHandles.lookup().defineHiddenClassWithClassData(classFile, classData, true);
Method testMethod = testLookup.lookupClass().getDeclaredMethod("test", boolean.class);
MethodHandle testMH = testLookup.unreflect(testMethod);
if (shouldThrow) {
// Install empty handler for linkage errors, but throw an error on successful invocation.
// try { Test.test(b); throw AssertionError(); } catch (LinkageError e) { /* expected */ }
testMH = MethodHandles.filterReturnValue(testMH,
MethodHandles.dropArguments(
MethodHandles.insertArguments(
MethodHandles.throwException(testMH.type().returnType(), AssertionError.class),
0, new AssertionError("no exception thrown")),
0, testMH.type().returnType()));
testMH = MethodHandles.catchException(testMH, LinkageError.class,
MethodHandles.empty(MethodType.methodType(testMH.type().returnType(), LinkageError.class)));
} else {
Class<?> type = testMH.type().returnType();
testMH = MethodHandles.filterReturnValue(testMH,
MethodHandles.insertArguments(VALIDATE_MH, 0, value)
.asType(MethodType.methodType(type, type)));
}
return MethodHandleProxies.asInterfaceInstance(Test.class, testMH);
} catch (Throwable e) {
throw new InternalError(e);
}
}
static final MethodHandle VALIDATE_MH;
static {
try {
VALIDATE_MH = MethodHandles.lookup().findStatic(THIS_CLASS, "validateResult",
MethodType.methodType(Object.class, Object.class, Object.class));
} catch (ReflectiveOperationException e) {
throw new InternalError(e);
}
}
static Object validateResult(Object expected, Object actual) {
if ((expected == null && actual != null) ||
(expected != null && !expected.equals(actual))) {
throw new AssertionError(String.format("expected=%s != actual=%s", expected.toString(), actual.toString()));
}
return actual;
}
private Handle bsm;
private boolean shouldThrow;
TestConstantDynamic(Handle bsm, boolean shouldThrow) {
this.bsm = bsm;
this.shouldThrow = shouldThrow;
}
static TestConstantDynamic shouldNotThrow() {
return new TestConstantDynamic(BSM, false);
}
static TestConstantDynamic shouldThrow() {
return new TestConstantDynamic(THROWING_BSM, true);
}
static void shouldThrow(Handle bsm, String desc, int retOpcode, Object value) {
(new TestConstantDynamic(bsm, true)).test(desc, retOpcode, value);
}
void test(String desc, int retOpcode, Object value) {
Test test = generate(desc, retOpcode, value, bsm, shouldThrow);
for (int i = 0; i < 200; i++) {
test.run(false);
}
for (int i = 0; i < 200; i++) {
test.run(true);
}
}
static void run(TestConstantDynamic t) {
t.test("Z", IRETURN, Boolean.TRUE);
t.test("B", IRETURN, Byte.MAX_VALUE);
t.test("S", IRETURN, Short.MAX_VALUE);
t.test("C", IRETURN, Character.MAX_VALUE);
t.test("I", IRETURN, Integer.MAX_VALUE);
t.test("J", LRETURN, Long.MAX_VALUE);
t.test("F", FRETURN, Float.MAX_VALUE);
t.test("D", DRETURN, Double.MAX_VALUE);
t.test("Ljava/lang/Object;", ARETURN, new Object());
t.test("Ljava/lang/Object;", ARETURN, null);
t.test("[Ljava/lang/Object;", ARETURN, new Object[0]);
t.test("[Ljava/lang/Object;", ARETURN, null);
t.test("[I", ARETURN, new int[0]);
t.test("[I", ARETURN, null);
t.test("Ljava/lang/Runnable;", ARETURN, (Runnable)(() -> {}));
t.test("Ljava/lang/Runnable;", ARETURN, null);
}
public static void main(String[] args) {
run(shouldNotThrow());
run(shouldThrow()); // use error-throwing BSM
shouldThrow(BSM, "Ljava/lang/Runnable;", ARETURN, new Object()); // not a Runnable
}
}

@ -230,16 +230,18 @@ public abstract class TestConstantsInError implements OutputProcessor {
}
public void process(OutputAnalyzer results, boolean isC1) {
if (isC1) {
results.shouldMatch("Test_CD1.*::test \\(3 bytes\\) COMPILE SKIPPED: could not resolve a constant")
.shouldMatch("Test_CD2.*::test \\(3 bytes\\) COMPILE SKIPPED: could not resolve a constant")
.shouldMatch("Test_CD3.*::test \\(3 bytes\\) COMPILE SKIPPED: could not resolve a constant")
.shouldMatch("Test_CD4.*::test \\(3 bytes\\) COMPILE SKIPPED: could not resolve a constant");
results.shouldMatch("Test_CD1.*::test \\(3 bytes\\)$")
.shouldMatch("Test_CD2.*::test \\(3 bytes\\)$")
.shouldMatch("Test_CD3.*::test \\(3 bytes\\)$")
.shouldMatch("Test_CD4.*::test \\(3 bytes\\)$");
if (isC1 && Platform.isAArch64()) { // no code patching
results.shouldMatch("Test_CD1.*::test \\(3 bytes\\) made not entrant")
.shouldMatch("Test_CD2.*::test \\(3 bytes\\) made not entrant")
.shouldMatch("Test_CD3.*::test \\(3 bytes\\) made not entrant")
.shouldMatch("Test_CD4.*::test \\(3 bytes\\) made not entrant");
} else {
results.shouldMatch("Test_CD1.*::test \\(3 bytes\\)$")
.shouldMatch("Test_CD2.*::test \\(3 bytes\\)$")
.shouldMatch("Test_CD3.*::test \\(3 bytes\\)$")
.shouldMatch("Test_CD4.*::test \\(3 bytes\\)$");
results.shouldNotContain("made not entrant");
}
}
}