c++v8

Generated code performing with simulator mode and without in V8


The generated code can be executed in different ways. The GeneratedCode class has a Call operation, in which the generated code can be executed in 3 different routes.

Source:

// Use this class either as {GeneratedCode<ret, arg1, arg2>} or
// {GeneratedCode<ret(arg1, arg2)>} (see specialization below).
template <typename Return, typename... Args>
class GeneratedCode {
 public:
  using Signature = Return(Args...);

  static GeneratedCode FromAddress(Isolate* isolate, Address addr) {
    return GeneratedCode(isolate, reinterpret_cast<Signature*>(addr));
  }

  static GeneratedCode FromBuffer(Isolate* isolate, uint8_t* buffer) {
    return GeneratedCode(isolate, reinterpret_cast<Signature*>(buffer));
  }

  static GeneratedCode FromCode(Isolate* isolate, Tagged<Code> code) {
    return FromAddress(isolate, code->instruction_start());
  }

#ifdef USE_SIMULATOR
  // Defined in simulator-base.h.
  Return Call(Args... args) {
// Starboard is a platform abstraction interface that also include Windows
// platforms like UWP.
#if defined(V8_TARGET_OS_WIN) && !defined(V8_OS_WIN) && \
    !defined(V8_OS_STARBOARD) && !defined(V8_TARGET_ARCH_ARM)
    FATAL(
        "Generated code execution not possible during cross-compilation."
        "Also, generic C function calls are not implemented on 32-bit arm "
        "yet.");
#endif  // defined(V8_TARGET_OS_WIN) && !defined(V8_OS_WIN) &&
        // !defined(V8_OS_STARBOARD) && !defined(V8_TARGET_ARCH_ARM)
    return Simulator::current(isolate_)->template Call<Return>(
        reinterpret_cast<Address>(fn_ptr_), args...);
  }
#else

  DISABLE_CFI_ICALL Return Call(Args... args) {
    // When running without a simulator we call the entry directly.
// Starboard is a platform abstraction interface that also include Windows
// platforms like UWP.
#if defined(V8_TARGET_OS_WIN) && !defined(V8_OS_WIN) && \
    !defined(V8_OS_STARBOARD)
    FATAL("Generated code execution not possible during cross-compilation.");
#endif  // defined(V8_TARGET_OS_WIN) && !defined(V8_OS_WIN)
#if ABI_USES_FUNCTION_DESCRIPTORS
#if V8_OS_ZOS
    // z/OS ABI requires function descriptors (FD). Artificially create a pseudo
    // FD to ensure correct dispatch to generated code.
    void* function_desc[2] = {0, reinterpret_cast<void*>(fn_ptr_)};
    asm volatile(" stg 5,%0 " : "=m"(function_desc[0])::"r5");
    Signature* fn = reinterpret_cast<Signature*>(function_desc);
    return fn(args...);
#else
    // AIX ABI requires function descriptors (FD).  Artificially create a pseudo
    // FD to ensure correct dispatch to generated code.  The 'volatile'
    // declaration is required to avoid the compiler from not observing the
    // alias of the pseudo FD to the function pointer, and hence, optimizing the
    // pseudo FD declaration/initialization away.
    volatile Address function_desc[] = {reinterpret_cast<Address>(fn_ptr_), 0,
                                        0};
    Signature* fn = reinterpret_cast<Signature*>(function_desc);
    return fn(args...);
#endif  // V8_OS_ZOS
#else
    return fn_ptr_(args...);
#endif  // ABI_USES_FUNCTION_DESCRIPTORS
  }
#endif  // USE_SIMULATOR

 private:
  friend class GeneratedCode<Return(Args...)>;
  Isolate* isolate_;
  Signature* fn_ptr_;
  GeneratedCode(Isolate* isolate, Signature* fn_ptr)
      : isolate_(isolate), fn_ptr_(fn_ptr) {}
};

The first way is through the simulator:

return Simulator::current(isolate_)->template Call<Return>(reinterpret_cast<Address>(fn_ptr_), args...);

Second without simulator:

return fn(args...);

And the third one without a simulator:

return fn_ptr_(args...);

The question is: Why are there so many execution routes, how do they differ and when are they used?


Solution

  • Why are there so many execution routes, how do they differ and when are they used?

    V8's simulators are for cross-platform testing.

    The other code paths are for platform-specific requirements, as the comments and #ifdefs indicate.


    This is not a good question to ask on Stackoverflow

    I agree, and I'm sorry to have encouraged it by having given answers to a handful of similar questions that preceded it. So this will be my last answer in this series of "just out of curiosity, why are these particular implementation details being done this way?" questions.

    I get that studying open-source code is fascinating, and you are certainly encouraged to continue to do it. Realistically, you should expect that you'll have to figure out most things on your own; that's why I've been trying to help you help yourself by pointing you at codesearch. If you prefer talking to people, perhaps consider forming a working group with a friend or two and figure out confusing bits together?

    Sometimes a bit of informed guesswork also goes a long way. When you see #ifdef V8_OS_ZOS, you don't really need anyone to tell you that this code is used on z/OS, do you?