zoukankan      html  css  js  c++  java
  • CoreLibs invokedynamic字节码指北

    VM层

    templateInterpreter的invokedynamic如下:

    void TemplateTable::invokedynamic(int byte_no) {
      transition(vtos, vtos);
      assert(byte_no == f1_byte, "use this argument");
    
      const Register rbx_method   = rbx;
      const Register rax_callsite = rax;
    
      prepare_invoke(byte_no, rbx_method, rax_callsite);
    
      // rax: CallSite object (from cpool->resolved_references[f1])
      // rbx: MH.linkToCallSite method (from f2)
    
      // Note:  rax_callsite is already pushed by prepare_invoke
    
      // %%% should make a type profile for any invokedynamic that takes a ref argument
      // profile this call
      __ profile_call(rbcp);
      __ profile_arguments_type(rdx, rbx_method, rbcp, false);
    
      __ verify_oop(rax_callsite);
    
      __ jump_from_interpreted(rbx_method, rdx);
    }
    
    
    void TemplateTable::prepare_invoke(int byte_no,
                                       Register method,  // linked method (or i-klass)
                                       Register index,   // itable index, MethodType, etc.
                                       Register recv,    // if caller wants to see it
                                       Register flags    // if caller wants to test it
                                       ) {
      // determine flags
      const Bytecodes::Code code = bytecode();
      const bool is_invokeinterface  = code == Bytecodes::_invokeinterface;
      const bool is_invokedynamic    = code == Bytecodes::_invokedynamic;
      const bool is_invokehandle     = code == Bytecodes::_invokehandle;
      const bool is_invokevirtual    = code == Bytecodes::_invokevirtual;
      const bool is_invokespecial    = code == Bytecodes::_invokespecial;
      const bool load_receiver       = (recv  != noreg);
      const bool save_flags          = (flags != noreg);
      assert(load_receiver == (code != Bytecodes::_invokestatic && code != Bytecodes::_invokedynamic), "");
      assert(save_flags    == (is_invokeinterface || is_invokevirtual), "need flags for vfinal");
      assert(flags == noreg || flags == rdx, "");
      assert(recv  == noreg || recv  == rcx, "");
    
      // setup registers & access constant pool cache
      if (recv  == noreg)  recv  = rcx;
      if (flags == noreg)  flags = rdx;
      assert_different_registers(method, index, recv, flags);
    
      // save 'interpreter return address'
      __ save_bcp();
    
      load_invoke_cp_cache_entry(byte_no, method, index, flags, is_invokevirtual, false, is_invokedynamic);
    
      // maybe push appendix to arguments (just before return address)
      if (is_invokedynamic || is_invokehandle) {
        Label L_no_push;
        __ testl(flags, (1 << ConstantPoolCacheEntry::has_appendix_shift));
        __ jcc(Assembler::zero, L_no_push);
        // Push the appendix as a trailing parameter.
        // This must be done before we get the receiver,
        // since the parameter_size includes it.
        __ push(rbx);
        __ mov(rbx, index);
        assert(ConstantPoolCacheEntry::_indy_resolved_references_appendix_offset == 0, "appendix expected at index+0");
        __ load_resolved_reference_at_index(index, rbx);
        __ pop(rbx);
        __ push(index);  // push appendix (MethodType, CallSite, etc.)
        __ bind(L_no_push);
      }
    
      // load receiver if needed (after appendix is pushed so parameter size is correct)
      // Note: no return address pushed yet
      if (load_receiver) {
        __ movl(recv, flags);
        __ andl(recv, ConstantPoolCacheEntry::parameter_size_mask);
        const int no_return_pc_pushed_yet = -1;  // argument slot correction before we push return address
        const int receiver_is_at_end      = -1;  // back off one slot to get receiver
        Address recv_addr = __ argument_address(recv, no_return_pc_pushed_yet + receiver_is_at_end);
        __ movptr(recv, recv_addr);
        __ verify_oop(recv);
      }
    
      if (save_flags) {
        __ movl(rbcp, flags);
      }
    
      // push return address
      __ push(flags);
    
      // Restore flags value from the constant pool cache, and restore rsi
      // for later null checks.  r13 is the bytecode pointer
      if (save_flags) {
        __ movl(flags, rbcp);
        __ restore_bcp();
      }
    
      // compute return type
      __ shrl(flags, ConstantPoolCacheEntry::tos_state_shift);
      // Make sure we don't need to mask flags after the above shift
      ConstantPoolCacheEntry::verify_tos_state_shift();
      // load return address
      {
        const address table_addr = (address) Interpreter::invoke_return_entry_table_for(code);
        ExternalAddress table(table_addr);
        LP64_ONLY(__ lea(rscratch1, table));
        LP64_ONLY(__ movptr(flags, Address(rscratch1, flags, Address::times_ptr)));
        NOT_LP64(__ movptr(flags, ArrayAddress(table, Address(noreg, flags, Address::times_ptr))));
      }
    }
    
    void TemplateTable::load_invoke_cp_cache_entry(int byte_no,
                                                   Register method,
                                                   Register itable_index,
                                                   Register flags,
                                                   bool is_invokevirtual,
                                                   bool is_invokevfinal, /*unused*/
                                                   bool is_invokedynamic) {
      // setup registers
      const Register cache = rcx;
      const Register index = rdx;
      assert_different_registers(method, flags);
      assert_different_registers(method, cache, index);
      assert_different_registers(itable_index, flags);
      assert_different_registers(itable_index, cache, index);
      // determine constant pool cache field offsets
      assert(is_invokevirtual == (byte_no == f2_byte), "is_invokevirtual flag redundant");
      const int method_offset = in_bytes(
        ConstantPoolCache::base_offset() +
          ((byte_no == f2_byte)
           ? ConstantPoolCacheEntry::f2_offset()
           : ConstantPoolCacheEntry::f1_offset()));
      const int flags_offset = in_bytes(ConstantPoolCache::base_offset() +
                                        ConstantPoolCacheEntry::flags_offset());
      // access constant pool cache fields
      const int index_offset = in_bytes(ConstantPoolCache::base_offset() +
                                        ConstantPoolCacheEntry::f2_offset());
    
      size_t index_size = (is_invokedynamic ? sizeof(u4) : sizeof(u2));
      resolve_cache_and_index(byte_no, cache, index, index_size);
        __ movptr(method, Address(cache, index, Address::times_ptr, method_offset));
    
      if (itable_index != noreg) {
        // pick up itable or appendix index from f2 also:
        __ movptr(itable_index, Address(cache, index, Address::times_ptr, index_offset));
      }
      __ movl(flags, Address(cache, index, Address::times_ptr, flags_offset));
    }
    
    void TemplateTable::resolve_cache_and_index(int byte_no,
                                                Register Rcache,
                                                Register index,
                                                size_t index_size) {
      const Register temp = rbx;
      assert_different_registers(Rcache, index, temp);
    
      Label resolved;
    
      Bytecodes::Code code = bytecode();
      switch (code) {
      case Bytecodes::_nofast_getfield: code = Bytecodes::_getfield; break;
      case Bytecodes::_nofast_putfield: code = Bytecodes::_putfield; break;
      default: break;
      }
    
      assert(byte_no == f1_byte || byte_no == f2_byte, "byte_no out of range");
      __ get_cache_and_index_and_bytecode_at_bcp(Rcache, index, temp, byte_no, 1, index_size);
      __ cmpl(temp, code);  // have we resolved this bytecode?
      __ jcc(Assembler::equal, resolved);
    
      // resolve first time through
      address entry = CAST_FROM_FN_PTR(address, InterpreterRuntime::resolve_from_cache);
      __ movl(temp, code);
      __ call_VM(noreg, entry, temp);
      // Update registers with resolved info
      __ get_cache_and_index_at_bcp(Rcache, index, 1, index_size);
      __ bind(resolved);
    }
    
    void TemplateTable::resolve_cache_and_index(int byte_no,
                                                Register Rcache,
                                                Register index,
                                                size_t index_size) {
      const Register temp = rbx;
      assert_different_registers(Rcache, index, temp);
    
      Label resolved;
    
      Bytecodes::Code code = bytecode();
      switch (code) {
      case Bytecodes::_nofast_getfield: code = Bytecodes::_getfield; break;
      case Bytecodes::_nofast_putfield: code = Bytecodes::_putfield; break;
      default: break;
      }
    
      assert(byte_no == f1_byte || byte_no == f2_byte, "byte_no out of range");
      __ get_cache_and_index_and_bytecode_at_bcp(Rcache, index, temp, byte_no, 1, index_size);
      __ cmpl(temp, code);  // have we resolved this bytecode?
      __ jcc(Assembler::equal, resolved);
    
      // resolve first time through
      address entry = CAST_FROM_FN_PTR(address, InterpreterRuntime::resolve_from_cache);
      __ movl(temp, code);
      __ call_VM(noreg, entry, temp);
      // Update registers with resolved info
      __ get_cache_and_index_at_bcp(Rcache, index, 1, index_size);
      __ bind(resolved);
    }
    

    TemplateTable::invokedynamic由两步组成,第一步prepare_invoke将要调用的方法放到rbx,然后jump_from_interpreted跳到这个rbx方法入口。所以invokedynamic的所有故事主要发生在prepare_invoke。

    prepare_invoke一步步会调用到InterpreterRuntime::resolve_from_cache,注意,从prepare_invoke到resolve_from_cache都是模板解释器的JIT代码,到resolve_from_cache就是VM代码了。resolve_from_cache是几个invoke*方法共同的入口:

    IRT_ENTRY(void, InterpreterRuntime::resolve_from_cache(JavaThread* thread, Bytecodes::Code bytecode)) {
      switch (bytecode) {
      case Bytecodes::_getstatic:
      case Bytecodes::_putstatic:
      case Bytecodes::_getfield:
      case Bytecodes::_putfield:
        resolve_get_put(thread, bytecode);
        break;
      case Bytecodes::_invokevirtual:
      case Bytecodes::_invokespecial:
      case Bytecodes::_invokestatic:
      case Bytecodes::_invokeinterface:
        resolve_invoke(thread, bytecode);
        break;
      case Bytecodes::_invokehandle:
        resolve_invokehandle(thread);
        break;
      case Bytecodes::_invokedynamic:
        resolve_invokedynamic(thread);
        break;
      default:
        fatal("unexpected bytecode: %s", Bytecodes::name(bytecode));
        break;
      }
    }
    IRT_END
    

    这里主要关注InterpreterRuntime::resolve_invokedynamic。它会一步步走下去,最终走到LinkResolver::resolve_invokedynamic:

    void LinkResolver::resolve_invokedynamic(CallInfo& result, const constantPoolHandle& pool, int index, TRAPS) {
      Symbol* method_name       = pool->name_ref_at(index);
      Symbol* method_signature  = pool->signature_ref_at(index);
      Klass* current_klass = pool->pool_holder();
    
      // Resolve the bootstrap specifier (BSM + optional arguments).
      Handle bootstrap_specifier;
      // Check if CallSite has been bound already:
      ConstantPoolCacheEntry* cpce = pool->invokedynamic_cp_cache_entry_at(index);
      int pool_index = cpce->constant_pool_index();
    
      if (cpce->is_f1_null()) {
        if (cpce->indy_resolution_failed()) {
          ConstantPool::throw_resolution_error(pool,
                                               ResolutionErrorTable::encode_cpcache_index(index),
                                               CHECK);
        }
    
        // The initial step in Call Site Specifier Resolution is to resolve the symbolic
        // reference to a method handle which will be the bootstrap method for a dynamic
        // call site.  If resolution for the java.lang.invoke.MethodHandle for the bootstrap
        // method fails, then a MethodHandleInError is stored at the corresponding bootstrap
        // method's CP index for the CONSTANT_MethodHandle_info.  So, there is no need to
        // set the indy_rf flag since any subsequent invokedynamic instruction which shares
        // this bootstrap method will encounter the resolution of MethodHandleInError.
        oop bsm_info = pool->resolve_bootstrap_specifier_at(pool_index, THREAD);
        Exceptions::wrap_dynamic_exception(CHECK);
        assert(bsm_info != NULL, "");
        // FIXME: Cache this once per BootstrapMethods entry, not once per CONSTANT_InvokeDynamic.
        bootstrap_specifier = Handle(THREAD, bsm_info);
      }
      if (!cpce->is_f1_null()) {
        methodHandle method(     THREAD, cpce->f1_as_method());
        Handle       appendix(   THREAD, cpce->appendix_if_resolved(pool));
        Handle       method_type(THREAD, cpce->method_type_if_resolved(pool));
        result.set_handle(method, appendix, method_type, THREAD);
        Exceptions::wrap_dynamic_exception(CHECK);
        return;
      }
    
      if (TraceMethodHandles) {
        ResourceMark rm(THREAD);
        tty->print_cr("resolve_invokedynamic #%d %s %s in %s",
                      ConstantPool::decode_invokedynamic_index(index),
                      method_name->as_C_string(), method_signature->as_C_string(),
                      current_klass->name()->as_C_string());
        tty->print("  BSM info: "); bootstrap_specifier->print();
      }
    
      resolve_dynamic_call(result, pool_index, bootstrap_specifier, method_name,
                           method_signature, current_klass, THREAD);
      if (HAS_PENDING_EXCEPTION && PENDING_EXCEPTION->is_a(SystemDictionary::LinkageError_klass())) {
        int encoded_index = ResolutionErrorTable::encode_cpcache_index(index);
        bool recorded_res_status = cpce->save_and_throw_indy_exc(pool, pool_index,
                                                                 encoded_index,
                                                                 pool()->tag_at(pool_index),
                                                                 CHECK);
        if (!recorded_res_status) {
          // Another thread got here just before we did.  So, either use the method
          // that it resolved or throw the LinkageError exception that it threw.
          if (!cpce->is_f1_null()) {
            methodHandle method(     THREAD, cpce->f1_as_method());
            Handle       appendix(   THREAD, cpce->appendix_if_resolved(pool));
            Handle       method_type(THREAD, cpce->method_type_if_resolved(pool));
            result.set_handle(method, appendix, method_type, THREAD);
            Exceptions::wrap_dynamic_exception(CHECK);
          } else {
            assert(cpce->indy_resolution_failed(), "Resolution failure flag not set");
            ConstantPool::throw_resolution_error(pool, encoded_index, CHECK);
          }
          return;
        }
        assert(cpce->indy_resolution_failed(), "Resolution failure flag wasn't set");
      }
    }
    

    LinkResolver::resolve_invokedynamic用pool->resolve_bootstrap_specifier_at找到bsm(BootStrap Method)方法,然后resolve_dynamic_call根据bsm方法找到调用方法。其中找bsm会call到Java代码MethodHandleNatives.linkMethodHandleConstant,调用bsm也会call到Java代码的MethodHandleNatives.linkCallSite。

    换句话说,prepare_invoke最终调用MethodHandleNatives.linkMethodHandleConstant找bsm方法,然后调用MethodHandleNatives.linkCallSite根据bsm方法找到最终调用的方法,然后jump_from_interpreted调用这个方法。

    Java层

    java层就好很多了,假设我们的代码是

    public class MHTest {
        public static void main(String[] args) {
            Runnable r = ()->{
                System.out.println("fk");
            };
            r.run();
        }
    }
    

    脑补一下,jvm走到lambda处会调用模板解释器解释执行invokedynamic,这一步对应TemplateTable::invokedynamic。,然后前面提到这个方法先用linkMethodHandleConstant找bsm方法:
    image.png
    然后它返回一个bsm方法(MethodHandle),传递给linkCallSite作为bootstrapMethodObj,根据它找到要调用的方法:
    image.png
    与例子结合,lambda的bsm方法是LambdaMetafactory.metafacotry,linkCallSite会调用metafactory生成callsite:
    image.png
    然后返回metafactory找到的方法,由解释器调用它。再精简一点,invokedynamic本质上是增加了一个bsm方法作为中间层,用户可以写自己的bsm方法逻辑来确定调用哪个方法,以前的查找调用方法逻辑都是写死在虚拟机中,现在多了一个中间层。

    LambdaMetafactory

    public static CallSite metafactory(MethodHandles.Lookup caller,
                                           String invokedName,
                                           MethodType invokedType,
                                           MethodType samMethodType,
                                           MethodHandle implMethod,
                                           MethodType instantiatedMethodType)
                throws LambdaConversionException {
            AbstractValidatingLambdaMetafactory mf;
            mf = new InnerClassLambdaMetafactory(caller, invokedType,
                                                 invokedName, samMethodType,
                                                 implMethod, instantiatedMethodType,
                                                 false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
            mf.validateMetafactoryArgs();
            return mf.buildCallSite();
        }
    

    实际上它调用的是InnerClassLambdaMetafactory.buildCallSite

     CallSite buildCallSite() throws LambdaConversionException {
            // 1. 创建生成的类对象
            final Class<?> innerClass = spinInnerClass();
            if (invokedType.parameterCount() == 0) {
                // 2. 用反射获取构造函数
                final Constructor<?>[] ctrs = AccessController.doPrivileged(
                        new PrivilegedAction<Constructor<?>[]>() {
                    @Override
                    public Constructor<?>[] run() {
                        Constructor<?>[] ctrs = innerClass.getDeclaredConstructors();
                        if (ctrs.length == 1) {
                            // The lambda implementing inner class constructor is private, set
                            // it accessible (by us) before creating the constant sole instance
                            ctrs[0].setAccessible(true);
                        }
                        return ctrs;
                    }
                        });
                if (ctrs.length != 1) {
                    throw new LambdaConversionException("Expected one lambda constructor for "
                            + innerClass.getCanonicalName() + ", got " + ctrs.length);
                }
    
                try {
                    // 3. 创建实例 
                    Object inst = ctrs[0].newInstance();
                    // 4. 根据实例和samBase(接口类型)生成MethodHandle
                    // 5. 生成ConstantCallSite
                    return new ConstantCallSite(MethodHandles.constant(samBase, inst));
                }
                catch (ReflectiveOperationException e) {
                    throw new LambdaConversionException("Exception instantiating lambda object", e);
                }
            } else {
                try {
                    UNSAFE.ensureClassInitialized(innerClass);
                    return new ConstantCallSite(
                            MethodHandles.Lookup.IMPL_LOOKUP
                                 .findStatic(innerClass, NAME_FACTORY, invokedType));
                }
                catch (ReflectiveOperationException e) {
                    throw new LambdaConversionException("Exception finding constructor", e);
                }
            }
        }
    

    它生成一个.class文件,虚拟机默认不会输出,需要下面设置VM option-Djdk.internal.lambda.dumpProxyClasses=.,Dump出虚拟机生成的类我得到的是:

    import java.lang.invoke.LambdaForm.Hidden;
    
    // $FF: synthetic class
    final class MethodReference$$Lambda$1 implements Encode {
        private MethodReference$$Lambda$1() {
        }
    
        @Hidden
        public void encode(Derive var1) {
            ((Base)var1).encrypt();
        }
    }
    

    该类实现了传来的接口函数(动态类生成,spring警告)。回到buildCallSite()源码,它使用MethodHandles.constant(samBase, inst)创建MethdHandle,放到CallSite里面,完成整个LambdaMetafactory的工作。 MethodHandles.constant(samBase, inst)相当于一个总是返回inst的方法。

    总结是最重要的,说到底,invokedynamic的思想是增加一层的思想,计算机科学中的银弹。它通过BSM第一次执行时生成callsite,后面调用callsite,而正常流程是直接调用callsite。

  • 相关阅读:
    Account group in ERP and its mapping relationship with CRM partner group
    错误消息Number not in interval XXX when downloading
    错误消息Form of address 0001 not designated for organization
    Algorithm类介绍(core)
    梯度下降与随机梯度下降
    反思
    绘图: matplotlib核心剖析
    ORB
    SIFT
    Harris角点
  • 原文地址:https://www.cnblogs.com/kelthuzadx/p/15726563.html
Copyright © 2011-2022 走看看