@aptre/v86 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/LICENSE +22 -0
  2. package/LICENSE.MIT +22 -0
  3. package/Readme.md +237 -0
  4. package/dist/v86.browser.js +26666 -0
  5. package/dist/v86.browser.js.map +7 -0
  6. package/dist/v86.js +26632 -0
  7. package/dist/v86.js.map +7 -0
  8. package/gen/generate_analyzer.ts +512 -0
  9. package/gen/generate_interpreter.ts +522 -0
  10. package/gen/generate_jit.ts +624 -0
  11. package/gen/rust_ast.ts +107 -0
  12. package/gen/util.ts +35 -0
  13. package/gen/x86_table.ts +1836 -0
  14. package/lib/9p.ts +1547 -0
  15. package/lib/filesystem.ts +1879 -0
  16. package/lib/marshall.ts +168 -0
  17. package/lib/softfloat/softfloat.c +32501 -0
  18. package/lib/zstd/zstddeclib.c +13520 -0
  19. package/package.json +75 -0
  20. package/src/acpi.ts +267 -0
  21. package/src/browser/dummy_screen.ts +106 -0
  22. package/src/browser/fake_network.ts +1771 -0
  23. package/src/browser/fetch_network.ts +361 -0
  24. package/src/browser/filestorage.ts +124 -0
  25. package/src/browser/inbrowser_network.ts +57 -0
  26. package/src/browser/keyboard.ts +564 -0
  27. package/src/browser/main.ts +3415 -0
  28. package/src/browser/mouse.ts +255 -0
  29. package/src/browser/network.ts +142 -0
  30. package/src/browser/print_stats.ts +336 -0
  31. package/src/browser/screen.ts +978 -0
  32. package/src/browser/serial.ts +316 -0
  33. package/src/browser/speaker.ts +1223 -0
  34. package/src/browser/starter.ts +1688 -0
  35. package/src/browser/wisp_network.ts +332 -0
  36. package/src/browser/worker_bus.ts +64 -0
  37. package/src/buffer.ts +652 -0
  38. package/src/bus.ts +78 -0
  39. package/src/const.ts +128 -0
  40. package/src/cpu.ts +2891 -0
  41. package/src/dma.ts +474 -0
  42. package/src/elf.ts +251 -0
  43. package/src/floppy.ts +1778 -0
  44. package/src/ide.ts +3455 -0
  45. package/src/io.ts +504 -0
  46. package/src/iso9660.ts +317 -0
  47. package/src/kernel.ts +250 -0
  48. package/src/lib.ts +645 -0
  49. package/src/log.ts +149 -0
  50. package/src/main.ts +199 -0
  51. package/src/ne2k.ts +1589 -0
  52. package/src/pci.ts +815 -0
  53. package/src/pit.ts +406 -0
  54. package/src/ps2.ts +820 -0
  55. package/src/rtc.ts +537 -0
  56. package/src/rust/analysis.rs +101 -0
  57. package/src/rust/codegen.rs +2660 -0
  58. package/src/rust/config.rs +3 -0
  59. package/src/rust/control_flow.rs +425 -0
  60. package/src/rust/cpu/apic.rs +658 -0
  61. package/src/rust/cpu/arith.rs +1207 -0
  62. package/src/rust/cpu/call_indirect.rs +2 -0
  63. package/src/rust/cpu/cpu.rs +4501 -0
  64. package/src/rust/cpu/fpu.rs +923 -0
  65. package/src/rust/cpu/global_pointers.rs +112 -0
  66. package/src/rust/cpu/instructions.rs +2486 -0
  67. package/src/rust/cpu/instructions_0f.rs +5261 -0
  68. package/src/rust/cpu/ioapic.rs +316 -0
  69. package/src/rust/cpu/memory.rs +351 -0
  70. package/src/rust/cpu/misc_instr.rs +613 -0
  71. package/src/rust/cpu/mod.rs +16 -0
  72. package/src/rust/cpu/modrm.rs +133 -0
  73. package/src/rust/cpu/pic.rs +402 -0
  74. package/src/rust/cpu/sse_instr.rs +361 -0
  75. package/src/rust/cpu/string.rs +701 -0
  76. package/src/rust/cpu/vga.rs +175 -0
  77. package/src/rust/cpu_context.rs +69 -0
  78. package/src/rust/dbg.rs +98 -0
  79. package/src/rust/gen/analyzer.rs +3807 -0
  80. package/src/rust/gen/analyzer0f.rs +3992 -0
  81. package/src/rust/gen/interpreter.rs +4447 -0
  82. package/src/rust/gen/interpreter0f.rs +5404 -0
  83. package/src/rust/gen/jit.rs +5080 -0
  84. package/src/rust/gen/jit0f.rs +5547 -0
  85. package/src/rust/gen/mod.rs +14 -0
  86. package/src/rust/jit.rs +2443 -0
  87. package/src/rust/jit_instructions.rs +7881 -0
  88. package/src/rust/js_api.rs +6 -0
  89. package/src/rust/leb.rs +46 -0
  90. package/src/rust/lib.rs +29 -0
  91. package/src/rust/modrm.rs +330 -0
  92. package/src/rust/opstats.rs +249 -0
  93. package/src/rust/page.rs +15 -0
  94. package/src/rust/paging.rs +25 -0
  95. package/src/rust/prefix.rs +15 -0
  96. package/src/rust/profiler.rs +155 -0
  97. package/src/rust/regs.rs +38 -0
  98. package/src/rust/softfloat.rs +286 -0
  99. package/src/rust/state_flags.rs +27 -0
  100. package/src/rust/wasmgen/mod.rs +2 -0
  101. package/src/rust/wasmgen/wasm_builder.rs +1047 -0
  102. package/src/rust/wasmgen/wasm_opcodes.rs +221 -0
  103. package/src/rust/zstd.rs +105 -0
  104. package/src/sb16.ts +1928 -0
  105. package/src/state.ts +359 -0
  106. package/src/uart.ts +472 -0
  107. package/src/vga.ts +2791 -0
  108. package/src/virtio.ts +1756 -0
  109. package/src/virtio_balloon.ts +273 -0
  110. package/src/virtio_console.ts +372 -0
  111. package/src/virtio_net.ts +326 -0
@@ -0,0 +1,2443 @@
1
+ use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
2
+ use std::iter::FromIterator;
3
+ use std::mem::{self, MaybeUninit};
4
+ use std::ops::{Deref, DerefMut};
5
+ use std::ptr::NonNull;
6
+ use std::sync::{Mutex, MutexGuard};
7
+
8
+ use crate::analysis;
9
+ use crate::analysis::AnalysisType;
10
+ use crate::codegen;
11
+ use crate::control_flow;
12
+ use crate::control_flow::WasmStructure;
13
+ use crate::cpu::cpu;
14
+ use crate::cpu::global_pointers;
15
+ use crate::cpu::memory;
16
+ use crate::cpu_context::CpuContext;
17
+ use crate::jit_instructions;
18
+ use crate::opstats;
19
+ use crate::page::Page;
20
+ use crate::profiler;
21
+ use crate::profiler::stat;
22
+ use crate::state_flags::CachedStateFlags;
23
+ use crate::wasmgen::wasm_builder::{Label, WasmBuilder, WasmLocal};
24
+
25
+ #[derive(Copy, Clone, Eq, Hash, PartialEq)]
26
+ #[repr(transparent)]
27
+ pub struct WasmTableIndex(u16);
28
+ impl WasmTableIndex {
29
+ pub fn to_u16(self) -> u16 { self.0 }
30
+ }
31
+
32
+ mod unsafe_jit {
33
+ use super::{CachedStateFlags, WasmTableIndex};
34
+
35
+ extern "C" {
36
+ pub fn codegen_finalize(
37
+ wasm_table_index: WasmTableIndex,
38
+ phys_addr: u32,
39
+ state_flags: CachedStateFlags,
40
+ ptr: u32,
41
+ len: u32,
42
+ );
43
+ pub fn jit_clear_func(wasm_table_index: WasmTableIndex);
44
+ }
45
+ }
46
+
47
+ fn codegen_finalize(
48
+ wasm_table_index: WasmTableIndex,
49
+ phys_addr: u32,
50
+ state_flags: CachedStateFlags,
51
+ ptr: u32,
52
+ len: u32,
53
+ ) {
54
+ unsafe { unsafe_jit::codegen_finalize(wasm_table_index, phys_addr, state_flags, ptr, len) }
55
+ }
56
+
57
+ pub fn jit_clear_func(wasm_table_index: WasmTableIndex) {
58
+ unsafe { unsafe_jit::jit_clear_func(wasm_table_index) }
59
+ }
60
+
61
+ static mut JIT_DISABLED: bool = false;
62
+
63
+ // Maximum number of pages per wasm module. Necessary for the following reasons:
64
+ // - There is an upper limit on the size of a single function in wasm (currently ~7MB in all browsers)
65
+ // See https://github.com/WebAssembly/design/issues/1138
66
+ // - v8 poorly handles large br_table elements and OOMs on modules much smaller than the above limit
67
+ // See https://bugs.chromium.org/p/v8/issues/detail?id=9697 and https://bugs.chromium.org/p/v8/issues/detail?id=9141
68
+ // Will hopefully be fixed in the near future by generating direct control flow
69
+ static mut MAX_PAGES: u32 = 3;
70
+
71
+ static mut JIT_USE_LOOP_SAFETY: bool = true;
72
+
73
+ pub static mut MAX_EXTRA_BASIC_BLOCKS: u32 = 250;
74
+
75
+ pub const JIT_THRESHOLD: u32 = 200 * 1000;
76
+
77
+ // less branches will generate if-else, more will generate brtable
78
+ pub const BRTABLE_CUTOFF: usize = 10;
79
+
80
+ // needs to be synced to const.js
81
+ pub const WASM_TABLE_SIZE: u32 = 900;
82
+
83
+ pub const CHECK_JIT_STATE_INVARIANTS: bool = false;
84
+
85
+ const MAX_INSTRUCTION_LENGTH: u32 = 16;
86
+
87
+ static JIT_STATE: Mutex<MaybeUninit<JitState>> = Mutex::new(MaybeUninit::uninit());
88
+ fn get_jit_state() -> JitStateRef { JitStateRef(JIT_STATE.try_lock().unwrap()) }
89
+
90
+ struct JitStateRef(MutexGuard<'static, MaybeUninit<JitState>>);
91
+
92
+ impl Deref for JitStateRef {
93
+ type Target = JitState;
94
+ fn deref(&self) -> &Self::Target { unsafe { self.0.assume_init_ref() } }
95
+ }
96
+ impl DerefMut for JitStateRef {
97
+ fn deref_mut(&mut self) -> &mut Self::Target { unsafe { self.0.assume_init_mut() } }
98
+ }
99
+
100
+ #[no_mangle]
101
+ pub fn rust_init() {
102
+ dbg_assert!(std::mem::size_of::<[Option<NonNull<cpu::Code>>; 0x100000]>() == 0x100000 * 4);
103
+
104
+ let _ = JIT_STATE
105
+ .try_lock()
106
+ .unwrap()
107
+ .write(JitState::create_and_initialise());
108
+
109
+ use std::panic;
110
+
111
+ panic::set_hook(Box::new(|panic_info| {
112
+ console_log!("{}", panic_info.to_string());
113
+ }));
114
+ }
115
+
116
+ struct PageInfo {
117
+ wasm_table_index: WasmTableIndex,
118
+ hidden_wasm_table_indices: Vec<WasmTableIndex>,
119
+ entry_points: Vec<(u16, u16)>,
120
+ state_flags: CachedStateFlags,
121
+ }
122
+
123
+ enum CompilingPageState {
124
+ Compiling { pages: HashMap<Page, PageInfo> },
125
+ CompilingWritten,
126
+ }
127
+
128
+ struct JitState {
129
+ wasm_builder: WasmBuilder,
130
+
131
+ // as an alternative to HashSet, we could use a bitmap of 4096 bits here
132
+ // (faster, but uses much more memory)
133
+ // or a compressed bitmap (likely faster)
134
+ // or HashSet<u32> rather than nested
135
+ entry_points: HashMap<Page, (u32, HashSet<u16>)>,
136
+ pages: HashMap<Page, PageInfo>,
137
+ wasm_table_index_free_list: Vec<WasmTableIndex>,
138
+ compiling: Option<(WasmTableIndex, CompilingPageState)>,
139
+ }
140
+
141
+ fn check_jit_state_invariants(ctx: &mut JitState) {
142
+ if !CHECK_JIT_STATE_INVARIANTS {
143
+ return;
144
+ }
145
+
146
+ match &ctx.compiling {
147
+ Some((_, CompilingPageState::Compiling { pages })) => {
148
+ dbg_assert!(pages.keys().all(|page| ctx.entry_points.contains_key(page)));
149
+ },
150
+ _ => {},
151
+ }
152
+
153
+ let free: HashSet<WasmTableIndex> =
154
+ HashSet::from_iter(ctx.wasm_table_index_free_list.iter().cloned());
155
+ let used = HashSet::from_iter(ctx.pages.values().map(|info| info.wasm_table_index));
156
+ let compiling = HashSet::from_iter(ctx.compiling.as_ref().map(|&(index, _)| index));
157
+ dbg_assert!(free.intersection(&used).next().is_none());
158
+ dbg_assert!(used.intersection(&compiling).next().is_none());
159
+ dbg_assert!(free.len() + used.len() + compiling.len() == (WASM_TABLE_SIZE - 1) as usize);
160
+
161
+ match &ctx.compiling {
162
+ Some((_, CompilingPageState::Compiling { pages })) => {
163
+ dbg_assert!(pages.keys().all(|page| ctx.entry_points.contains_key(page)));
164
+ },
165
+ _ => {},
166
+ }
167
+
168
+ for i in 0..unsafe { cpu::valid_tlb_entries_count } {
169
+ let page = unsafe { cpu::valid_tlb_entries[i as usize] };
170
+ let entry = unsafe { cpu::tlb_data[page as usize] };
171
+ if 0 != entry {
172
+ let tlb_physical_page = Page::of_u32(
173
+ (entry as u32 >> 12 ^ page as u32) - (unsafe { memory::mem8 } as u32 >> 12),
174
+ );
175
+ let w = match unsafe { cpu::tlb_code[page as usize] } {
176
+ None => None,
177
+ Some(c) => unsafe { Some(c.as_ref().wasm_table_index) },
178
+ };
179
+ let tlb_has_code = entry & cpu::TLB_HAS_CODE == cpu::TLB_HAS_CODE;
180
+ let infos = ctx.pages.get(&tlb_physical_page);
181
+ let entry_points = ctx.entry_points.get(&tlb_physical_page);
182
+ dbg_assert!(tlb_has_code || !w.is_some());
183
+ dbg_assert!(tlb_has_code || !infos.is_some());
184
+ dbg_assert!(tlb_has_code || !entry_points.is_some());
185
+ //dbg_assert!((w.is_some() || page.is_some() || entry_points.is_some()) == tlb_has_code); // XXX: check this
186
+ }
187
+ }
188
+ }
189
+
190
+ impl JitState {
191
+ pub fn create_and_initialise() -> JitState {
192
+ // don't assign 0 (XXX: Check)
193
+ let wasm_table_indices = (1..=(WASM_TABLE_SIZE - 1) as u16).map(|x| WasmTableIndex(x));
194
+
195
+ JitState {
196
+ wasm_builder: WasmBuilder::new(),
197
+
198
+ entry_points: HashMap::new(),
199
+ pages: HashMap::new(),
200
+
201
+ wasm_table_index_free_list: Vec::from_iter(wasm_table_indices),
202
+ compiling: None,
203
+ }
204
+ }
205
+ }
206
+
207
+ #[derive(PartialEq, Eq)]
208
+ pub enum BasicBlockType {
209
+ Normal {
210
+ next_block_addr: Option<u32>,
211
+ jump_offset: i32,
212
+ jump_offset_is_32: bool,
213
+ },
214
+ ConditionalJump {
215
+ next_block_addr: Option<u32>,
216
+ next_block_branch_taken_addr: Option<u32>,
217
+ condition: u8,
218
+ jump_offset: i32,
219
+ jump_offset_is_32: bool,
220
+ },
221
+ // Set eip to an absolute value (ret, jmp r/m, call r/m)
222
+ AbsoluteEip,
223
+ Exit,
224
+ }
225
+
226
+ pub struct BasicBlock {
227
+ pub addr: u32,
228
+ pub virt_addr: i32,
229
+ pub last_instruction_addr: u32,
230
+ pub end_addr: u32,
231
+ pub is_entry_block: bool,
232
+ pub ty: BasicBlockType,
233
+ pub has_sti: bool,
234
+ pub number_of_instructions: u32,
235
+ }
236
+
237
+ #[derive(Copy, Clone, PartialEq)]
238
+ pub struct CachedCode {
239
+ pub wasm_table_index: WasmTableIndex,
240
+ pub initial_state: u16,
241
+ }
242
+
243
+ impl CachedCode {
244
+ pub const NONE: CachedCode = CachedCode {
245
+ wasm_table_index: WasmTableIndex(0),
246
+ initial_state: 0,
247
+ };
248
+ }
249
+
250
+ #[derive(PartialEq)]
251
+ pub enum InstructionOperandDest {
252
+ WasmLocal(WasmLocal),
253
+ Other,
254
+ }
255
+ #[derive(PartialEq)]
256
+ pub enum InstructionOperand {
257
+ WasmLocal(WasmLocal),
258
+ Immediate(i32),
259
+ Other,
260
+ }
261
+ impl InstructionOperand {
262
+ pub fn is_zero(&self) -> bool {
263
+ match self {
264
+ InstructionOperand::Immediate(0) => true,
265
+ _ => false,
266
+ }
267
+ }
268
+ }
269
+ impl Into<InstructionOperand> for InstructionOperandDest {
270
+ fn into(self: InstructionOperandDest) -> InstructionOperand {
271
+ match self {
272
+ InstructionOperandDest::WasmLocal(l) => InstructionOperand::WasmLocal(l),
273
+ InstructionOperandDest::Other => InstructionOperand::Other,
274
+ }
275
+ }
276
+ }
277
+ pub enum Instruction {
278
+ Cmp {
279
+ dest: InstructionOperandDest,
280
+ source: InstructionOperand,
281
+ opsize: i32,
282
+ },
283
+ Sub {
284
+ dest: InstructionOperandDest,
285
+ source: InstructionOperand,
286
+ opsize: i32,
287
+ is_dec: bool,
288
+ },
289
+ Add {
290
+ dest: InstructionOperandDest,
291
+ source: InstructionOperand,
292
+ opsize: i32,
293
+ is_inc: bool,
294
+ },
295
+ AdcSbb {
296
+ dest: InstructionOperandDest,
297
+ #[allow(dead_code)]
298
+ source: InstructionOperand,
299
+ opsize: i32,
300
+ },
301
+ NonZeroShift {
302
+ dest: InstructionOperandDest,
303
+ opsize: i32,
304
+ },
305
+ Bitwise {
306
+ dest: InstructionOperandDest,
307
+ opsize: i32,
308
+ },
309
+ Other,
310
+ }
311
+
312
+ pub struct JitContext<'a> {
313
+ pub cpu: &'a mut CpuContext,
314
+ pub builder: &'a mut WasmBuilder,
315
+ pub register_locals: &'a mut Vec<WasmLocal>,
316
+ pub start_of_current_instruction: u32,
317
+ pub exit_with_fault_label: Label,
318
+ pub exit_label: Label,
319
+ pub current_instruction: Instruction,
320
+ pub previous_instruction: Instruction,
321
+ pub instruction_counter: WasmLocal,
322
+ }
323
+ impl<'a> JitContext<'a> {
324
+ pub fn reg(&self, i: u32) -> WasmLocal {
325
+ match self.register_locals.get(i as usize) {
326
+ Some(x) => x.unsafe_clone(),
327
+ None => {
328
+ dbg_assert!(false);
329
+ unsafe { std::hint::unreachable_unchecked() }
330
+ },
331
+ }
332
+ }
333
+ }
334
+
335
+ pub const JIT_INSTR_BLOCK_BOUNDARY_FLAG: u32 = 1 << 0;
336
+
337
+ pub fn is_near_end_of_page(address: u32) -> bool {
338
+ address & 0xFFF >= 0x1000 - MAX_INSTRUCTION_LENGTH
339
+ }
340
+
341
+ pub fn jit_find_cache_entry(phys_address: u32, state_flags: CachedStateFlags) -> CachedCode {
342
+ // TODO: dedup with jit_find_cache_entry_in_page?
343
+ // NOTE: This is currently only used for invariant/missed-entry-point checking
344
+ let ctx = get_jit_state();
345
+
346
+ match ctx.pages.get(&Page::page_of(phys_address)) {
347
+ Some(PageInfo {
348
+ wasm_table_index,
349
+ state_flags: s,
350
+ entry_points,
351
+ hidden_wasm_table_indices: _,
352
+ }) => {
353
+ if *s == state_flags {
354
+ let page_offset = phys_address as u16 & 0xFFF;
355
+ if let Some(&(_, initial_state)) =
356
+ entry_points.iter().find(|(p, _)| p == &page_offset)
357
+ {
358
+ return CachedCode {
359
+ wasm_table_index: *wasm_table_index,
360
+ initial_state,
361
+ };
362
+ }
363
+ }
364
+ },
365
+ None => {},
366
+ }
367
+
368
+ return CachedCode::NONE;
369
+ }
370
+
371
+ #[no_mangle]
372
+ pub fn jit_find_cache_entry_in_page(
373
+ virt_address: u32,
374
+ wasm_table_index: WasmTableIndex,
375
+ state_flags: u32,
376
+ ) -> i32 {
377
+ // TODO: generate code for this
378
+ profiler::stat_increment(stat::INDIRECT_JUMP);
379
+
380
+ let state_flags = CachedStateFlags::of_u32(state_flags);
381
+
382
+ unsafe {
383
+ match cpu::tlb_code[(virt_address >> 12) as usize] {
384
+ None => {},
385
+ Some(c) => {
386
+ let c = c.as_ref();
387
+ if state_flags == c.state_flags && wasm_table_index == c.wasm_table_index {
388
+ let state = c.state_table[virt_address as usize & 0xFFF];
389
+ if state != u16::MAX {
390
+ return state.into();
391
+ }
392
+ }
393
+ },
394
+ }
395
+ }
396
+
397
+ profiler::stat_increment(stat::INDIRECT_JUMP_NO_ENTRY);
398
+
399
+ return -1;
400
+ }
401
+
402
+ fn jit_find_basic_blocks(
403
+ ctx: &mut JitState,
404
+ entry_points: HashSet<i32>,
405
+ cpu: CpuContext,
406
+ ) -> Vec<BasicBlock> {
407
+ fn follow_jump(
408
+ virt_target: i32,
409
+ ctx: &mut JitState,
410
+ pages: &mut HashSet<Page>,
411
+ page_blacklist: &mut HashSet<Page>,
412
+ max_pages: u32,
413
+ marked_as_entry: &mut HashSet<i32>,
414
+ to_visit_stack: &mut Vec<i32>,
415
+ ) -> Option<u32> {
416
+ if is_near_end_of_page(virt_target as u32) {
417
+ return None;
418
+ }
419
+ let phys_target = match cpu::translate_address_read_no_side_effects(virt_target) {
420
+ Err(()) => {
421
+ dbg_log!("Not analysing {:x} (page not mapped)", virt_target);
422
+ return None;
423
+ },
424
+ Ok(t) => t,
425
+ };
426
+
427
+ let phys_page = Page::page_of(phys_target);
428
+
429
+ if !pages.contains(&phys_page) && pages.len() as u32 == max_pages
430
+ || page_blacklist.contains(&phys_page)
431
+ {
432
+ return None;
433
+ }
434
+
435
+ if !pages.contains(&phys_page) {
436
+ // page seen for the first time, handle entry points
437
+ if let Some((hotness, entry_points)) = ctx.entry_points.get_mut(&phys_page) {
438
+ let existing_entry_points = match ctx.pages.get(&phys_page) {
439
+ Some(PageInfo { entry_points, .. }) => {
440
+ HashSet::from_iter(entry_points.iter().map(|x| x.0))
441
+ },
442
+ None => HashSet::new(),
443
+ };
444
+
445
+ if entry_points
446
+ .iter()
447
+ .all(|entry_point| existing_entry_points.contains(entry_point))
448
+ {
449
+ page_blacklist.insert(phys_page);
450
+ return None;
451
+ }
452
+
453
+ // XXX: Remove this paragraph
454
+ //let old_length = entry_points.len();
455
+ //entry_points.extend(existing_entry_points);
456
+ //dbg_assert!(
457
+ // entry_points.union(&existing_entry_points).count() == entry_points.len()
458
+ //);
459
+
460
+ *hotness = 0;
461
+
462
+ for &addr_low in entry_points.iter() {
463
+ let addr = virt_target & !0xFFF | addr_low as i32;
464
+ to_visit_stack.push(addr);
465
+ marked_as_entry.insert(addr);
466
+ }
467
+ }
468
+ else {
469
+ // no entry points: ignore this page?
470
+ page_blacklist.insert(phys_page);
471
+ return None;
472
+ }
473
+
474
+ pages.insert(phys_page);
475
+ dbg_assert!(pages.len() as u32 <= max_pages);
476
+ }
477
+
478
+ to_visit_stack.push(virt_target);
479
+ Some(phys_target)
480
+ }
481
+
482
+ let mut to_visit_stack: Vec<i32> = Vec::new();
483
+ let mut marked_as_entry: HashSet<i32> = HashSet::new();
484
+ let mut basic_blocks: BTreeMap<u32, BasicBlock> = BTreeMap::new();
485
+ let mut pages: HashSet<Page> = HashSet::new();
486
+ let mut page_blacklist = HashSet::new();
487
+
488
+ // 16-bit doesn't work correctly, most likely due to instruction pointer wrap-around
489
+ let max_pages = if cpu.state_flags.is_32() { unsafe { MAX_PAGES } } else { 1 };
490
+
491
+ for virt_addr in entry_points {
492
+ let ok = follow_jump(
493
+ virt_addr,
494
+ ctx,
495
+ &mut pages,
496
+ &mut page_blacklist,
497
+ max_pages,
498
+ &mut marked_as_entry,
499
+ &mut to_visit_stack,
500
+ );
501
+ dbg_assert!(ok.is_some());
502
+ dbg_assert!(marked_as_entry.contains(&virt_addr));
503
+ }
504
+
505
+ while let Some(to_visit) = to_visit_stack.pop() {
506
+ let phys_addr = match cpu::translate_address_read_no_side_effects(to_visit) {
507
+ Err(()) => {
508
+ dbg_log!("Not analysing {:x} (page not mapped)", to_visit);
509
+ continue;
510
+ },
511
+ Ok(phys_addr) => phys_addr,
512
+ };
513
+
514
+ if basic_blocks.contains_key(&phys_addr) {
515
+ continue;
516
+ }
517
+
518
+ if is_near_end_of_page(phys_addr) {
519
+ // Empty basic block, don't insert
520
+ profiler::stat_increment(stat::COMPILE_CUT_OFF_AT_END_OF_PAGE);
521
+ continue;
522
+ }
523
+
524
+ let mut current_address = phys_addr;
525
+ let mut current_block = BasicBlock {
526
+ addr: current_address,
527
+ virt_addr: to_visit,
528
+ last_instruction_addr: 0,
529
+ end_addr: 0,
530
+ ty: BasicBlockType::Exit,
531
+ is_entry_block: false,
532
+ has_sti: false,
533
+ number_of_instructions: 0,
534
+ };
535
+ loop {
536
+ let addr_before_instruction = current_address;
537
+ let mut cpu = &mut CpuContext {
538
+ eip: current_address,
539
+ ..cpu
540
+ };
541
+ let analysis = analysis::analyze_step(&mut cpu);
542
+ let has_next_instruction = !analysis.no_next_instruction;
543
+ current_address = cpu.eip;
544
+
545
+ dbg_assert!(Page::page_of(current_address) == Page::page_of(addr_before_instruction));
546
+ let current_virt_addr = to_visit & !0xFFF | current_address as i32 & 0xFFF;
547
+
548
+ if analysis.ty == AnalysisType::STI && is_near_end_of_page(current_address) {
549
+ // cut off before the STI so that it is handled by interpreted mode
550
+ profiler::stat_increment(stat::COMPILE_CUT_OFF_AT_END_OF_PAGE);
551
+ break;
552
+ }
553
+
554
+ current_block.number_of_instructions += 1;
555
+ current_block.last_instruction_addr = addr_before_instruction;
556
+ current_block.end_addr = current_address;
557
+
558
+ match analysis.ty {
559
+ AnalysisType::Normal | AnalysisType::STI => {
560
+ dbg_assert!(has_next_instruction);
561
+ dbg_assert!(!analysis.absolute_jump);
562
+
563
+ if current_block.has_sti {
564
+ // Convert next instruction after STI (i.e., the current instruction) into block boundary
565
+
566
+ marked_as_entry.insert(current_virt_addr);
567
+ to_visit_stack.push(current_virt_addr);
568
+
569
+ break;
570
+ }
571
+
572
+ if analysis.ty == AnalysisType::STI {
573
+ dbg_assert!(
574
+ !is_near_end_of_page(current_address),
575
+ "should be handled above"
576
+ );
577
+
578
+ current_block.has_sti = true;
579
+ }
580
+ else {
581
+ // Only split non-STI blocks (one instruction needs to run after STI before
582
+ // handle_irqs may be called)
583
+
584
+ if basic_blocks.contains_key(&current_address) {
585
+ dbg_assert!(!is_near_end_of_page(current_address));
586
+ current_block.ty = BasicBlockType::Normal {
587
+ next_block_addr: Some(current_address),
588
+ jump_offset: 0,
589
+ jump_offset_is_32: true,
590
+ };
591
+ break;
592
+ }
593
+ }
594
+ },
595
+ AnalysisType::Jump {
596
+ offset,
597
+ is_32,
598
+ condition: Some(condition),
599
+ } => {
600
+ dbg_assert!(!analysis.absolute_jump);
601
+ // conditional jump: continue at next and continue at jump target
602
+
603
+ let jump_target = if is_32 {
604
+ current_virt_addr + offset
605
+ }
606
+ else {
607
+ cpu.cs_offset as i32
608
+ + (current_virt_addr - cpu.cs_offset as i32 + offset & 0xFFFF)
609
+ };
610
+
611
+ dbg_assert!(has_next_instruction);
612
+ to_visit_stack.push(current_virt_addr);
613
+
614
+ let next_block_addr = if is_near_end_of_page(current_address) {
615
+ None
616
+ }
617
+ else {
618
+ Some(current_address)
619
+ };
620
+
621
+ current_block.ty = BasicBlockType::ConditionalJump {
622
+ next_block_addr,
623
+ next_block_branch_taken_addr: follow_jump(
624
+ jump_target,
625
+ ctx,
626
+ &mut pages,
627
+ &mut page_blacklist,
628
+ max_pages,
629
+ &mut marked_as_entry,
630
+ &mut to_visit_stack,
631
+ ),
632
+ condition,
633
+ jump_offset: offset,
634
+ jump_offset_is_32: is_32,
635
+ };
636
+
637
+ break;
638
+ },
639
+ AnalysisType::Jump {
640
+ offset,
641
+ is_32,
642
+ condition: None,
643
+ } => {
644
+ dbg_assert!(!analysis.absolute_jump);
645
+ // non-conditional jump: continue at jump target
646
+
647
+ let jump_target = if is_32 {
648
+ current_virt_addr + offset
649
+ }
650
+ else {
651
+ cpu.cs_offset as i32
652
+ + (current_virt_addr - cpu.cs_offset as i32 + offset & 0xFFFF)
653
+ };
654
+
655
+ if has_next_instruction {
656
+ // Execution will eventually come back to the next instruction (CALL)
657
+ marked_as_entry.insert(current_virt_addr);
658
+ to_visit_stack.push(current_virt_addr);
659
+ }
660
+
661
+ current_block.ty = BasicBlockType::Normal {
662
+ next_block_addr: follow_jump(
663
+ jump_target,
664
+ ctx,
665
+ &mut pages,
666
+ &mut page_blacklist,
667
+ max_pages,
668
+ &mut marked_as_entry,
669
+ &mut to_visit_stack,
670
+ ),
671
+ jump_offset: offset,
672
+ jump_offset_is_32: is_32,
673
+ };
674
+
675
+ break;
676
+ },
677
+ AnalysisType::BlockBoundary => {
678
+ // a block boundary but not a jump, get out
679
+
680
+ if has_next_instruction {
681
+ // block boundary, but execution will eventually come back
682
+ // to the next instruction. Create a new basic block
683
+ // starting at the next instruction and register it as an
684
+ // entry point
685
+ marked_as_entry.insert(current_virt_addr);
686
+ to_visit_stack.push(current_virt_addr);
687
+ }
688
+
689
+ if analysis.absolute_jump {
690
+ current_block.ty = BasicBlockType::AbsoluteEip;
691
+ }
692
+
693
+ break;
694
+ },
695
+ }
696
+
697
+ if is_near_end_of_page(current_address) {
698
+ profiler::stat_increment(stat::COMPILE_CUT_OFF_AT_END_OF_PAGE);
699
+ break;
700
+ }
701
+ }
702
+
703
+ if current_block.number_of_instructions == 0 {
704
+ // Empty basic block, don't insert (only happens when STI is found near end of page)
705
+ continue;
706
+ }
707
+
708
+ let previous_block = basic_blocks
709
+ .range(..current_block.addr)
710
+ .next_back()
711
+ .filter(|(_, previous_block)| !previous_block.has_sti)
712
+ .map(|(_, previous_block)| previous_block);
713
+
714
+ if let Some(previous_block) = previous_block {
715
+ if current_block.addr < previous_block.end_addr {
716
+ // If this block overlaps with the previous block, re-analyze the previous block
717
+ to_visit_stack.push(previous_block.virt_addr);
718
+
719
+ let addr = previous_block.addr;
720
+ let old_block = basic_blocks.remove(&addr);
721
+ dbg_assert!(old_block.is_some());
722
+
723
+ // Note that this does not ensure the invariant that two consecutive blocks don't
724
+ // overlay. For that, we also need to check the following block.
725
+ }
726
+ }
727
+
728
+ dbg_assert!(current_block.addr < current_block.end_addr);
729
+ dbg_assert!(current_block.addr <= current_block.last_instruction_addr);
730
+ dbg_assert!(current_block.last_instruction_addr < current_block.end_addr);
731
+
732
+ basic_blocks.insert(current_block.addr, current_block);
733
+ }
734
+
735
+ dbg_assert!(pages.len() as u32 <= max_pages);
736
+
737
+ for block in basic_blocks.values_mut() {
738
+ if marked_as_entry.contains(&block.virt_addr) {
739
+ block.is_entry_block = true;
740
+ }
741
+ }
742
+
743
+ let basic_blocks: Vec<BasicBlock> = basic_blocks.into_iter().map(|(_, block)| block).collect();
744
+
745
+ for i in 0..basic_blocks.len() - 1 {
746
+ let next_block_addr = basic_blocks[i + 1].addr;
747
+ let next_block_end_addr = basic_blocks[i + 1].end_addr;
748
+ let next_block_is_entry = basic_blocks[i + 1].is_entry_block;
749
+ let block = &basic_blocks[i];
750
+ dbg_assert!(block.addr < next_block_addr);
751
+ if next_block_addr < block.end_addr {
752
+ dbg_log!(
753
+ "Overlapping first=[from={:x} to={:x} is_entry={}] second=[from={:x} to={:x} is_entry={}]",
754
+ block.addr,
755
+ block.end_addr,
756
+ block.is_entry_block as u8,
757
+ next_block_addr,
758
+ next_block_end_addr,
759
+ next_block_is_entry as u8
760
+ );
761
+ }
762
+ }
763
+
764
+ basic_blocks
765
+ }
766
+
767
+ #[no_mangle]
768
+ #[cfg(debug_assertions)]
769
+ pub fn jit_force_generate_unsafe(virt_addr: i32) {
770
+ dbg_assert!(
771
+ !is_near_end_of_page(virt_addr as u32),
772
+ "cannot force compile near end of page"
773
+ );
774
+ jit_increase_hotness_and_maybe_compile(
775
+ virt_addr,
776
+ cpu::translate_address_read(virt_addr).unwrap(),
777
+ cpu::get_seg_cs() as u32,
778
+ cpu::get_state_flags(),
779
+ JIT_THRESHOLD,
780
+ );
781
+ dbg_assert!(get_jit_state().compiling.is_some());
782
+ }
783
+
784
+ #[inline(never)]
785
+ fn jit_analyze_and_generate(
786
+ ctx: &mut JitState,
787
+ virt_entry_point: i32,
788
+ phys_entry_point: u32,
789
+ cs_offset: u32,
790
+ state_flags: CachedStateFlags,
791
+ ) {
792
+ let page = Page::page_of(phys_entry_point);
793
+
794
+ dbg_assert!(ctx.compiling.is_none());
795
+
796
+ let (_, entry_points) = match ctx.entry_points.get(&page) {
797
+ None => return,
798
+ Some(entry_points) => entry_points,
799
+ };
800
+
801
+ let existing_entry_points = match ctx.pages.get(&page) {
802
+ Some(PageInfo { entry_points, .. }) => HashSet::from_iter(entry_points.iter().map(|x| x.0)),
803
+ None => HashSet::new(),
804
+ };
805
+
806
+ if entry_points
807
+ .iter()
808
+ .all(|entry_point| existing_entry_points.contains(entry_point))
809
+ {
810
+ profiler::stat_increment(stat::COMPILE_SKIPPED_NO_NEW_ENTRY_POINTS);
811
+ return;
812
+ }
813
+
814
+ // XXX: check and remove
815
+ //let old_length = entry_points.len();
816
+ //entry_points.extend(existing_entry_points);
817
+ //dbg_log!(
818
+ // "{} + {} = {}",
819
+ // entry_points.len(),
820
+ // existing_entry_points.len(),
821
+ // entry_points.union(&existing_entry_points).count()
822
+ //);
823
+ //dbg_assert!(entry_points.union(&existing_entry_points).count() == entry_points.len());
824
+
825
+ profiler::stat_increment(stat::COMPILE);
826
+
827
+ let cpu = CpuContext {
828
+ eip: 0,
829
+ prefixes: 0,
830
+ cs_offset,
831
+ state_flags,
832
+ };
833
+
834
+ dbg_assert!(
835
+ cpu::translate_address_read_no_side_effects(virt_entry_point).unwrap() == phys_entry_point
836
+ );
837
+ let virt_page = Page::page_of(virt_entry_point as u32);
838
+ let entry_points: HashSet<i32> = entry_points
839
+ .iter()
840
+ .map(|e| virt_page.to_address() as i32 | *e as i32)
841
+ .collect();
842
+ let basic_blocks = jit_find_basic_blocks(ctx, entry_points, cpu.clone());
843
+
844
+ let mut pages = HashSet::new();
845
+
846
+ for b in basic_blocks.iter() {
847
+ // Remove this assertion once page-crossing jit is enabled
848
+ dbg_assert!(Page::page_of(b.addr) == Page::page_of(b.end_addr));
849
+ pages.insert(Page::page_of(b.addr));
850
+ }
851
+
852
+ let print = false;
853
+
854
+ for b in basic_blocks.iter() {
855
+ if !print {
856
+ break;
857
+ }
858
+ let last_instruction_opcode = memory::read32s(b.last_instruction_addr);
859
+ let op = opstats::decode(last_instruction_opcode as u32);
860
+ dbg_log!(
861
+ "BB: 0x{:x} {}{:02x} {} {}",
862
+ b.addr,
863
+ if op.is_0f { "0f" } else { "" },
864
+ op.opcode,
865
+ if b.is_entry_block { "entry" } else { "noentry" },
866
+ match &b.ty {
867
+ BasicBlockType::ConditionalJump {
868
+ next_block_addr: Some(next_block_addr),
869
+ next_block_branch_taken_addr: Some(next_block_branch_taken_addr),
870
+ ..
871
+ } => format!(
872
+ "0x{:x} 0x{:x}",
873
+ next_block_addr, next_block_branch_taken_addr
874
+ ),
875
+ BasicBlockType::ConditionalJump {
876
+ next_block_addr: None,
877
+ next_block_branch_taken_addr: Some(next_block_branch_taken_addr),
878
+ ..
879
+ } => format!("0x{:x}", next_block_branch_taken_addr),
880
+ BasicBlockType::ConditionalJump {
881
+ next_block_addr: Some(next_block_addr),
882
+ next_block_branch_taken_addr: None,
883
+ ..
884
+ } => format!("0x{:x}", next_block_addr),
885
+ BasicBlockType::ConditionalJump {
886
+ next_block_addr: None,
887
+ next_block_branch_taken_addr: None,
888
+ ..
889
+ } => format!(""),
890
+ BasicBlockType::Normal {
891
+ next_block_addr: Some(next_block_addr),
892
+ ..
893
+ } => format!("0x{:x}", next_block_addr),
894
+ BasicBlockType::Normal {
895
+ next_block_addr: None,
896
+ ..
897
+ } => format!(""),
898
+ BasicBlockType::Exit => format!(""),
899
+ BasicBlockType::AbsoluteEip => format!(""),
900
+ }
901
+ );
902
+ }
903
+
904
+ let graph = control_flow::make_graph(&basic_blocks);
905
+ let mut structure = control_flow::loopify(&graph);
906
+
907
+ if print {
908
+ dbg_log!("before blockify:");
909
+ for group in &structure {
910
+ dbg_log!("=> Group");
911
+ group.print(0);
912
+ }
913
+ }
914
+
915
+ control_flow::blockify(&mut structure, &graph);
916
+
917
+ if cfg!(debug_assertions) {
918
+ control_flow::assert_invariants(&structure);
919
+ }
920
+
921
+ if print {
922
+ dbg_log!("after blockify:");
923
+ for group in &structure {
924
+ dbg_log!("=> Group");
925
+ group.print(0);
926
+ }
927
+ }
928
+
929
+ if ctx.wasm_table_index_free_list.is_empty() {
930
+ dbg_log!("wasm_table_index_free_list empty, clearing cache");
931
+
932
+ // When no free slots are available, delete all cached modules. We could increase the
933
+ // size of the table, but this way the initial size acts as an upper bound for the
934
+ // number of wasm modules that we generate, which we want anyway to avoid getting our
935
+ // tab killed by browsers due to memory constraints.
936
+ jit_clear_cache(ctx);
937
+
938
+ profiler::stat_increment(stat::INVALIDATE_ALL_MODULES_NO_FREE_WASM_INDICES);
939
+
940
+ dbg_log!(
941
+ "after jit_clear_cache: {} free",
942
+ ctx.wasm_table_index_free_list.len(),
943
+ );
944
+
945
+ // This assertion can fail if all entries are pending (not possible unless
946
+ // WASM_TABLE_SIZE is set very low)
947
+ dbg_assert!(!ctx.wasm_table_index_free_list.is_empty());
948
+ }
949
+
950
+ // allocate an index in the wasm table
951
+ let wasm_table_index = ctx
952
+ .wasm_table_index_free_list
953
+ .pop()
954
+ .expect("allocate wasm table index");
955
+ dbg_assert!(wasm_table_index != WasmTableIndex(0));
956
+
957
+ dbg_assert!(!pages.is_empty());
958
+ dbg_assert!(pages.len() <= unsafe { MAX_PAGES } as usize);
959
+
960
+ let basic_block_by_addr: HashMap<u32, BasicBlock> =
961
+ basic_blocks.into_iter().map(|b| (b.addr, b)).collect();
962
+
963
+ let entries = jit_generate_module(
964
+ structure,
965
+ &basic_block_by_addr,
966
+ cpu,
967
+ &mut ctx.wasm_builder,
968
+ wasm_table_index,
969
+ state_flags,
970
+ );
971
+ dbg_assert!(!entries.is_empty());
972
+
973
+ let mut page_info = HashMap::new();
974
+ for &(addr, state) in &entries {
975
+ let code = page_info
976
+ .entry(Page::page_of(addr))
977
+ .or_insert_with(|| PageInfo {
978
+ wasm_table_index,
979
+ state_flags,
980
+ entry_points: Vec::new(),
981
+ hidden_wasm_table_indices: Vec::new(),
982
+ });
983
+ code.entry_points.push((addr as u16 & 0xFFF, state));
984
+ }
985
+
986
+ profiler::stat_increment_by(
987
+ stat::COMPILE_WASM_TOTAL_BYTES,
988
+ ctx.wasm_builder.get_output_len() as u64,
989
+ );
990
+ profiler::stat_increment_by(stat::COMPILE_PAGE, pages.len() as u64);
991
+
992
+ for &p in &pages {
993
+ ctx.entry_points
994
+ .entry(p)
995
+ .or_insert_with(|| (0, HashSet::new()));
996
+ }
997
+
998
+ cpu::tlb_set_has_code_multiple(&pages, true);
999
+
1000
+ dbg_assert!(ctx.compiling.is_none());
1001
+ ctx.compiling = Some((
1002
+ wasm_table_index,
1003
+ CompilingPageState::Compiling { pages: page_info },
1004
+ ));
1005
+
1006
+ let phys_addr = page.to_address();
1007
+
1008
+ // will call codegen_finalize_finished asynchronously when finished
1009
+ codegen_finalize(
1010
+ wasm_table_index,
1011
+ phys_addr,
1012
+ state_flags,
1013
+ ctx.wasm_builder.get_output_ptr() as u32,
1014
+ ctx.wasm_builder.get_output_len(),
1015
+ );
1016
+
1017
+ check_jit_state_invariants(ctx);
1018
+ }
1019
+
1020
+ #[no_mangle]
1021
+ pub fn codegen_finalize_finished(
1022
+ wasm_table_index: WasmTableIndex,
1023
+ phys_addr: u32,
1024
+ state_flags: CachedStateFlags,
1025
+ ) {
1026
+ let mut ctx = get_jit_state();
1027
+
1028
+ dbg_assert!(wasm_table_index != WasmTableIndex(0));
1029
+
1030
+ dbg_log!(
1031
+ "Finished compiling for page at {:x}",
1032
+ Page::page_of(phys_addr).to_address()
1033
+ );
1034
+
1035
+ let pages = match mem::replace(&mut ctx.compiling, None) {
1036
+ None => {
1037
+ dbg_assert!(false);
1038
+ return;
1039
+ },
1040
+ Some((in_progress_wasm_table_index, CompilingPageState::CompilingWritten)) => {
1041
+ dbg_assert!(wasm_table_index == in_progress_wasm_table_index);
1042
+
1043
+ profiler::stat_increment(stat::INVALIDATE_MODULE_WRITTEN_WHILE_COMPILED);
1044
+ free_wasm_table_index(&mut ctx, wasm_table_index);
1045
+ check_jit_state_invariants(&mut ctx);
1046
+ return;
1047
+ },
1048
+ Some((in_progress_wasm_table_index, CompilingPageState::Compiling { pages })) => {
1049
+ dbg_assert!(wasm_table_index == in_progress_wasm_table_index);
1050
+ dbg_assert!(!pages.is_empty());
1051
+ pages
1052
+ },
1053
+ };
1054
+
1055
+ for i in 0..unsafe { cpu::valid_tlb_entries_count } {
1056
+ let page = unsafe { cpu::valid_tlb_entries[i as usize] };
1057
+ let entry = unsafe { cpu::tlb_data[page as usize] };
1058
+ if 0 != entry {
1059
+ let tlb_physical_page = Page::of_u32(
1060
+ (entry as u32 >> 12 ^ page as u32) - (unsafe { memory::mem8 } as u32 >> 12),
1061
+ );
1062
+ if let Some(info) = pages.get(&tlb_physical_page) {
1063
+ set_tlb_code(
1064
+ Page::of_u32(page as u32),
1065
+ wasm_table_index,
1066
+ &info.entry_points,
1067
+ state_flags,
1068
+ );
1069
+ }
1070
+ }
1071
+ }
1072
+
1073
+ let mut check_for_unused_wasm_table_index = HashSet::new();
1074
+
1075
+ for (page, mut info) in pages {
1076
+ if let Some(old_entry) = ctx.pages.remove(&page) {
1077
+ info.hidden_wasm_table_indices
1078
+ .extend(old_entry.hidden_wasm_table_indices);
1079
+ info.hidden_wasm_table_indices
1080
+ .push(old_entry.wasm_table_index);
1081
+ check_for_unused_wasm_table_index.insert(old_entry.wasm_table_index);
1082
+ }
1083
+ ctx.pages.insert(page, info);
1084
+ }
1085
+
1086
+ let unused: Vec<&WasmTableIndex> = check_for_unused_wasm_table_index
1087
+ .iter()
1088
+ .filter(|&&i| ctx.pages.values().all(|page| page.wasm_table_index != i))
1089
+ .collect();
1090
+
1091
+ for &index in unused {
1092
+ for p in ctx.pages.values_mut() {
1093
+ p.hidden_wasm_table_indices.retain(|&w| w != index);
1094
+ }
1095
+
1096
+ dbg_log!("unused after overwrite {}", index.to_u16());
1097
+ profiler::stat_increment(stat::INVALIDATE_MODULE_UNUSED_AFTER_OVERWRITE);
1098
+ free_wasm_table_index(&mut ctx, index);
1099
+ }
1100
+
1101
+ check_jit_state_invariants(&mut ctx);
1102
+ }
1103
+
1104
+ pub fn update_tlb_code(virt_page: Page, phys_page: Page) {
1105
+ let ctx = get_jit_state();
1106
+
1107
+ match ctx.pages.get(&phys_page) {
1108
+ Some(PageInfo {
1109
+ wasm_table_index,
1110
+ entry_points,
1111
+ state_flags,
1112
+ hidden_wasm_table_indices: _,
1113
+ }) => set_tlb_code(virt_page, *wasm_table_index, entry_points, *state_flags),
1114
+ None => cpu::clear_tlb_code(phys_page.to_u32() as i32),
1115
+ };
1116
+ }
1117
+
1118
+ pub fn set_tlb_code(
1119
+ virt_page: Page,
1120
+ wasm_table_index: WasmTableIndex,
1121
+ entries: &Vec<(u16, u16)>,
1122
+ state_flags: CachedStateFlags,
1123
+ ) {
1124
+ let c = match unsafe { cpu::tlb_code[virt_page.to_u32() as usize] } {
1125
+ None => {
1126
+ let state_table = [u16::MAX; 0x1000];
1127
+ unsafe {
1128
+ let mut c = NonNull::new_unchecked(Box::into_raw(Box::new(cpu::Code {
1129
+ wasm_table_index,
1130
+ state_flags,
1131
+ state_table,
1132
+ })));
1133
+ cpu::tlb_code[virt_page.to_u32() as usize] = Some(c);
1134
+ c.as_mut()
1135
+ }
1136
+ },
1137
+ Some(mut c) => unsafe {
1138
+ let c = c.as_mut();
1139
+ c.state_table.fill(u16::MAX);
1140
+ c.state_flags = state_flags;
1141
+ c.wasm_table_index = wasm_table_index;
1142
+ c
1143
+ },
1144
+ };
1145
+
1146
+ for &(addr, state) in entries {
1147
+ dbg_assert!(state != u16::MAX);
1148
+ c.state_table[addr as usize] = state;
1149
+ }
1150
+ }
1151
+
1152
+ fn jit_generate_module(
1153
+ structure: Vec<WasmStructure>,
1154
+ basic_blocks: &HashMap<u32, BasicBlock>,
1155
+ mut cpu: CpuContext,
1156
+ builder: &mut WasmBuilder,
1157
+ wasm_table_index: WasmTableIndex,
1158
+ state_flags: CachedStateFlags,
1159
+ ) -> Vec<(u32, u16)> {
1160
+ builder.reset();
1161
+
1162
+ let mut register_locals = (0..8)
1163
+ .map(|i| {
1164
+ builder.load_fixed_i32(global_pointers::get_reg32_offset(i));
1165
+ builder.set_new_local()
1166
+ })
1167
+ .collect();
1168
+
1169
+ builder.const_i32(0);
1170
+ let instruction_counter = builder.set_new_local();
1171
+
1172
+ let exit_label = builder.block_void();
1173
+ let exit_with_fault_label = builder.block_void();
1174
+ let main_loop_label = builder.loop_void();
1175
+ if unsafe { JIT_USE_LOOP_SAFETY } {
1176
+ builder.get_local(&instruction_counter);
1177
+ builder.const_i32(cpu::LOOP_COUNTER);
1178
+ builder.geu_i32();
1179
+ if cfg!(feature = "profiler") {
1180
+ builder.if_void();
1181
+ codegen::gen_debug_track_jit_exit(builder, 0);
1182
+ builder.br(exit_label);
1183
+ builder.block_end();
1184
+ }
1185
+ else {
1186
+ builder.br_if(exit_label);
1187
+ }
1188
+ }
1189
+ let brtable_default = builder.block_void();
1190
+
1191
+ let ctx = &mut JitContext {
1192
+ cpu: &mut cpu,
1193
+ builder,
1194
+ register_locals: &mut register_locals,
1195
+ start_of_current_instruction: 0,
1196
+ exit_with_fault_label,
1197
+ exit_label,
1198
+ current_instruction: Instruction::Other,
1199
+ previous_instruction: Instruction::Other,
1200
+ instruction_counter,
1201
+ };
1202
+
1203
+ let entry_blocks = {
1204
+ let mut nodes = &structure;
1205
+ let result;
1206
+ loop {
1207
+ match &nodes[0] {
1208
+ WasmStructure::Dispatcher(e) => {
1209
+ result = e.clone();
1210
+ break;
1211
+ },
1212
+ WasmStructure::Loop { .. } => {
1213
+ dbg_assert!(false);
1214
+ },
1215
+ WasmStructure::BasicBlock(_) => {
1216
+ dbg_assert!(false);
1217
+ },
1218
+ // Note: We could use these blocks as entry points, which will yield
1219
+ // more entries for free, but it requires adding those to the dispatcher
1220
+ // It's to be investigated if this yields a performance improvement
1221
+ // See also the comment at the bottom of this function when creating entry
1222
+ // points
1223
+ WasmStructure::Block(children) => {
1224
+ nodes = children;
1225
+ },
1226
+ }
1227
+ }
1228
+ result
1229
+ };
1230
+
1231
+ let mut index_for_addr = HashMap::new();
1232
+ for (i, &addr) in entry_blocks.iter().enumerate() {
1233
+ dbg_assert!(i < 0x10000);
1234
+ index_for_addr.insert(addr, i as u16);
1235
+ }
1236
+ for b in basic_blocks.values() {
1237
+ if !index_for_addr.contains_key(&b.addr) {
1238
+ let i = index_for_addr.len();
1239
+ dbg_assert!(i < 0x10000);
1240
+ index_for_addr.insert(b.addr, i as u16);
1241
+ }
1242
+ }
1243
+
1244
+ let mut label_for_addr: HashMap<u32, (Label, Option<u16>)> = HashMap::new();
1245
+
1246
+ enum Work {
1247
+ WasmStructure(WasmStructure),
1248
+ BlockEnd {
1249
+ label: Label,
1250
+ targets: Vec<u32>,
1251
+ olds: HashMap<u32, (Label, Option<u16>)>,
1252
+ },
1253
+ LoopEnd {
1254
+ label: Label,
1255
+ entries: Vec<u32>,
1256
+ olds: HashMap<u32, (Label, Option<u16>)>,
1257
+ },
1258
+ }
1259
+ let mut work: VecDeque<Work> = structure
1260
+ .into_iter()
1261
+ .map(|x| Work::WasmStructure(x))
1262
+ .collect();
1263
+
1264
+ while let Some(block) = work.pop_front() {
1265
+ let next_addr: Option<Vec<u32>> = work.iter().find_map(|x| match x {
1266
+ Work::WasmStructure(l) => Some(l.head().collect()),
1267
+ _ => None,
1268
+ });
1269
+ let target_block = &ctx.builder.arg_local_initial_state.unsafe_clone();
1270
+
1271
+ match block {
1272
+ Work::WasmStructure(WasmStructure::BasicBlock(addr)) => {
1273
+ let block = basic_blocks.get(&addr).unwrap();
1274
+ jit_generate_basic_block(ctx, block);
1275
+
1276
+ if block.has_sti {
1277
+ match block.ty {
1278
+ BasicBlockType::ConditionalJump {
1279
+ condition,
1280
+ jump_offset,
1281
+ jump_offset_is_32,
1282
+ ..
1283
+ } => {
1284
+ codegen::gen_set_eip_low_bits(
1285
+ ctx.builder,
1286
+ block.end_addr as i32 & 0xFFF,
1287
+ );
1288
+ codegen::gen_condition_fn(ctx, condition);
1289
+ ctx.builder.if_void();
1290
+ if jump_offset_is_32 {
1291
+ codegen::gen_relative_jump(ctx.builder, jump_offset);
1292
+ }
1293
+ else {
1294
+ codegen::gen_jmp_rel16(ctx.builder, jump_offset as u16);
1295
+ }
1296
+ ctx.builder.block_end();
1297
+ },
1298
+ BasicBlockType::Normal {
1299
+ jump_offset,
1300
+ jump_offset_is_32,
1301
+ ..
1302
+ } => {
1303
+ if jump_offset_is_32 {
1304
+ codegen::gen_set_eip_low_bits_and_jump_rel32(
1305
+ ctx.builder,
1306
+ block.end_addr as i32 & 0xFFF,
1307
+ jump_offset,
1308
+ );
1309
+ }
1310
+ else {
1311
+ codegen::gen_set_eip_low_bits(
1312
+ ctx.builder,
1313
+ block.end_addr as i32 & 0xFFF,
1314
+ );
1315
+ codegen::gen_jmp_rel16(ctx.builder, jump_offset as u16);
1316
+ }
1317
+ },
1318
+ BasicBlockType::Exit => {},
1319
+ BasicBlockType::AbsoluteEip => {},
1320
+ };
1321
+ codegen::gen_debug_track_jit_exit(ctx.builder, block.last_instruction_addr);
1322
+ codegen::gen_move_registers_from_locals_to_memory(ctx);
1323
+ codegen::gen_fn0_const(ctx.builder, "handle_irqs");
1324
+ codegen::gen_update_instruction_counter(ctx);
1325
+ ctx.builder.return_();
1326
+ continue;
1327
+ }
1328
+
1329
+ match &block.ty {
1330
+ BasicBlockType::Exit => {
1331
+ // Exit this function
1332
+ codegen::gen_debug_track_jit_exit(ctx.builder, block.last_instruction_addr);
1333
+ codegen::gen_profiler_stat_increment(ctx.builder, stat::DIRECT_EXIT);
1334
+ ctx.builder.br(ctx.exit_label);
1335
+ },
1336
+ BasicBlockType::AbsoluteEip => {
1337
+ // Check if we can stay in this module, if not exit
1338
+ codegen::gen_get_eip(ctx.builder);
1339
+ ctx.builder.const_i32(wasm_table_index.to_u16() as i32);
1340
+ ctx.builder.const_i32(state_flags.to_u32() as i32);
1341
+ ctx.builder.call_fn3_ret("jit_find_cache_entry_in_page");
1342
+ ctx.builder.tee_local(target_block);
1343
+ ctx.builder.const_i32(0);
1344
+ ctx.builder.ge_i32();
1345
+ // TODO: Could make this unconditional by including exit_label in the main br_table
1346
+ ctx.builder.br_if(main_loop_label);
1347
+
1348
+ codegen::gen_debug_track_jit_exit(ctx.builder, block.last_instruction_addr);
1349
+ ctx.builder.br(ctx.exit_label);
1350
+ },
1351
+ &BasicBlockType::Normal {
1352
+ next_block_addr: None,
1353
+ jump_offset,
1354
+ jump_offset_is_32,
1355
+ } => {
1356
+ if jump_offset_is_32 {
1357
+ codegen::gen_set_eip_low_bits_and_jump_rel32(
1358
+ ctx.builder,
1359
+ block.end_addr as i32 & 0xFFF,
1360
+ jump_offset,
1361
+ );
1362
+ }
1363
+ else {
1364
+ codegen::gen_set_eip_low_bits(
1365
+ ctx.builder,
1366
+ block.end_addr as i32 & 0xFFF,
1367
+ );
1368
+ codegen::gen_jmp_rel16(ctx.builder, jump_offset as u16);
1369
+ }
1370
+
1371
+ codegen::gen_debug_track_jit_exit(ctx.builder, block.last_instruction_addr);
1372
+ codegen::gen_profiler_stat_increment(ctx.builder, stat::DIRECT_EXIT);
1373
+ ctx.builder.br(ctx.exit_label);
1374
+ },
1375
+ &BasicBlockType::Normal {
1376
+ next_block_addr: Some(next_block_addr),
1377
+ jump_offset,
1378
+ jump_offset_is_32,
1379
+ } => {
1380
+ // Unconditional jump to next basic block
1381
+ // - All instructions that don't change eip
1382
+ // - Unconditional jumps
1383
+
1384
+ if Page::page_of(next_block_addr) != Page::page_of(block.addr) {
1385
+ if jump_offset_is_32 {
1386
+ codegen::gen_set_eip_low_bits_and_jump_rel32(
1387
+ ctx.builder,
1388
+ block.end_addr as i32 & 0xFFF,
1389
+ jump_offset,
1390
+ );
1391
+ }
1392
+ else {
1393
+ codegen::gen_set_eip_low_bits(
1394
+ ctx.builder,
1395
+ block.end_addr as i32 & 0xFFF,
1396
+ );
1397
+ codegen::gen_jmp_rel16(ctx.builder, jump_offset as u16);
1398
+ }
1399
+
1400
+ codegen::gen_profiler_stat_increment(
1401
+ ctx.builder,
1402
+ stat::NORMAL_PAGE_CHANGE,
1403
+ );
1404
+
1405
+ codegen::gen_page_switch_check(
1406
+ ctx,
1407
+ next_block_addr,
1408
+ block.last_instruction_addr,
1409
+ );
1410
+
1411
+ #[cfg(debug_assertions)]
1412
+ codegen::gen_fn2_const(
1413
+ ctx.builder,
1414
+ "check_page_switch",
1415
+ block.addr,
1416
+ next_block_addr,
1417
+ );
1418
+ }
1419
+
1420
+ if next_addr
1421
+ .as_ref()
1422
+ .map_or(false, |n| n.contains(&next_block_addr))
1423
+ {
1424
+ // Blocks are consecutive
1425
+ if next_addr.unwrap().len() > 1 {
1426
+ let target_index = *index_for_addr.get(&next_block_addr).unwrap();
1427
+ if cfg!(feature = "profiler") {
1428
+ ctx.builder.const_i32(target_index.into());
1429
+ ctx.builder.call_fn1("debug_set_dispatcher_target");
1430
+ }
1431
+ ctx.builder.const_i32(target_index.into());
1432
+ ctx.builder.set_local(target_block);
1433
+ codegen::gen_profiler_stat_increment(
1434
+ ctx.builder,
1435
+ stat::NORMAL_FALLTHRU_WITH_TARGET_BLOCK,
1436
+ );
1437
+ }
1438
+ else {
1439
+ codegen::gen_profiler_stat_increment(
1440
+ ctx.builder,
1441
+ stat::NORMAL_FALLTHRU,
1442
+ );
1443
+ }
1444
+ }
1445
+ else {
1446
+ let &(br, target_index) = label_for_addr.get(&next_block_addr).unwrap();
1447
+ if let Some(target_index) = target_index {
1448
+ if cfg!(feature = "profiler") {
1449
+ ctx.builder.const_i32(target_index.into());
1450
+ ctx.builder.call_fn1("debug_set_dispatcher_target");
1451
+ }
1452
+ ctx.builder.const_i32(target_index.into());
1453
+ ctx.builder.set_local(target_block);
1454
+ codegen::gen_profiler_stat_increment(
1455
+ ctx.builder,
1456
+ stat::NORMAL_BRANCH_WITH_TARGET_BLOCK,
1457
+ );
1458
+ }
1459
+ else {
1460
+ codegen::gen_profiler_stat_increment(
1461
+ ctx.builder,
1462
+ stat::NORMAL_BRANCH,
1463
+ );
1464
+ }
1465
+ ctx.builder.br(br);
1466
+ }
1467
+ },
1468
+ &BasicBlockType::ConditionalJump {
1469
+ next_block_addr,
1470
+ next_block_branch_taken_addr,
1471
+ condition,
1472
+ jump_offset,
1473
+ jump_offset_is_32,
1474
+ } => {
1475
+ // Conditional jump to next basic block
1476
+ // - jnz, jc, loop, jcxz, etc.
1477
+
1478
+ // Generate:
1479
+ // (1) condition()
1480
+ // (2) br_if()
1481
+ // (3) br()
1482
+ // Except:
1483
+ // If we need to update eip in case (2), it's replaced by if { update_eip(); br() }
1484
+ // If case (3) can fall through to the next basic block, the branch is eliminated
1485
+ // Dispatcher target writes can be generated in either case
1486
+ // Condition may be inverted if it helps generate a fallthrough instead of the second branch
1487
+
1488
+ codegen::gen_profiler_stat_increment(ctx.builder, stat::CONDITIONAL_JUMP);
1489
+
1490
+ #[derive(PartialEq)]
1491
+ enum Case {
1492
+ BranchTaken,
1493
+ BranchNotTaken,
1494
+ }
1495
+
1496
+ let mut handle_case = |case: Case, is_first| {
1497
+ // first case generates condition and *has* to branch away,
1498
+ // second case branches unconditionally or falls through
1499
+
1500
+ if is_first {
1501
+ if case == Case::BranchNotTaken {
1502
+ codegen::gen_condition_fn_negated(ctx, condition);
1503
+ }
1504
+ else {
1505
+ codegen::gen_condition_fn(ctx, condition);
1506
+ }
1507
+ }
1508
+
1509
+ let next_block_addr = if case == Case::BranchTaken {
1510
+ next_block_branch_taken_addr
1511
+ }
1512
+ else {
1513
+ next_block_addr
1514
+ };
1515
+
1516
+ if let Some(next_block_addr) = next_block_addr {
1517
+ if Page::page_of(next_block_addr) != Page::page_of(block.addr) {
1518
+ dbg_assert!(case == Case::BranchTaken); // currently not possible in other case
1519
+ if is_first {
1520
+ ctx.builder.if_i32();
1521
+ }
1522
+ if jump_offset_is_32 {
1523
+ codegen::gen_set_eip_low_bits_and_jump_rel32(
1524
+ ctx.builder,
1525
+ block.end_addr as i32 & 0xFFF,
1526
+ jump_offset,
1527
+ );
1528
+ }
1529
+ else {
1530
+ codegen::gen_set_eip_low_bits(
1531
+ ctx.builder,
1532
+ block.end_addr as i32 & 0xFFF,
1533
+ );
1534
+ codegen::gen_jmp_rel16(ctx.builder, jump_offset as u16);
1535
+ }
1536
+
1537
+ codegen::gen_profiler_stat_increment(
1538
+ ctx.builder,
1539
+ stat::CONDITIONAL_JUMP_PAGE_CHANGE,
1540
+ );
1541
+ codegen::gen_page_switch_check(
1542
+ ctx,
1543
+ next_block_addr,
1544
+ block.last_instruction_addr,
1545
+ );
1546
+
1547
+ #[cfg(debug_assertions)]
1548
+ codegen::gen_fn2_const(
1549
+ ctx.builder,
1550
+ "check_page_switch",
1551
+ block.addr,
1552
+ next_block_addr,
1553
+ );
1554
+
1555
+ if is_first {
1556
+ ctx.builder.const_i32(1);
1557
+ ctx.builder.else_();
1558
+ ctx.builder.const_i32(0);
1559
+ ctx.builder.block_end();
1560
+ }
1561
+ }
1562
+
1563
+ if next_addr
1564
+ .as_ref()
1565
+ .map_or(false, |n| n.contains(&next_block_addr))
1566
+ {
1567
+ // blocks are consecutive
1568
+
1569
+ // fallthrough, has to be second
1570
+ dbg_assert!(!is_first);
1571
+
1572
+ if next_addr.as_ref().unwrap().len() > 1 {
1573
+ let target_index =
1574
+ *index_for_addr.get(&next_block_addr).unwrap();
1575
+ if cfg!(feature = "profiler") {
1576
+ ctx.builder.const_i32(target_index.into());
1577
+ ctx.builder.call_fn1("debug_set_dispatcher_target");
1578
+ }
1579
+ ctx.builder.const_i32(target_index.into());
1580
+ ctx.builder.set_local(target_block);
1581
+ codegen::gen_profiler_stat_increment(
1582
+ ctx.builder,
1583
+ stat::CONDITIONAL_JUMP_FALLTHRU_WITH_TARGET_BLOCK,
1584
+ );
1585
+ }
1586
+ else {
1587
+ codegen::gen_profiler_stat_increment(
1588
+ ctx.builder,
1589
+ stat::CONDITIONAL_JUMP_FALLTHRU,
1590
+ );
1591
+ }
1592
+ }
1593
+ else {
1594
+ let &(br, target_index) =
1595
+ label_for_addr.get(&next_block_addr).unwrap();
1596
+ if let Some(target_index) = target_index {
1597
+ if cfg!(feature = "profiler") {
1598
+ // Note: Currently called unconditionally, even if the
1599
+ // br_if below doesn't branch
1600
+ ctx.builder.const_i32(target_index.into());
1601
+ ctx.builder.call_fn1("debug_set_dispatcher_target");
1602
+ }
1603
+ ctx.builder.const_i32(target_index.into());
1604
+ ctx.builder.set_local(target_block);
1605
+ }
1606
+
1607
+ if is_first {
1608
+ if cfg!(feature = "profiler") {
1609
+ ctx.builder.if_void();
1610
+ codegen::gen_profiler_stat_increment(
1611
+ ctx.builder,
1612
+ if target_index.is_some() {
1613
+ stat::CONDITIONAL_JUMP_BRANCH_WITH_TARGET_BLOCK
1614
+ }
1615
+ else {
1616
+ stat::CONDITIONAL_JUMP_BRANCH
1617
+ },
1618
+ );
1619
+ ctx.builder.br(br);
1620
+ ctx.builder.block_end();
1621
+ }
1622
+ else {
1623
+ ctx.builder.br_if(br);
1624
+ }
1625
+ }
1626
+ else {
1627
+ codegen::gen_profiler_stat_increment(
1628
+ ctx.builder,
1629
+ if target_index.is_some() {
1630
+ stat::CONDITIONAL_JUMP_BRANCH_WITH_TARGET_BLOCK
1631
+ }
1632
+ else {
1633
+ stat::CONDITIONAL_JUMP_BRANCH
1634
+ },
1635
+ );
1636
+ ctx.builder.br(br);
1637
+ }
1638
+ }
1639
+ }
1640
+ else {
1641
+ // target is outside of this module, update eip and exit
1642
+ if is_first {
1643
+ ctx.builder.if_void();
1644
+ }
1645
+
1646
+ if case == Case::BranchTaken {
1647
+ if jump_offset_is_32 {
1648
+ codegen::gen_set_eip_low_bits_and_jump_rel32(
1649
+ ctx.builder,
1650
+ block.end_addr as i32 & 0xFFF,
1651
+ jump_offset,
1652
+ );
1653
+ }
1654
+ else {
1655
+ codegen::gen_set_eip_low_bits(
1656
+ ctx.builder,
1657
+ block.end_addr as i32 & 0xFFF,
1658
+ );
1659
+ codegen::gen_jmp_rel16(ctx.builder, jump_offset as u16);
1660
+ }
1661
+ }
1662
+ else {
1663
+ codegen::gen_set_eip_low_bits(
1664
+ ctx.builder,
1665
+ block.end_addr as i32 & 0xFFF,
1666
+ );
1667
+ }
1668
+
1669
+ codegen::gen_debug_track_jit_exit(
1670
+ ctx.builder,
1671
+ block.last_instruction_addr,
1672
+ );
1673
+ codegen::gen_profiler_stat_increment(
1674
+ ctx.builder,
1675
+ stat::CONDITIONAL_JUMP_EXIT,
1676
+ );
1677
+ ctx.builder.br(ctx.exit_label);
1678
+
1679
+ if is_first {
1680
+ ctx.builder.block_end();
1681
+ }
1682
+ }
1683
+ };
1684
+
1685
+ let branch_taken_is_fallthrough = next_block_branch_taken_addr
1686
+ .map_or(false, |addr| {
1687
+ next_addr.as_ref().map_or(false, |n| n.contains(&addr))
1688
+ });
1689
+ let branch_not_taken_is_fallthrough = next_block_addr
1690
+ .map_or(false, |addr| {
1691
+ next_addr.as_ref().map_or(false, |n| n.contains(&addr))
1692
+ });
1693
+
1694
+ if branch_not_taken_is_fallthrough && branch_taken_is_fallthrough {
1695
+ let next_block_addr = next_block_addr.unwrap();
1696
+ let next_block_branch_taken_addr =
1697
+ next_block_branch_taken_addr.unwrap();
1698
+
1699
+ dbg_log!(
1700
+ "Conditional control flow: fallthrough in both cases, page_switch={} next_is_multi={}",
1701
+ Page::page_of(next_block_branch_taken_addr)
1702
+ != Page::page_of(block.addr),
1703
+ next_addr.as_ref().unwrap().len() > 1,
1704
+ );
1705
+
1706
+ dbg_assert!(
1707
+ Page::page_of(next_block_addr) == Page::page_of(block.addr)
1708
+ ); // currently not possible
1709
+
1710
+ if Page::page_of(next_block_branch_taken_addr)
1711
+ != Page::page_of(block.addr)
1712
+ {
1713
+ codegen::gen_condition_fn(ctx, condition);
1714
+ ctx.builder.if_void();
1715
+
1716
+ if jump_offset_is_32 {
1717
+ codegen::gen_set_eip_low_bits_and_jump_rel32(
1718
+ ctx.builder,
1719
+ block.end_addr as i32 & 0xFFF,
1720
+ jump_offset,
1721
+ );
1722
+ }
1723
+ else {
1724
+ codegen::gen_set_eip_low_bits(
1725
+ ctx.builder,
1726
+ block.end_addr as i32 & 0xFFF,
1727
+ );
1728
+ codegen::gen_jmp_rel16(ctx.builder, jump_offset as u16);
1729
+ }
1730
+
1731
+ codegen::gen_profiler_stat_increment(
1732
+ ctx.builder,
1733
+ stat::CONDITIONAL_JUMP_PAGE_CHANGE,
1734
+ );
1735
+ codegen::gen_page_switch_check(
1736
+ ctx,
1737
+ next_block_branch_taken_addr,
1738
+ block.last_instruction_addr,
1739
+ );
1740
+
1741
+ #[cfg(debug_assertions)]
1742
+ codegen::gen_fn2_const(
1743
+ ctx.builder,
1744
+ "check_page_switch",
1745
+ block.addr,
1746
+ next_block_branch_taken_addr,
1747
+ );
1748
+
1749
+ dbg_assert!(next_addr.unwrap().len() > 1);
1750
+
1751
+ let target_index_taken =
1752
+ *index_for_addr.get(&next_block_branch_taken_addr).unwrap();
1753
+ let target_index_not_taken =
1754
+ *index_for_addr.get(&next_block_addr).unwrap();
1755
+
1756
+ ctx.builder.const_i32(target_index_taken.into());
1757
+ ctx.builder.set_local(target_block);
1758
+
1759
+ ctx.builder.else_();
1760
+ ctx.builder.const_i32(target_index_not_taken.into());
1761
+ ctx.builder.set_local(target_block);
1762
+
1763
+ ctx.builder.block_end();
1764
+ }
1765
+ else if next_addr.unwrap().len() > 1 {
1766
+ let target_index_taken =
1767
+ *index_for_addr.get(&next_block_branch_taken_addr).unwrap();
1768
+ let target_index_not_taken =
1769
+ *index_for_addr.get(&next_block_addr).unwrap();
1770
+
1771
+ codegen::gen_condition_fn(ctx, condition);
1772
+ ctx.builder.if_i32();
1773
+ ctx.builder.const_i32(target_index_taken.into());
1774
+ ctx.builder.else_();
1775
+ ctx.builder.const_i32(target_index_not_taken.into());
1776
+ ctx.builder.block_end();
1777
+ ctx.builder.set_local(target_block);
1778
+ }
1779
+ }
1780
+ else if branch_taken_is_fallthrough {
1781
+ handle_case(Case::BranchNotTaken, true);
1782
+ handle_case(Case::BranchTaken, false);
1783
+ }
1784
+ else {
1785
+ handle_case(Case::BranchTaken, true);
1786
+ handle_case(Case::BranchNotTaken, false);
1787
+ }
1788
+ },
1789
+ }
1790
+ },
1791
+ Work::WasmStructure(WasmStructure::Dispatcher(entries)) => {
1792
+ profiler::stat_increment(stat::COMPILE_DISPATCHER);
1793
+
1794
+ if cfg!(feature = "profiler") {
1795
+ ctx.builder.get_local(target_block);
1796
+ ctx.builder.const_i32(index_for_addr.len() as i32);
1797
+ ctx.builder.call_fn2("check_dispatcher_target");
1798
+ }
1799
+
1800
+ if entries.len() > BRTABLE_CUTOFF {
1801
+ // generate a brtable
1802
+ codegen::gen_profiler_stat_increment(ctx.builder, stat::DISPATCHER_LARGE);
1803
+ let mut cases = Vec::new();
1804
+ for &addr in &entries {
1805
+ let &(label, target_index) = label_for_addr.get(&addr).unwrap();
1806
+ let &index = index_for_addr.get(&addr).unwrap();
1807
+ dbg_assert!(target_index.is_none() || target_index == Some(index));
1808
+ while index as usize >= cases.len() {
1809
+ cases.push(brtable_default);
1810
+ }
1811
+ cases[index as usize] = label;
1812
+ }
1813
+ ctx.builder.get_local(target_block);
1814
+ ctx.builder.brtable(brtable_default, &mut cases.iter());
1815
+ }
1816
+ else {
1817
+ // generate a if target == block.addr then br block.label ...
1818
+ codegen::gen_profiler_stat_increment(ctx.builder, stat::DISPATCHER_SMALL);
1819
+ let nexts: HashSet<u32> = next_addr
1820
+ .as_ref()
1821
+ .map_or(HashSet::new(), |nexts| nexts.iter().copied().collect());
1822
+ for &addr in &entries {
1823
+ if nexts.contains(&addr) {
1824
+ continue;
1825
+ }
1826
+ let index = *index_for_addr.get(&addr).unwrap();
1827
+ let &(label, _) = label_for_addr.get(&addr).unwrap();
1828
+ ctx.builder.get_local(target_block);
1829
+ ctx.builder.const_i32(index.into());
1830
+ ctx.builder.eq_i32();
1831
+ ctx.builder.br_if(label);
1832
+ }
1833
+ }
1834
+ },
1835
+ Work::WasmStructure(WasmStructure::Loop(children)) => {
1836
+ profiler::stat_increment(stat::COMPILE_WASM_LOOP);
1837
+
1838
+ let entries: Vec<u32> = children[0].head().collect();
1839
+ let label = ctx.builder.loop_void();
1840
+ codegen::gen_profiler_stat_increment(ctx.builder, stat::LOOP);
1841
+
1842
+ if entries.len() == 1 {
1843
+ let addr = entries[0];
1844
+ codegen::gen_set_eip_low_bits(ctx.builder, addr as i32 & 0xFFF);
1845
+ profiler::stat_increment(stat::COMPILE_WITH_LOOP_SAFETY);
1846
+ codegen::gen_profiler_stat_increment(ctx.builder, stat::LOOP_SAFETY);
1847
+ if unsafe { JIT_USE_LOOP_SAFETY } {
1848
+ ctx.builder.get_local(&ctx.instruction_counter);
1849
+ ctx.builder.const_i32(cpu::LOOP_COUNTER);
1850
+ ctx.builder.geu_i32();
1851
+ if cfg!(feature = "profiler") {
1852
+ ctx.builder.if_void();
1853
+ codegen::gen_debug_track_jit_exit(ctx.builder, addr);
1854
+ ctx.builder.br(exit_label);
1855
+ ctx.builder.block_end();
1856
+ }
1857
+ else {
1858
+ ctx.builder.br_if(exit_label);
1859
+ }
1860
+ }
1861
+ }
1862
+
1863
+ let mut olds = HashMap::new();
1864
+ for &target in entries.iter() {
1865
+ let index = if entries.len() == 1 {
1866
+ None
1867
+ }
1868
+ else {
1869
+ Some(*index_for_addr.get(&target).unwrap())
1870
+ };
1871
+ let old = label_for_addr.insert(target, (label, index));
1872
+ if let Some(old) = old {
1873
+ olds.insert(target, old);
1874
+ }
1875
+ }
1876
+
1877
+ work.push_front(Work::LoopEnd {
1878
+ label,
1879
+ entries,
1880
+ olds,
1881
+ });
1882
+ for c in children.into_iter().rev() {
1883
+ work.push_front(Work::WasmStructure(c));
1884
+ }
1885
+ },
1886
+ Work::LoopEnd {
1887
+ label,
1888
+ entries,
1889
+ olds,
1890
+ } => {
1891
+ for target in entries {
1892
+ let old = label_for_addr.remove(&target);
1893
+ dbg_assert!(old.map(|(l, _)| l) == Some(label));
1894
+ }
1895
+ for (target, old) in olds {
1896
+ let old = label_for_addr.insert(target, old);
1897
+ dbg_assert!(old.is_none());
1898
+ }
1899
+
1900
+ ctx.builder.block_end();
1901
+ },
1902
+ Work::WasmStructure(WasmStructure::Block(children)) => {
1903
+ profiler::stat_increment(stat::COMPILE_WASM_BLOCK);
1904
+
1905
+ let targets = next_addr.clone().unwrap();
1906
+ let label = ctx.builder.block_void();
1907
+ let mut olds = HashMap::new();
1908
+ for &target in targets.iter() {
1909
+ let index = if targets.len() == 1 {
1910
+ None
1911
+ }
1912
+ else {
1913
+ Some(*index_for_addr.get(&target).unwrap())
1914
+ };
1915
+ let old = label_for_addr.insert(target, (label, index));
1916
+ if let Some(old) = old {
1917
+ olds.insert(target, old);
1918
+ }
1919
+ }
1920
+
1921
+ work.push_front(Work::BlockEnd {
1922
+ label,
1923
+ targets,
1924
+ olds,
1925
+ });
1926
+ for c in children.into_iter().rev() {
1927
+ work.push_front(Work::WasmStructure(c));
1928
+ }
1929
+ },
1930
+ Work::BlockEnd {
1931
+ label,
1932
+ targets,
1933
+ olds,
1934
+ } => {
1935
+ for target in targets {
1936
+ let old = label_for_addr.remove(&target);
1937
+ dbg_assert!(old.map(|(l, _)| l) == Some(label));
1938
+ }
1939
+ for (target, old) in olds {
1940
+ let old = label_for_addr.insert(target, old);
1941
+ dbg_assert!(old.is_none());
1942
+ }
1943
+
1944
+ ctx.builder.block_end();
1945
+ },
1946
+ }
1947
+ }
1948
+
1949
+ dbg_assert!(label_for_addr.is_empty());
1950
+
1951
+ {
1952
+ ctx.builder.block_end(); // default case for the brtable
1953
+ ctx.builder.unreachable();
1954
+ }
1955
+ {
1956
+ ctx.builder.block_end(); // main loop
1957
+ }
1958
+ {
1959
+ // exit-with-fault case
1960
+ ctx.builder.block_end();
1961
+ codegen::gen_move_registers_from_locals_to_memory(ctx);
1962
+ codegen::gen_fn0_const(ctx.builder, "trigger_fault_end_jit");
1963
+ codegen::gen_update_instruction_counter(ctx);
1964
+ ctx.builder.return_();
1965
+ }
1966
+ {
1967
+ // exit
1968
+ ctx.builder.block_end();
1969
+ codegen::gen_move_registers_from_locals_to_memory(ctx);
1970
+ codegen::gen_update_instruction_counter(ctx);
1971
+ }
1972
+
1973
+ for local in ctx.register_locals.drain(..) {
1974
+ ctx.builder.free_local(local);
1975
+ }
1976
+ ctx.builder
1977
+ .free_local(ctx.instruction_counter.unsafe_clone());
1978
+
1979
+ ctx.builder.finish();
1980
+
1981
+ let entries = Vec::from_iter(entry_blocks.iter().map(|addr| {
1982
+ let block = basic_blocks.get(&addr).unwrap();
1983
+ let index = *index_for_addr.get(&addr).unwrap();
1984
+
1985
+ profiler::stat_increment(stat::COMPILE_ENTRY_POINT);
1986
+
1987
+ dbg_assert!(block.addr < block.end_addr);
1988
+ // Note: We also insert blocks that weren't originally marked as entries here
1989
+ // This doesn't have any downside, besides making the hash table slightly larger
1990
+
1991
+ (block.addr, index)
1992
+ }));
1993
+
1994
+ for b in basic_blocks.values() {
1995
+ if b.is_entry_block {
1996
+ dbg_assert!(entries.iter().find(|(addr, _)| *addr == b.addr).is_some());
1997
+ }
1998
+ }
1999
+
2000
+ return entries;
2001
+ }
2002
+
2003
+ fn jit_generate_basic_block(ctx: &mut JitContext, block: &BasicBlock) {
2004
+ let needs_eip_updated = match block.ty {
2005
+ BasicBlockType::Exit => true,
2006
+ _ => false,
2007
+ };
2008
+
2009
+ profiler::stat_increment(stat::COMPILE_BASIC_BLOCK);
2010
+
2011
+ let start_addr = block.addr;
2012
+ let last_instruction_addr = block.last_instruction_addr;
2013
+ let stop_addr = block.end_addr;
2014
+
2015
+ // First iteration of do-while assumes the caller confirms this condition
2016
+ dbg_assert!(!is_near_end_of_page(start_addr));
2017
+
2018
+ if cfg!(feature = "profiler") {
2019
+ ctx.builder.const_i32(start_addr as i32);
2020
+ ctx.builder.call_fn1("enter_basic_block");
2021
+ }
2022
+
2023
+ ctx.builder.get_local(&ctx.instruction_counter);
2024
+ ctx.builder.const_i32(block.number_of_instructions as i32);
2025
+ ctx.builder.add_i32();
2026
+ ctx.builder.set_local(&ctx.instruction_counter);
2027
+
2028
+ ctx.cpu.eip = start_addr;
2029
+ ctx.current_instruction = Instruction::Other;
2030
+ ctx.previous_instruction = Instruction::Other;
2031
+
2032
+ loop {
2033
+ let mut instruction = 0;
2034
+ if cfg!(feature = "profiler") {
2035
+ instruction = memory::read32s(ctx.cpu.eip) as u32;
2036
+ opstats::gen_opstats(ctx.builder, instruction);
2037
+ opstats::record_opstat_compiled(instruction);
2038
+ }
2039
+
2040
+ if ctx.cpu.eip == last_instruction_addr {
2041
+ // Before the last instruction:
2042
+ // - Set eip to *after* the instruction
2043
+ // - Set previous_eip to *before* the instruction
2044
+ if needs_eip_updated {
2045
+ codegen::gen_set_previous_eip_offset_from_eip_with_low_bits(
2046
+ ctx.builder,
2047
+ last_instruction_addr as i32 & 0xFFF,
2048
+ );
2049
+ codegen::gen_set_eip_low_bits(ctx.builder, stop_addr as i32 & 0xFFF);
2050
+ }
2051
+ }
2052
+
2053
+ let wasm_length_before = ctx.builder.instruction_body_length();
2054
+
2055
+ ctx.start_of_current_instruction = ctx.cpu.eip;
2056
+ let start_eip = ctx.cpu.eip;
2057
+ let mut instruction_flags = 0;
2058
+ jit_instructions::jit_instruction(ctx, &mut instruction_flags);
2059
+ let end_eip = ctx.cpu.eip;
2060
+
2061
+ let instruction_length = end_eip - start_eip;
2062
+ let was_block_boundary = instruction_flags & JIT_INSTR_BLOCK_BOUNDARY_FLAG != 0;
2063
+
2064
+ let wasm_length = ctx.builder.instruction_body_length() - wasm_length_before;
2065
+ opstats::record_opstat_size_wasm(instruction, wasm_length as u64);
2066
+
2067
+ dbg_assert!((end_eip == stop_addr) == (start_eip == last_instruction_addr));
2068
+ dbg_assert!(instruction_length < MAX_INSTRUCTION_LENGTH);
2069
+
2070
+ let end_addr = ctx.cpu.eip;
2071
+
2072
+ if end_addr == stop_addr {
2073
+ // no page was crossed
2074
+ dbg_assert!(Page::page_of(end_addr) == Page::page_of(start_addr));
2075
+ break;
2076
+ }
2077
+
2078
+ if was_block_boundary || is_near_end_of_page(end_addr) || end_addr > stop_addr {
2079
+ dbg_log!(
2080
+ "Overlapping basic blocks start={:x} expected_end={:x} end={:x} was_block_boundary={} near_end_of_page={}",
2081
+ start_addr,
2082
+ stop_addr,
2083
+ end_addr,
2084
+ was_block_boundary,
2085
+ is_near_end_of_page(end_addr)
2086
+ );
2087
+ dbg_assert!(false);
2088
+ break;
2089
+ }
2090
+
2091
+ ctx.previous_instruction = mem::replace(&mut ctx.current_instruction, Instruction::Other);
2092
+ }
2093
+ }
2094
+
2095
+ pub fn jit_increase_hotness_and_maybe_compile(
2096
+ virt_address: i32,
2097
+ phys_address: u32,
2098
+ cs_offset: u32,
2099
+ state_flags: CachedStateFlags,
2100
+ heat: u32,
2101
+ ) {
2102
+ if unsafe { JIT_DISABLED } {
2103
+ return;
2104
+ }
2105
+
2106
+ let mut ctx = get_jit_state();
2107
+ let is_compiling = ctx.compiling.is_some();
2108
+ let page = Page::page_of(phys_address);
2109
+ let (hotness, entry_points) = ctx.entry_points.entry(page).or_insert_with(|| {
2110
+ cpu::tlb_set_has_code(page, true);
2111
+ profiler::stat_increment(stat::RUN_INTERPRETED_NEW_PAGE);
2112
+ (0, HashSet::new())
2113
+ });
2114
+
2115
+ if !is_near_end_of_page(phys_address) {
2116
+ entry_points.insert(phys_address as u16 & 0xFFF);
2117
+ }
2118
+
2119
+ *hotness += heat;
2120
+ if *hotness >= JIT_THRESHOLD {
2121
+ if is_compiling {
2122
+ return;
2123
+ }
2124
+ // only try generating if we're in the correct address space
2125
+ if cpu::translate_address_read_no_side_effects(virt_address) == Ok(phys_address) {
2126
+ *hotness = 0;
2127
+ jit_analyze_and_generate(&mut ctx, virt_address, phys_address, cs_offset, state_flags)
2128
+ }
2129
+ else {
2130
+ profiler::stat_increment(stat::COMPILE_WRONG_ADDRESS_SPACE);
2131
+ }
2132
+ }
2133
+ }
2134
+
2135
+ fn free_wasm_table_index(ctx: &mut JitState, wasm_table_index: WasmTableIndex) {
2136
+ if CHECK_JIT_STATE_INVARIANTS {
2137
+ dbg_assert!(!ctx.wasm_table_index_free_list.contains(&wasm_table_index));
2138
+
2139
+ match &ctx.compiling {
2140
+ Some((wasm_table_index_compiling, _)) => {
2141
+ dbg_assert!(
2142
+ *wasm_table_index_compiling != wasm_table_index,
2143
+ "Attempt to free wasm table index that is currently being compiled"
2144
+ );
2145
+ },
2146
+ _ => {},
2147
+ }
2148
+
2149
+ dbg_assert!(!ctx
2150
+ .pages
2151
+ .values()
2152
+ .any(|info| info.wasm_table_index == wasm_table_index));
2153
+
2154
+ dbg_assert!(!ctx
2155
+ .pages
2156
+ .values()
2157
+ .any(|info| info.hidden_wasm_table_indices.contains(&wasm_table_index)));
2158
+
2159
+ for i in 0..unsafe { cpu::valid_tlb_entries_count } {
2160
+ let page = unsafe { cpu::valid_tlb_entries[i as usize] };
2161
+ unsafe {
2162
+ match cpu::tlb_code[page as usize] {
2163
+ None => {},
2164
+ Some(c) => {
2165
+ let c = c.as_ref();
2166
+ dbg_assert!(c.wasm_table_index != wasm_table_index);
2167
+ },
2168
+ }
2169
+ }
2170
+ }
2171
+ }
2172
+
2173
+ ctx.wasm_table_index_free_list.push(wasm_table_index);
2174
+
2175
+ // It is not strictly necessary to clear the function, but it will fail more predictably if we
2176
+ // accidentally use the function and may garbage collect unused modules earlier
2177
+ jit_clear_func(wasm_table_index);
2178
+ }
2179
+
2180
+ /// Register a write in this page: Delete all present code
2181
+ fn jit_dirty_page_ctx(ctx: &mut JitState, page: Page) {
2182
+ let mut did_have_code = false;
2183
+
2184
+ if let Some(PageInfo {
2185
+ wasm_table_index,
2186
+ hidden_wasm_table_indices,
2187
+ state_flags: _,
2188
+ entry_points: _,
2189
+ }) = ctx.pages.remove(&page)
2190
+ {
2191
+ profiler::stat_increment(stat::INVALIDATE_PAGE_HAD_CODE);
2192
+ did_have_code = true;
2193
+
2194
+ free(ctx, wasm_table_index);
2195
+ for wasm_table_index in hidden_wasm_table_indices {
2196
+ free(ctx, wasm_table_index);
2197
+ }
2198
+
2199
+ fn free(ctx: &mut JitState, wasm_table_index: WasmTableIndex) {
2200
+ for i in 0..unsafe { cpu::valid_tlb_entries_count } {
2201
+ let page = unsafe { cpu::valid_tlb_entries[i as usize] };
2202
+ let entry = unsafe { cpu::tlb_data[page as usize] };
2203
+ if 0 != entry {
2204
+ let tlb_physical_page = Page::of_u32(
2205
+ (entry as u32 >> 12 ^ page as u32) - (unsafe { memory::mem8 } as u32 >> 12),
2206
+ );
2207
+ match unsafe { cpu::tlb_code[page as usize] } {
2208
+ None => {},
2209
+ Some(c) => unsafe {
2210
+ let w = c.as_ref().wasm_table_index;
2211
+ if wasm_table_index == w {
2212
+ drop(Box::from_raw(c.as_ptr()));
2213
+ cpu::tlb_code[page as usize] = None;
2214
+ if !ctx.entry_points.contains_key(&tlb_physical_page) {
2215
+ // XXX
2216
+ cpu::tlb_data[page as usize] &= !cpu::TLB_HAS_CODE;
2217
+ }
2218
+ }
2219
+ },
2220
+ }
2221
+ }
2222
+ }
2223
+
2224
+ ctx.pages.retain(
2225
+ |_,
2226
+ &mut PageInfo {
2227
+ wasm_table_index: w,
2228
+ ..
2229
+ }| w != wasm_table_index,
2230
+ );
2231
+
2232
+ for info in ctx.pages.values_mut() {
2233
+ info.hidden_wasm_table_indices
2234
+ .retain(|&w| w != wasm_table_index)
2235
+ }
2236
+
2237
+ free_wasm_table_index(ctx, wasm_table_index);
2238
+ }
2239
+ }
2240
+
2241
+ match ctx.entry_points.remove(&page) {
2242
+ None => {},
2243
+ Some(_) => {
2244
+ profiler::stat_increment(stat::INVALIDATE_PAGE_HAD_ENTRY_POINTS);
2245
+ did_have_code = true;
2246
+
2247
+ match &ctx.compiling {
2248
+ Some((index, CompilingPageState::Compiling { pages })) => {
2249
+ if pages.contains_key(&page) {
2250
+ ctx.compiling = Some((*index, CompilingPageState::CompilingWritten));
2251
+ }
2252
+ },
2253
+ _ => {},
2254
+ }
2255
+ },
2256
+ }
2257
+
2258
+ match &ctx.compiling {
2259
+ Some((_, CompilingPageState::Compiling { pages })) => {
2260
+ dbg_assert!(!pages.contains_key(&page));
2261
+ },
2262
+ _ => {},
2263
+ }
2264
+
2265
+ check_jit_state_invariants(ctx);
2266
+
2267
+ dbg_assert!(!jit_page_has_code_ctx(ctx, page));
2268
+
2269
+ if did_have_code {
2270
+ cpu::tlb_set_has_code(page, false);
2271
+ }
2272
+
2273
+ if !did_have_code {
2274
+ profiler::stat_increment(stat::DIRTY_PAGE_DID_NOT_HAVE_CODE);
2275
+ }
2276
+ }
2277
+
2278
+ #[no_mangle]
2279
+ pub fn jit_dirty_cache(start_addr: u32, end_addr: u32) {
2280
+ dbg_assert!(start_addr < end_addr);
2281
+
2282
+ let start_page = Page::page_of(start_addr);
2283
+ let end_page = Page::page_of(end_addr - 1);
2284
+
2285
+ for page in start_page.to_u32()..end_page.to_u32() + 1 {
2286
+ jit_dirty_page_ctx(&mut get_jit_state(), Page::page_of(page << 12));
2287
+ }
2288
+ }
2289
+
2290
+ #[no_mangle]
2291
+ pub fn jit_dirty_page(page: Page) { jit_dirty_page_ctx(&mut get_jit_state(), page) }
2292
+
2293
+ /// dirty pages in the range of start_addr and end_addr, which must span at most two pages
2294
+ pub fn jit_dirty_cache_small(start_addr: u32, end_addr: u32) {
2295
+ dbg_assert!(start_addr < end_addr);
2296
+
2297
+ let start_page = Page::page_of(start_addr);
2298
+ let end_page = Page::page_of(end_addr - 1);
2299
+
2300
+ let mut ctx = get_jit_state();
2301
+ jit_dirty_page_ctx(&mut ctx, start_page);
2302
+
2303
+ // Note: This can't happen when paging is enabled, as writes across
2304
+ // boundaries are split up on two pages
2305
+ if start_page != end_page {
2306
+ dbg_assert!(start_page.to_u32() + 1 == end_page.to_u32());
2307
+ jit_dirty_page_ctx(&mut ctx, end_page);
2308
+ }
2309
+ }
2310
+
2311
+ #[no_mangle]
2312
+ pub fn jit_clear_cache_js() { jit_clear_cache(&mut get_jit_state()) }
2313
+
2314
+ fn jit_clear_cache(ctx: &mut JitState) {
2315
+ let mut pages_with_code = HashSet::new();
2316
+
2317
+ for &p in ctx.entry_points.keys() {
2318
+ pages_with_code.insert(p);
2319
+ }
2320
+ for &p in ctx.pages.keys() {
2321
+ pages_with_code.insert(p);
2322
+ }
2323
+
2324
+ for page in pages_with_code {
2325
+ jit_dirty_page_ctx(ctx, page);
2326
+ }
2327
+ }
2328
+
2329
+ pub fn jit_page_has_code(page: Page) -> bool { jit_page_has_code_ctx(&mut get_jit_state(), page) }
2330
+
2331
+ fn jit_page_has_code_ctx(ctx: &mut JitState, page: Page) -> bool {
2332
+ ctx.pages.contains_key(&page) || ctx.entry_points.contains_key(&page)
2333
+ }
2334
+
2335
+ #[no_mangle]
2336
+ pub fn jit_get_wasm_table_index_free_list_count() -> u32 {
2337
+ if cfg!(feature = "profiler") {
2338
+ get_jit_state().wasm_table_index_free_list.len() as u32
2339
+ }
2340
+ else {
2341
+ 0
2342
+ }
2343
+ }
2344
+ #[no_mangle]
2345
+ pub fn jit_get_cache_size() -> u32 {
2346
+ if cfg!(feature = "profiler") {
2347
+ get_jit_state()
2348
+ .pages
2349
+ .values()
2350
+ .map(|p| p.entry_points.len() as u32)
2351
+ .sum()
2352
+ }
2353
+ else {
2354
+ 0
2355
+ }
2356
+ }
2357
+
2358
+ #[cfg(feature = "profiler")]
2359
+ pub fn check_missed_entry_points(phys_address: u32, state_flags: CachedStateFlags) {
2360
+ let ctx = get_jit_state();
2361
+
2362
+ if let Some(infos) = ctx.pages.get(&Page::page_of(phys_address)) {
2363
+ if infos.state_flags != state_flags {
2364
+ return;
2365
+ }
2366
+
2367
+ #[allow(static_mut_refs)]
2368
+ let last_jump_type = unsafe { cpu::debug_last_jump.name() };
2369
+ #[allow(static_mut_refs)]
2370
+ let last_jump_addr = unsafe { cpu::debug_last_jump.phys_address() }.unwrap_or(0);
2371
+ let last_jump_opcode =
2372
+ if last_jump_addr != 0 { memory::read32s(last_jump_addr) } else { 0 };
2373
+
2374
+ let opcode = memory::read32s(phys_address);
2375
+ dbg_log!(
2376
+ "Compiled exists, but no entry point, \
2377
+ phys_addr={:x} opcode={:02x} {:02x} {:02x} {:02x}. \
2378
+ Last jump at {:x} ({}) opcode={:02x} {:02x} {:02x} {:02x}",
2379
+ phys_address,
2380
+ opcode & 0xFF,
2381
+ opcode >> 8 & 0xFF,
2382
+ opcode >> 16 & 0xFF,
2383
+ opcode >> 16 & 0xFF,
2384
+ last_jump_addr,
2385
+ last_jump_type,
2386
+ last_jump_opcode & 0xFF,
2387
+ last_jump_opcode >> 8 & 0xFF,
2388
+ last_jump_opcode >> 16 & 0xFF,
2389
+ last_jump_opcode >> 16 & 0xFF,
2390
+ );
2391
+ }
2392
+ }
2393
+
2394
+ #[no_mangle]
2395
+ #[cfg(feature = "profiler")]
2396
+ pub fn debug_set_dispatcher_target(_target_index: i32) {
2397
+ //dbg_log!("About to call dispatcher target_index={}", target_index);
2398
+ }
2399
+
2400
+ #[no_mangle]
2401
+ #[cfg(feature = "profiler")]
2402
+ pub fn check_dispatcher_target(target_index: i32, max: i32) {
2403
+ //dbg_log!("Dispatcher called target={}", target_index);
2404
+ dbg_assert!(target_index >= 0);
2405
+ dbg_assert!(target_index < max);
2406
+ }
2407
+
2408
+ #[no_mangle]
2409
+ #[cfg(feature = "profiler")]
2410
+ pub fn enter_basic_block(phys_eip: u32) {
2411
+ let eip =
2412
+ unsafe { cpu::translate_address_read(*global_pointers::instruction_pointer).unwrap() };
2413
+ if Page::page_of(eip) != Page::page_of(phys_eip) {
2414
+ dbg_log!(
2415
+ "enter basic block failed block=0x{:x} actual eip=0x{:x}",
2416
+ phys_eip,
2417
+ eip
2418
+ );
2419
+ panic!();
2420
+ }
2421
+ }
2422
+
2423
+ #[no_mangle]
2424
+ pub unsafe fn set_jit_config(index: u32, value: u32) {
2425
+ match index {
2426
+ 0 => JIT_DISABLED = value != 0,
2427
+ 1 => MAX_PAGES = value,
2428
+ 2 => JIT_USE_LOOP_SAFETY = value != 0,
2429
+ 3 => MAX_EXTRA_BASIC_BLOCKS = value,
2430
+ _ => dbg_assert!(false),
2431
+ }
2432
+ }
2433
+
2434
+ #[no_mangle]
2435
+ pub unsafe fn get_jit_config(index: u32) -> u32 {
2436
+ match index {
2437
+ 0 => JIT_DISABLED as u32,
2438
+ 1 => MAX_PAGES as u32,
2439
+ 2 => JIT_USE_LOOP_SAFETY as u32,
2440
+ 3 => MAX_EXTRA_BASIC_BLOCKS as u32,
2441
+ _ => 0,
2442
+ }
2443
+ }