36 invoke static/special/virtual/interface
Posted 蓝风9
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了36 invoke static/special/virtual/interface相关的知识,希望对你有一定的参考价值。
前言
// 呵呵 加更, 今天早上看到了一个很治愈很可爱的小朋友, 和小朋友聊天是一件很有意思的事情, 从他/她们身上也能了解到很多, 文末mark一下
这四条指令的使用场景请参见 : Chapter 4. The class File Format
以下代码, 截图 基于 jdk9
invokestatic
请直接参照这篇文章 : 方法调用的流程(invokestatic为例)
直接获取的声明的类型上面的这个方法, 然后进行调用
判断了一下 方法是否已经解析完成, 如果没有解析 则调用 InterpreterRuntime::resolve_from_cache 触发解析
然后 之后更新 rbx 为方法的 解析的目标地址, 然后 跳到给定的方法的 entry_point
entry_point 部分的调用代码 可以参考 15 main方法的栈帧信息
当然 其中还有一些 处理标志, 处理返回地址, profiling 方法的调用信息 等等
void TemplateTable::invokestatic(int byte_no)
transition(vtos, vtos);
assert(byte_no == f1_byte, "use this argument");
prepare_invoke(byte_no, rbx); // get f1 Method*
// do the call
__ profile_call(rax);
__ profile_arguments_type(rax, rbx, rbcp, false);
__ jump_from_interpreted(rbx, rax);
invokespecial
类似于上面的 invokestatic 不过这里多了一些东西, 获取 receiver(this, 调用方法的对象), 对 receiver 做空校验
直接获取的声明的类型上面的这个方法, 然后进行调用
void TemplateTable::invokespecial(int byte_no)
transition(vtos, vtos);
assert(byte_no == f1_byte, "use this argument");
prepare_invoke(byte_no, rbx, noreg, // get f1 Method*
rcx); // get receiver also for null check
__ verify_oop(rcx);
__ null_check(rcx);
// do the call
__ profile_call(rax);
__ profile_arguments_type(rax, rbx, rbcp, false);
__ jump_from_interpreted(rbx, rax);
invokevirtual
多态调用 receiver 上面的这个方法
void TemplateTable::invokevirtual(int byte_no)
transition(vtos, vtos);
assert(byte_no == f2_byte, "use this argument");
prepare_invoke(byte_no,
rbx, // method or vtable index
noreg, // unused itable index
rcx, rdx); // recv, flags
// rbx: index
// rcx: receiver
// rdx: flags
invokevirtual_helper(rbx, rcx, rdx);
void TemplateTable::invokevirtual_helper(Register index,
Register recv,
Register flags)
// Uses temporary registers rax, rdx
assert_different_registers(index, recv, rax, rdx);
assert(index == rbx, "");
assert(recv == rcx, "");
// Test for an invoke of a final method
Label notFinal;
__ movl(rax, flags);
__ andl(rax, (1 << ConstantPoolCacheEntry::is_vfinal_shift));
__ jcc(Assembler::zero, notFinal);
const Register method = index; // method must be rbx
assert(method == rbx,
"Method* must be rbx for interpreter calling convention");
// do the call - the index is actually the method to call
// that is, f2 is a vtable index if !is_vfinal, else f2 is a Method*
// It's final, need a null check here!
__ null_check(recv);
// profile this call
__ profile_final_call(rax);
__ profile_arguments_type(rax, method, rbcp, true);
__ jump_from_interpreted(method, rax);
__ bind(notFinal);
// get receiver klass
__ null_check(recv, oopDesc::klass_offset_in_bytes());
__ load_klass(rax, recv);
// profile this call
__ profile_virtual_call(rax, rlocals, rdx);
// get target Method* & entry point
__ lookup_virtual_method(rax, index, method);
__ profile_called_method(method, rdx, rbcp);
__ profile_arguments_type(rdx, method, rbcp, true);
__ jump_from_interpreted(method, rdx);
// virtual method calling
void MacroAssembler::lookup_virtual_method(Register recv_klass,
RegisterOrConstant vtable_index,
Register method_result)
const int base = in_bytes(Klass::vtable_start_offset());
assert(vtableEntry::size() * wordSize == wordSize, "else adjust the scaling in the code below");
Address vtable_entry_addr(recv_klass,
vtable_index, Address::times_ptr,
base + vtableEntry::method_offset_in_bytes());
movptr(method_result, vtable_entry_addr);
如果方法是 final(不可重写) 方法, 则对 reciver 做空校验, 直接调用 该方法
如果是非 final 方法, 则查询 vtable(receiver 的 InstanceKlass) 中 vtable_index 对应的需要执行的方法, 然后调用该方法
vtable 结构相关 请参见 : 根据 InstanceKlass 查找 vtable 的数据
invokeinterface
多态调用 receiver 上面的这个方法
void TemplateTable::invokeinterface(int byte_no)
transition(vtos, vtos);
assert(byte_no == f1_byte, "use this argument");
prepare_invoke(byte_no, rax, rbx, // get f1 Klass*, f2 itable index
rcx, rdx); // recv, flags
// rax: interface klass (from f1)
// rbx: itable index (from f2)
// rcx: receiver
// rdx: flags
// Special case of invokeinterface called for virtual method of
// java.lang.Object. See cpCacheOop.cpp for details.
// This code isn't produced by javac, but could be produced by
// another compliant java compiler.
Label notMethod;
__ movl(rlocals, rdx);
__ andl(rlocals, (1 << ConstantPoolCacheEntry::is_forced_virtual_shift));
__ jcc(Assembler::zero, notMethod);
invokevirtual_helper(rbx, rcx, rdx);
__ bind(notMethod);
// Get receiver klass into rdx - also a null check
__ restore_locals(); // restore r14
__ null_check(rcx, oopDesc::klass_offset_in_bytes());
__ load_klass(rdx, rcx);
// profile this call
__ profile_virtual_call(rdx, rbcp, rlocals);
Label no_such_interface, no_such_method;
__ lookup_interface_method(// inputs: rec. class, interface, itable index
rdx, rax, rbx,
// outputs: method, scan temp. reg
rbx, rbcp,
no_such_interface);
// rbx: Method* to call
// rcx: receiver
// Check for abstract method error
// Note: This should be done more efficiently via a throw_abstract_method_error
// interpreter entry point and a conditional jump to it in case of a null
// method.
__ testptr(rbx, rbx);
__ jcc(Assembler::zero, no_such_method);
__ profile_called_method(rbx, rbcp, rdx);
__ profile_arguments_type(rdx, rbx, rbcp, true);
// do the call
// rcx: receiver
// rbx,: Method*
__ jump_from_interpreted(rbx, rdx);
__ should_not_reach_here();
// exception handling code follows...
// note: must restore interpreter registers to canonical
// state for exception handling to work correctly!
__ bind(no_such_method);
// throw exception
__ pop(rbx); // pop return address (pushed by prepare_invoke)
__ restore_bcp(); // rbcp must be correct for exception handler (was destroyed)
__ restore_locals(); // make sure locals pointer is correct as well (was destroyed)
__ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_AbstractMethodError));
// the call_VM checks for exception, so we should never return here.
__ should_not_reach_here();
__ bind(no_such_interface);
// throw exception
__ pop(rbx); // pop return address (pushed by prepare_invoke)
__ restore_bcp(); // rbcp must be correct for exception handler (was destroyed)
__ restore_locals(); // make sure locals pointer is correct as well (was destroyed)
__ call_VM(noreg, CAST_FROM_FN_PTR(address,
InterpreterRuntime::throw_IncompatibleClassChangeError));
// the call_VM checks for exception, so we should never return here.
__ should_not_reach_here();
// Look up the method for a megamorphic invokeinterface call.
// The target method is determined by <intf_klass, itable_index>.
// The receiver klass is in recv_klass.
// On success, the result will be in method_result, and execution falls through.
// On failure, execution transfers to the given label.
void MacroAssembler::lookup_interface_method(Register recv_klass,
Register intf_klass,
RegisterOrConstant itable_index,
Register method_result,
Register scan_temp,
Label& L_no_such_interface)
assert_different_registers(recv_klass, intf_klass, method_result, scan_temp);
assert(itable_index.is_constant() || itable_index.as_register() == method_result,
"caller must use same register for non-constant itable index as for method");
// Compute start of first itableOffsetEntry (which is at the end of the vtable)
int vtable_base = in_bytes(Klass::vtable_start_offset());
int itentry_off = itableMethodEntry::method_offset_in_bytes();
int scan_step = itableOffsetEntry::size() * wordSize;
int vte_size = vtableEntry::size_in_bytes();
Address::ScaleFactor times_vte_scale = Address::times_ptr;
assert(vte_size == wordSize, "else adjust times_vte_scale");
movl(scan_temp, Address(recv_klass, Klass::vtable_length_offset()));
// %%% Could store the aligned, prescaled offset in the klassoop.
lea(scan_temp, Address(recv_klass, scan_temp, times_vte_scale, vtable_base));
// Adjust recv_klass by scaled itable_index, so we can free itable_index.
assert(itableMethodEntry::size() * wordSize == wordSize, "adjust the scaling in the code below");
lea(recv_klass, Address(recv_klass, itable_index, Address::times_ptr, itentry_off));
// for (scan = klass->itable(); scan->interface() != NULL; scan += scan_step)
// if (scan->interface() == intf)
// result = (klass + scan->offset() + itable_index);
//
//
Label search, found_method;
for (int peel = 1; peel >= 0; peel--)
movptr(method_result, Address(scan_temp, itableOffsetEntry::interface_offset_in_bytes()));
cmpptr(intf_klass, method_result);
if (peel)
jccb(Assembler::equal, found_method);
else
jccb(Assembler::notEqual, search);
// (invert the test to fall through to found_method...)
if (!peel) break;
bind(search);
// Check that the previous entry is non-null. A null entry means that
// the receiver class doesn't implement the interface, and wasn't the
// same as when the caller was compiled.
testptr(method_result, method_result);
jcc(Assembler::zero, L_no_such_interface);
addptr(scan_temp, scan_step);
bind(found_method);
// Got a hit.
movl(scan_temp, Address(scan_temp, itableOffsetEntry::offset_offset_in_bytes()));
movptr(method_result, Address(recv_klass, scan_temp, Address::times_1));
如果 "is the interface reference forced to virtual mode", 直接采用 invokevirtual 的方式调用当前方法
如果 itable 里面没有找到目标接口, 抛出 IncompatibleClassChangeError
如果 itable 里面没有找到目标方法, 抛出 AbstractMethodError
然后 重点是 在 itable 里面查询 目标方法
获取到 目标方法之后, 调用目标方法
我们吧 lookup_interface_method 里面的循环展开来看
void MacroAssembler::lookup_interface_method(Register recv_klass,
Register intf_klass,
RegisterOrConstant itable_index,
Register method_result,
Register scan_temp,
Label& L_no_such_interface)
assert_different_registers(recv_klass, intf_klass, method_result, scan_temp);
assert(itable_index.is_constant() || itable_index.as_register() == method_result,
"caller must use same register for non-constant itable index as for method");
// Compute start of first itableOffsetEntry (which is at the end of the vtable)
int vtable_base = in_bytes(Klass::vtable_start_offset());
int itentry_off = itableMethodEntry::method_offset_in_bytes();
int scan_step = itableOffsetEntry::size() * wordSize;
int vte_size = vtableEntry::size_in_bytes();
Address::ScaleFactor times_vte_scale = Address::times_ptr;
assert(vte_size == wordSize, "else adjust times_vte_scale");
movl(scan_temp, Address(recv_klass, Klass::vtable_length_offset()));
// %%% Could store the aligned, prescaled offset in the klassoop.
lea(scan_temp, Address(recv_klass, scan_temp, times_vte_scale, vtable_base));
// Adjust recv_klass by scaled itable_index, so we can free itable_index.
assert(itableMethodEntry::size() * wordSize == wordSize, "adjust the scaling in the code below");
lea(recv_klass, Address(recv_klass, itable_index, Address::times_ptr, itentry_off));
// for (scan = klass->itable(); scan->interface() != NULL; scan += scan_step)
// if (scan->interface() == intf)
// result = (klass + scan->offset() + itable_index);
//
//
Label search, found_method;
movptr(method_result, Address(scan_temp, itableOffsetEntry::interface_offset_in_bytes()));
cmpptr(intf_klass, method_result);
jccb(Assembler::equal, found_method);
bind(search);
// Check that the previous entry is non-null. A null entry means that
// the receiver class doesn't implement the interface, and wasn't the
// same as when the caller was compiled.
testptr(method_result, method_result);
jcc(Assembler::zero, L_no_such_interface);
addptr(scan_temp, scan_step);
movptr(method_result, Address(scan_temp, itableOffsetEntry::interface_offset_in_bytes()));
cmpptr(intf_klass, method_result);
jccb(Assembler::notEqual, search);
bind(found_method);
// Got a hit.
movl(scan_temp, Address(scan_temp, itableOffsetEntry::offset_offset_in_bytes()));
movptr(method_result, Address(recv_klass, scan_temp, Address::times_1));
就回发现 他的思路是 遍历 itable 里面的 itableOffsetEntry, 查询 目标接口 对应的 itableOffsetEntry
然后获取 目标接口 对应的 methodEntry 的偏移
然后在加上 itable_index 获取我们需要的目标方法的地址
itable 结构相关 请参见 : 根据 InstanceKlass 查找 itable 的数据
添加于2020.07.04 - bytecodeInterpreter.cpp 中的逻辑代码
bytecodeInterpreter.cpp 中可读性更好的代码
CASE(_invokeinterface):
u2 index = Bytes::get_native_u2(pc+1);
// QQQ Need to make this as inlined as possible. Probably need to split all the bytecode cases
// out so c++ compiler has a chance for constant prop to fold everything possible away.
ConstantPoolCacheEntry* cache = cp->entry_at(index);
if (!cache->is_resolved((Bytecodes::Code)opcode))
CALL_VM(InterpreterRuntime::resolve_from_cache(THREAD, (Bytecodes::Code)opcode),
handle_exception);
cache = cp->entry_at(index);
istate->set_msg(call_method);
// Special case of invokeinterface called for virtual method of
// java.lang.Object. See cpCacheOop.cpp for details.
// This code isn't produced by javac, but could be produced by
// another compliant java compiler.
if (cache->is_forced_virtual())
Method* callee;
CHECK_NULL(STACK_OBJECT(-(cache->parameter_size())));
if (cache->is_vfinal())
callee = cache->f2_as_vfinal_method();
// Profile 'special case of invokeinterface' final call.
BI_PROFILE_UPDATE_FINALCALL();
else
// Get receiver.
int parms = cache->parameter_size();
// Same comments as invokevirtual apply here.
oop rcvr = STACK_OBJECT(-parms);
VERIFY_OOP(rcvr);
Klass* rcvrKlass = rcvr->klass();
callee = (Method*) rcvrKlass->method_at_vtable(cache->f2_as_index());
// Profile 'special case of invokeinterface' virtual call.
BI_PROFILE_UPDATE_VIRTUALCALL(rcvrKlass);
istate->set_callee(callee);
istate->set_callee_entry_point(callee->from_interpreted_entry());
#ifdef VM_JVMTI
if (JvmtiExport::can_post_interpreter_events() && THREAD->is_interp_only_mode())
istate->set_callee_entry_point(callee->interpreter_entry());
#endif /* VM_JVMTI */
istate->set_bcp_advance(5);
UPDATE_PC_AND_RETURN(0); // I'll be back...
// this could definitely be cleaned up QQQ
Method* callee;
Klass* iclass = cache->f1_as_klass();
// InstanceKlass* interface = (InstanceKlass*) iclass;
// get receiver
int parms = cache->parameter_size();
oop rcvr = STACK_OBJECT(-parms);
CHECK_NULL(rcvr);
InstanceKlass* int2 = (InstanceKlass*) rcvr->klass();
itableOffsetEntry* ki = (itableOffsetEntry*) int2->start_of_itable();
int i;
for ( i = 0 ; i < int2->itable_length() ; i++, ki++ )
if (ki->interface_klass() == iclass) break;
// If the interface isn't found, this class doesn't implement this
// interface. The link resolver checks this but only for the first
// time this interface is called.
if (i == int2->itable_length())
VM_JAVA_ERROR(vmSymbols::java_lang_IncompatibleClassChangeError(), "", note_no_trap);
int mindex = cache->f2_as_index();
itableMethodEntry* im = ki->first_method_entry(rcvr->klass());
callee = im[mindex].method();
if (callee == NULL)
VM_JAVA_ERROR(vmSymbols::java_lang_AbstractMethodError(), "", note_no_trap);
// Profile virtual call.
BI_PROFILE_UPDATE_VIRTUALCALL(rcvr->klass());
istate->set_callee(callee);
istate->set_callee_entry_point(callee->from_interpreted_entry());
#ifdef VM_JVMTI
if (JvmtiExport::can_post_interpreter_events() && THREAD->is_interp_only_mode())
istate->set_callee_entry_point(callee->interpreter_entry());
#endif /* VM_JVMTI */
istate->set_bcp_advance(5);
UPDATE_PC_AND_RETURN(0); // I'll be back...
CASE(_invokevirtual):
CASE(_invokespecial):
CASE(_invokestatic):
u2 index = Bytes::get_native_u2(pc+1);
ConstantPoolCacheEntry* cache = cp->entry_at(index);
// QQQ Need to make this as inlined as possible. Probably need to split all the bytecode cases
// out so c++ compiler has a chance for constant prop to fold everything possible away.
if (!cache->is_resolved((Bytecodes::Code)opcode))
CALL_VM(InterpreterRuntime::resolve_from_cache(THREAD, (Bytecodes::Code)opcode),
handle_exception);
cache = cp->entry_at(index);
istate->set_msg(call_method);
Method* callee;
if ((Bytecodes::Code)opcode == Bytecodes::_invokevirtual)
CHECK_NULL(STACK_OBJECT(-(cache->parameter_size())));
if (cache->is_vfinal())
callee = cache->f2_as_vfinal_method();
// Profile final call.
BI_PROFILE_UPDATE_FINALCALL();
else
// get receiver
int parms = cache->parameter_size();
// this works but needs a resourcemark and seems to create a vtable on every call:
// Method* callee = rcvr->klass()->vtable()->method_at(cache->f2_as_index());
//
// this fails with an assert
// InstanceKlass* rcvrKlass = InstanceKlass::cast(STACK_OBJECT(-parms)->klass());
// but this works
oop rcvr = STACK_OBJECT(-parms);
VERIFY_OOP(rcvr);
Klass* rcvrKlass = rcvr->klass();
/*
Executing this code in java.lang.String:
public String(char value[])
this.count = value.length;
this.value = (char[])value.clone();
a find on rcvr->klass() reports:
type array chartype array class
- klass: other class
but using InstanceKlass::cast(STACK_OBJECT(-parms)->klass()) causes in assertion failure
because rcvr->klass()->is_instance_klass() == 0
However it seems to have a vtable in the right location. Huh?
Because vtables have the same offset for ArrayKlass and InstanceKlass.
*/
callee = (Method*) rcvrKlass->method_at_vtable(cache->f2_as_index());
// Profile virtual call.
BI_PROFILE_UPDATE_VIRTUALCALL(rcvrKlass);
else
if ((Bytecodes::Code)opcode == Bytecodes::_invokespecial)
CHECK_NULL(STACK_OBJECT(-(cache->parameter_size())));
callee = cache->f1_as_method();
// Profile call.
BI_PROFILE_UPDATE_CALL();
istate->set_callee(callee);
istate->set_callee_entry_point(callee->from_interpreted_entry());
#ifdef VM_JVMTI
if (JvmtiExport::can_post_interpreter_events() && THREAD->is_interp_only_mode())
istate->set_callee_entry_point(callee->interpreter_entry());
#endif /* VM_JVMTI */
istate->set_bcp_advance(3);
UPDATE_PC_AND_RETURN(0); // I'll be back...
完
-- 2022.05.04 08:44 ^_^
参考
根据 InstanceKlass 查找 vtable 的数据
根据 InstanceKlass 查找 itable 的数据
以上是关于36 invoke static/special/virtual/interface的主要内容,如果未能解决你的问题,请参考以下文章
在 Dispatcher.Invoke() 中使用 lambda 表达式作为参数
Cloud Functions 返回 403 错误也具有 Cloud Functions Invoker 权限