1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#![allow(clippy::fn_to_numeric_cast)]
use super::{TranspilerBackend, CONTEXT};
use crate::{
DebugFn, EcallHandler, ExternFn, JitFunction, JitMemory, RiscOperand, RiscRegister,
RiscvTranspiler,
};
use dynasmrt::{
dynasm,
x64::{Assembler, Rq},
DynasmApi,
};
use hashbrown::HashMap;
use std::io;
impl RiscvTranspiler for TranspilerBackend {
fn new(
program_size: usize,
memory_size: usize,
max_trace_size: u64,
pc_start: u64,
pc_base: u64,
clk_bump: u64,
) -> Result<Self, std::io::Error> {
if pc_start < pc_base {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"pc_start must be greater than pc_base",
));
}
let mut this = Self {
inner: Assembler::new()?,
jump_table: Vec::with_capacity(program_size),
memory_size,
has_instructions: false,
pc_base,
pc_start,
// Register a dummy ecall handler.
ecall_handler: super::ecallk as _,
control_flow_instruction_inserted: false,
instruction_started: false,
branch_generated: false,
clk_bump,
max_trace_size,
may_early_exit: false,
pc_current: pc_base,
reg_values: HashMap::new(),
labels: HashMap::new(),
program_size,
};
// Handle calling conventions and save anything were gonna clobber.
this.prologue();
Ok(this)
}
fn register_ecall_handler(&mut self, handler: EcallHandler) {
self.ecall_handler = handler;
}
fn start_instr(&mut self) {
// We dont want to compile without a single jumpdest, otherwise we will sigsegv.
self.has_instructions = true;
// If the instruction has already started, then we are in a bad state.
if self.instruction_started {
panic!("start_instr called without calling end_instr");
}
// Push the offset of the jumpdest for this instruction.
let offset = self.inner.offset();
self.jump_table.push(offset.0);
// We are now "within" an instruction.
self.instruction_started = true;
}
fn end_instr(&mut self) {
if self.control_flow_instruction_inserted {
// Control flow instructions might have multiple branch targets,
// as a result, we let them call `end_branch` directly. Here we
// only handle bookkeeping tasks for control flow instructions.
if !self.branch_generated {
panic!("No branch has been generated, maybe a JIT mistake?");
}
// When basic block ends, reset all transpile-time register values, as they
// would be incorrect for the next basic block.
self.reg_values.clear();
} else {
// Add the base amount of cycles for the instruction.
self.bump_clk();
// We only bump / update PC when:
// * A control flow instruction is executing.
// * Before ecall and unimp calls extern functions.
// * When trace is complete, execution suspends.
// For normal, sequential operations, there is no need to bump PC.
// We dont have a control flow insruction so we need to bump the pc first.
if self.may_early_exit {
self.exit_if_trace_exceeds(self.max_trace_size);
}
}
// Transpiling is done for current instruction
self.pc_current += 4;
self.may_early_exit = false;
self.control_flow_instruction_inserted = false;
self.instruction_started = false;
self.branch_generated = false;
}
fn finalize<M: JitMemory>(mut self) -> io::Result<JitFunction<M>> {
self.epilogue();
let code = self.inner.finalize().expect("failed to finalize x86 backend");
debug_assert!(code.size() > 0, "Got empty x86 code buffer");
JitFunction::new(code, self.jump_table, self.memory_size, self.pc_start)
}
fn call_extern_fn(&mut self, fn_ptr: ExternFn) {
// Load the JitContext pointer into the argument register.
dynasm! {
self;
.arch x64;
mov rdi, Rq(CONTEXT)
};
self.call_extern_fn_raw(fn_ptr as _);
}
fn inspect_register(&mut self, reg: RiscRegister, handler: DebugFn) {
// Load into the argument register for the function call.
self.emit_risc_operand_load(RiscOperand::Register(reg), Rq::RDI as u8);
// Call the handler with the value of the register.
self.call_extern_fn_raw(handler as _);
}
fn inspect_immediate(&mut self, imm: u64, handler: DebugFn) {
dynasm! {
self;
.arch x64;
mov rdi, imm as i32
}
self.call_extern_fn_raw(handler as _);
}
}