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。

  • 相关阅读:
    z-index优先级小结
    如何消除img间的默认间隙
    text-align和vertical-align
    HTTP
    HTTP
    HTTP
    HTTP
    ES6标准入门
    ES6标准入门
    ES6标准入门
  • 原文地址:https://www.cnblogs.com/kelthuzadx/p/15726563.html
Copyright © 2011-2022 走看看