void compileFunction()

in runtime/interpreter-gen-x64.cpp [3200:3357]


void compileFunction(Thread* thread, const Function& function) {
  EVENT(COMPILE_FUNCTION);
  HandleScope scope(thread);
  MutableBytes code(&scope, function.rewrittenBytecode());
  word num_opcodes = rewrittenBytecodeLength(code);
  JitEnv environ(&scope, thread, function, num_opcodes);
  JitEnv* env = &environ;

  // TODO(T89721395): Deduplicate these assignments with emitInterpreter.
  RegisterAssignment function_entry_assignment[] = {
      {&env->pc, kPCReg},
      {&env->oparg, kOpargReg},
      {&env->frame, kFrameReg},
      {&env->thread, kThreadReg},
      {&env->handlers_base, kHandlersBaseReg},
      {&env->callable, kCallableReg},
  };
  env->function_entry_assignment = function_entry_assignment;

  RegisterAssignment handler_assignment[] = {
      {&env->bytecode, kBCReg},   {&env->pc, kPCReg},
      {&env->oparg, kOpargReg},   {&env->frame, kFrameReg},
      {&env->thread, kThreadReg}, {&env->handlers_base, kHandlersBaseReg},
  };
  env->handler_assignment = handler_assignment;

  // Similar to handler_assignment but no PC or oparg.
  RegisterAssignment jit_handler_assignment[] = {
      {&env->bytecode, kBCReg},
      {&env->frame, kFrameReg},
      {&env->thread, kThreadReg},
      {&env->handlers_base, kHandlersBaseReg},
  };
  env->jit_handler_assignment = jit_handler_assignment;

  RegisterAssignment call_interpreted_slow_path_assignment[] = {
      {&env->pc, kPCReg},       {&env->callable, kCallableReg},
      {&env->frame, kFrameReg}, {&env->thread, kThreadReg},
      {&env->oparg, kOpargReg}, {&env->handlers_base, kHandlersBaseReg},
  };
  env->call_interpreted_slow_path_assignment =
      call_interpreted_slow_path_assignment;

  RegisterAssignment call_trampoline_assignment[] = {
      {&env->pc, kPCReg},       {&env->callable, kCallableReg},
      {&env->frame, kFrameReg}, {&env->thread, kThreadReg},
      {&env->oparg, kOpargReg}, {&env->handlers_base, kHandlersBaseReg},
  };
  env->call_trampoline_assignment = call_trampoline_assignment;

  RegisterAssignment return_handler_assignment[] = {
      {&env->thread, kThreadReg},
      {&env->handlers_base, kHandlersBaseReg},
  };
  env->return_handler_assignment = return_handler_assignment;

  RegisterAssignment do_return_assignment[] = {
      {&env->return_value, kReturnRegs[0]},
  };
  env->do_return_assignment = do_return_assignment;

  RegisterAssignment deopt_assignment[] = {
      {&env->thread, kThreadReg},
      {&env->frame, kFrameReg},
      {&env->pc, kPCReg},
  };
  env->deopt_assignment = deopt_assignment;

  DCHECK(function.isInterpreted(), "function must be interpreted");
  DCHECK(function.hasSimpleCall(),
         "function must have a simple calling convention");

  // JIT entrypoints are in entryAsm and are called with the function entry
  // assignment.
  env->register_state.resetTo(function_entry_assignment);

  COMMENT("Function <%s>",
          unique_c_ptr<char>(Str::cast(function.qualname()).toCStr()).get());
  COMMENT("Prologue");
  // Check that we received the right number of arguments.
  Label call_interpreted_slow_path;
  __ cmpl(env->oparg, Immediate(function.argcount()));
  env->register_state.check(env->call_interpreted_slow_path_assignment);
  __ jcc(NOT_EQUAL, &call_interpreted_slow_path, Assembler::kFarJump);

  // Open a new frame.
  env->register_state.assign(&env->return_mode, kReturnModeReg);
  __ xorl(env->return_mode, env->return_mode);
  emitPushCallFrame(env, /*stack_overflow=*/&call_interpreted_slow_path);

  for (word i = 0; i < num_opcodes;) {
    word current_pc = i * kCodeUnitSize;
    BytecodeOp op = nextBytecodeOp(code, &i);
    if (!isSupportedInJIT(op.bc)) {
      UNIMPLEMENTED("unsupported jit opcode %s", kBytecodeNames[op.bc]);
    }
    env->current_op = op.bc;
    env->setCurrentOp(op);
    env->setVirtualPC(i * kCodeUnitSize);
    env->register_state.resetTo(env->jit_handler_assignment);
    COMMENT("%s %d (%d)", kBytecodeNames[op.bc], op.arg, op.cache);
    __ bind(env->opcodeAtByteOffset(current_pc));
    switch (op.bc) {
#define BC(name, _0, _1)                                                       \
  case name: {                                                                 \
    jitEmitHandler<name>(env);                                                 \
    break;                                                                     \
  }
      FOREACH_BYTECODE(BC)
#undef BC
    }
  }

  if (!env->unwind_handler.isUnused()) {
    COMMENT("Unwind");
    __ bind(&env->unwind_handler);
    // TODO(T91715866): Unwind.
    __ ud2();
  }

  COMMENT("Call interpreted slow path");
  __ bind(&call_interpreted_slow_path);
  // TODO(T89721522): Have one canonical slow path chunk of code that all JIT
  // functions jump to, instead of one per function.
  env->register_state.resetTo(env->call_interpreted_slow_path_assignment);
  emitCallInterpretedSlowPath(env);

  if (!env->deopt_handler.isUnused()) {
    COMMENT("Deopt");
    // Handle deoptimization by resetting the entrypoint to an assembly
    // entrypoint and then jumping back into the interpreter.
    __ bind(&env->deopt_handler);
    env->register_state.resetTo(env->deopt_assignment);
    __ movq(kArgRegs[0], env->thread);
    emitSaveInterpreterState(env, kVMPC | kVMStack | kVMFrame);
    emitCall<void (*)(Thread*)>(env, deoptimizeCurrentFunction);
    // Jump back into the interpreter.
    emitRestoreInterpreterState(env, kAllState);
    emitNextOpcodeImpl(env);
  }

  COMMENT("<END>");
  __ ud2();

  // Finalize the code.
  word jit_size = Utils::roundUp(env->as.codeSize(), kBitsPerByte);
  uword address;
  bool allocated =
      thread->runtime()->allocateForMachineCode(jit_size, &address);
  CHECK(allocated, "could not allocate memory for JIT function");
  byte* jit_code = reinterpret_cast<byte*>(address);
  env->as.finalizeInstructions(MemoryRegion(jit_code, jit_size));
  // TODO(T83754516): Mark memory as RX.

  // Replace the entrypoint.
  function.setEntryAsm(jit_code);
  function.setFlags(function.flags() | Function::Flags::kCompiled);
}