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);
}