Open Bug 2088588 Opened 6 days ago Updated 6 days ago

[riscv64] Call and jump to JIT code with auipc and jalr

Categories

(Core :: JavaScript Engine: JIT, task)

RISCV64
Unspecified
task

Tracking

()

ASSIGNED
Tracking Status
firefox161 --- affected

People

(Reporter: andrew, Assigned: andrew)

References

(Blocks 1 open bug)

Details

Attachments

(2 external files)

On riscv64, calls and jumps to other JIT code (MacroAssembler::call(JitCode*), branch(JitCode*), toggledCall, and call/jump to a TrampolinePtr) load the 48-bit target with the six-instruction patchable li_ptr sequence and then jalr or jr, seven instructions per site.

All JIT code is allocated in the process executable memory region, which is smaller than 2 GiB, so an auipc + jalr pair reaches any JIT target from any JIT code. The offset is only known once the code is copied to its final address, so the pair can be recorded and patched in Assembler::executableCopy, as x64 and arm64 do for their pc-relative branches. Calls to C++ functions are outside that region and keep the absolute sequence. Assembler::TraceJumpRelocations has to decode the new pair, and a toggled call becomes auipc, addi and jalr or nop so that disabling it keeps the low bits of the offset.

On JetStream 3 under the riscv64 simulator there are about 490,000 such sites (367,000 calls and 123,000 jumps). Replacing them shrinks Baseline code by 6.1% and Ion code by 5.1%.

The change relies on the auipc ra; jalr ra call sequence, which the riscv64 simulator currently mis-executes: its jalr writes rd before reading rs1, so jalr ra, imm(ra) jumps relative to the return address. That needs fixing in the simulator as part of this bug.

Found with riscvlint, a peephole checker for RISC-V code, run over the JIT code generated during JetStream 3.

Calls and jumps to JitCode and to trampolines loaded the 48-bit target
with the six-instruction patchable li_ptr sequence before the jalr or
jr. All JIT code lives in the executable memory region, which is smaller
than 2 GiB, so an auipc and jalr pair reaches any target from any JIT
code. The offset is only known once the code is copied to its final
address, so record each pair and patch it in executableCopy, as x64 and
arm64 do for their pc-relative branches. call(ImmPtr) and jump(ImmPtr)
take the pc-relative form when the target is in the executable memory
region, which covers call(TrampolinePtr) and jump(TrampolinePtr); calls
to C++ keep the absolute sequence.

Jump relocations now decode the pair. A toggled call becomes auipc, addi
and jalr or nop, so that disabling the call keeps the low bits of the
offset.

On JetStream 3 under the riscv64 simulator this replaces about 490,000
li_ptr sequences, 367,000 before a call and 123,000 before a jump, and
shrinks Baseline code by 6.1% and Ion code by 5.1%.

Found with riscvlint.

Assignee: nobody → andrew
Status: NEW → ASSIGNED

The simulator wrote the link register before it read the base register,
so jalr ra, imm(ra), the second half of the auipc ra; jalr ra call
sequence, jumped relative to the return address rather than to the
auipc result. The JIT does not emit that form yet.

Blocks: sm-riscv64
Hardware: Unspecified → RISCV64
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: