8280473: CI: Support unresolved JVM_CONSTANT_Dynamic constant pool entries
Reviewed-by: dlong, redestad, neliasso
This commit is contained in:
parent
f07b816523
commit
88fc3bfdff
src/hotspot/share
c1
ci
opto
test/hotspot/jtreg/compiler/runtime
@ -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));
|
||||
|
254
test/hotspot/jtreg/compiler/runtime/TestConstantDynamic.java
Normal file
254
test/hotspot/jtreg/compiler/runtime/TestConstantDynamic.java
Normal file
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user