@aptre/v86 0.6.0 → 0.6.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aptre/v86",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "license": "BSD-2-Clause",
5
5
  "description": "x86 PC emulator and x86-to-wasm JIT, running in the browser",
6
6
  "homepage": "https://github.com/aperturerobotics/v86",
@@ -105,7 +105,23 @@ export class V86 {
105
105
 
106
106
  let cpu: any
107
107
 
108
- let wasm_memory: any
108
+ // Reserve VA upfront so memory.grow() is fast (no realloc+copy).
109
+ // Without maximum, engines must reallocate on every grow which
110
+ // is expensive and can OOM for large guest memories.
111
+ const memory_size = options.memory_size || 64 * 1024 * 1024
112
+ const vga_memory_size = options.vga_memory_size || 8 * 1024 * 1024
113
+ const memory_max =
114
+ options.memory_max || (memory_size + vga_memory_size) * 4
115
+ const WASM_PAGE_SIZE = 65536
116
+ const wasm_initial_pages = 256
117
+ const wasm_max_pages = Math.max(
118
+ wasm_initial_pages,
119
+ Math.min(Math.ceil(memory_max / WASM_PAGE_SIZE), 65536),
120
+ )
121
+ const wasm_memory = new WebAssembly.Memory({
122
+ initial: wasm_initial_pages,
123
+ maximum: wasm_max_pages,
124
+ })
109
125
 
110
126
  const wasm_table = new WebAssembly.Table({
111
127
  element: 'anyfunc',
@@ -113,6 +129,7 @@ export class V86 {
113
129
  })
114
130
 
115
131
  const wasm_shared_funcs: Record<string, any> = {
132
+ memory: wasm_memory,
116
133
  cpu_exception_hook: (n: number) => this.cpu_exception_hook(n),
117
134
 
118
135
  run_hardware_timers: function (a: any, t: any) {
@@ -280,7 +297,6 @@ export class V86 {
280
297
  }
281
298
 
282
299
  wasm_fn({ env: wasm_shared_funcs }).then((exports: WasmExports) => {
283
- wasm_memory = exports.memory
284
300
  exports['rust_init']()
285
301
 
286
302
  const emulator = (this.v86 = new v86(this.emulator_bus, {
@@ -957,6 +973,30 @@ export class V86 {
957
973
  this.v86.run()
958
974
  }
959
975
 
976
+ /**
977
+ * Grow guest memory to the specified size in bytes. Stops the VM,
978
+ * grows WASM linear memory, updates memory_size, clears the TLB,
979
+ * and resumes execution.
980
+ */
981
+ async growMemory(newSizeBytes: number): Promise<void> {
982
+ const cpu = this.v86.cpu
983
+ if (newSizeBytes <= cpu.memory_size[0]) {
984
+ return
985
+ }
986
+
987
+ const wasRunning = this.cpu_is_running
988
+ if (wasRunning) {
989
+ await this.stop()
990
+ }
991
+
992
+ cpu.resize_memory(newSizeBytes)
993
+ cpu.full_clear_tlb()
994
+
995
+ if (wasRunning) {
996
+ await this.run()
997
+ }
998
+ }
999
+
960
1000
  /**
961
1001
  * Stop emulation. Do nothing if emulator is not running. Can be asynchronous.
962
1002
  */
package/src/cpu.ts CHANGED
@@ -155,6 +155,7 @@ export class CPU {
155
155
  memory_size!: Int32Array
156
156
 
157
157
  mem8: Uint8Array
158
+ mem8_offset: number = 0
158
159
  mem32s: Int32Array
159
160
 
160
161
  segment_is_null!: Uint8Array
@@ -347,148 +348,111 @@ export class CPU {
347
348
  this.wasm_patch()
348
349
  this.create_jit_imports()
349
350
 
350
- const memory = this.wasm_memory
351
-
352
- this.memory_size = view(Uint32Array, memory, 812, 1)
353
-
354
351
  this.mem8 = new Uint8Array(0)
355
352
  this.mem32s = new Int32Array(this.mem8.buffer)
356
353
 
354
+ this.rebuild_wasm_views()
355
+
356
+ // @ts-expect-error Devices are populated during init()
357
+ this.devices = {}
358
+
359
+ // managed in io.js
360
+ this.memory_map_read8 = []
361
+ this.memory_map_write8 = []
362
+ this.memory_map_read32 = []
363
+ this.memory_map_write32 = []
364
+
365
+ this.bios = {
366
+ main: null,
367
+ vga: null,
368
+ }
369
+
370
+ this.fpu_stack_empty[0] = 0xff
371
+ this.fpu_stack_ptr[0] = 0
372
+
373
+ this.fpu_control_word[0] = 0x37f
374
+ this.fpu_status_word[0] = 0
375
+ this.fpu_ip[0] = 0
376
+ this.fpu_ip_selector[0] = 0
377
+ this.fpu_opcode[0] = 0
378
+ this.fpu_dp[0] = 0
379
+ this.fpu_dp_selector[0] = 0
380
+
381
+ this.fw_value = []
382
+ this.fw_pointer = 0
383
+ this.option_roms = []
384
+
385
+ this.io = undefined!
386
+
387
+ this.bus = bus
388
+
389
+ this.set_tsc(0, 0)
390
+
391
+ if (DEBUG) {
392
+ this.seen_code = {}
393
+ this.seen_code_uncompiled = {}
394
+ }
395
+ }
396
+
397
+ /**
398
+ * Rebuild all TypedArray views into WASM linear memory.
399
+ * Must be called after any wasm_memory.grow() since growth
400
+ * detaches the old ArrayBuffer, invalidating all views.
401
+ */
402
+ rebuild_wasm_views(): void {
403
+ const memory = this.wasm_memory
404
+ this.memory_size = view(Uint32Array, memory, 812, 1)
357
405
  this.segment_is_null = view(Uint8Array, memory, 724, 8)
358
406
  this.segment_offsets = view(Int32Array, memory, 736, 8)
359
407
  this.segment_limits = view(Uint32Array, memory, 768, 8)
360
408
  this.segment_access_bytes = view(Uint8Array, memory, 512, 8)
361
-
362
- // Whether or not in protected mode
363
409
  this.protected_mode = view(Int32Array, memory, 800, 1)
364
-
365
410
  this.idtr_size = view(Int32Array, memory, 564, 1)
366
411
  this.idtr_offset = view(Int32Array, memory, 568, 1)
367
-
368
- // global descriptor table register
369
412
  this.gdtr_size = view(Int32Array, memory, 572, 1)
370
413
  this.gdtr_offset = view(Int32Array, memory, 576, 1)
371
-
372
414
  this.tss_size_32 = view(Int32Array, memory, 1128, 1)
373
-
374
- // whether or not a page fault occured
375
415
  this.page_fault = view(Uint32Array, memory, 540, 8)
376
-
377
416
  this.cr = view(Int32Array, memory, 580, 8)
378
-
379
- // current privilege level
380
417
  this.cpl = view(Uint8Array, memory, 612, 1)
381
-
382
- // current operand/address size
383
418
  this.is_32 = view(Int32Array, memory, 804, 1)
384
-
385
419
  this.stack_size_32 = view(Int32Array, memory, 808, 1)
386
-
387
- // Was the last instruction a hlt?
388
420
  this.in_hlt = view(Uint8Array, memory, 616, 1)
389
-
390
421
  this.last_virt_eip = view(Int32Array, memory, 620, 1)
391
422
  this.eip_phys = view(Int32Array, memory, 624, 1)
392
-
393
423
  this.sysenter_cs = view(Int32Array, memory, 636, 1)
394
-
395
424
  this.sysenter_esp = view(Int32Array, memory, 640, 1)
396
-
397
425
  this.sysenter_eip = view(Int32Array, memory, 644, 1)
398
-
399
426
  this.prefixes = view(Int32Array, memory, 648, 1)
400
-
401
427
  this.flags = view(Int32Array, memory, 120, 1)
402
-
403
- // bitmap of flags which are not updated in the flags variable
404
- // changed by arithmetic instructions, so only relevant to arithmetic flags
405
428
  this.flags_changed = view(Int32Array, memory, 100, 1)
406
-
407
- // enough infos about the last arithmetic operation to compute eflags
408
429
  this.last_op_size = view(Int32Array, memory, 96, 1)
409
430
  this.last_op1 = view(Int32Array, memory, 104, 1)
410
431
  this.last_result = view(Int32Array, memory, 112, 1)
411
-
412
- this.current_tsc = view(Uint32Array, memory, 960, 2) // 64 bit
413
-
414
- // @ts-expect-error Devices are populated during init()
415
- this.devices = {}
416
-
432
+ this.current_tsc = view(Uint32Array, memory, 960, 2)
417
433
  this.instruction_pointer = view(Int32Array, memory, 556, 1)
418
434
  this.previous_ip = view(Int32Array, memory, 560, 1)
419
-
420
- // configured by guest
421
435
  this.apic_enabled = view(Uint8Array, memory, 548, 1)
422
- // configured when the emulator starts (changes bios initialisation)
423
436
  this.acpi_enabled = view(Uint8Array, memory, 552, 1)
424
-
425
- // managed in io.js
426
- this.memory_map_read8 = []
427
- this.memory_map_write8 = []
428
- this.memory_map_read32 = []
429
- this.memory_map_write32 = []
430
-
431
- this.bios = {
432
- main: null,
433
- vga: null,
434
- }
435
-
436
437
  this.instruction_counter = view(Uint32Array, memory, 664, 1)
437
-
438
- // registers
439
438
  this.reg32 = view(Int32Array, memory, 64, 8)
440
-
441
439
  this.fpu_st = view(Int32Array, memory, 1152, 4 * 8)
442
-
443
440
  this.fpu_stack_empty = view(Uint8Array, memory, 816, 1)
444
- this.fpu_stack_empty[0] = 0xff
445
441
  this.fpu_stack_ptr = view(Uint8Array, memory, 1032, 1)
446
- this.fpu_stack_ptr[0] = 0
447
-
448
442
  this.fpu_control_word = view(Uint16Array, memory, 1036, 1)
449
- this.fpu_control_word[0] = 0x37f
450
443
  this.fpu_status_word = view(Uint16Array, memory, 1040, 1)
451
- this.fpu_status_word[0] = 0
452
444
  this.fpu_ip = view(Int32Array, memory, 1048, 1)
453
- this.fpu_ip[0] = 0
454
445
  this.fpu_ip_selector = view(Int32Array, memory, 1052, 1)
455
- this.fpu_ip_selector[0] = 0
456
446
  this.fpu_opcode = view(Int32Array, memory, 1044, 1)
457
- this.fpu_opcode[0] = 0
458
447
  this.fpu_dp = view(Int32Array, memory, 1056, 1)
459
- this.fpu_dp[0] = 0
460
448
  this.fpu_dp_selector = view(Int32Array, memory, 1060, 1)
461
- this.fpu_dp_selector[0] = 0
462
-
463
449
  this.reg_xmm32s = view(Int32Array, memory, 832, 8 * 4)
464
-
465
450
  this.mxcsr = view(Int32Array, memory, 824, 1)
466
-
467
- // segment registers, tr and ldtr
468
451
  this.sreg = view(Uint16Array, memory, 668, 8)
469
-
470
- // debug registers
471
452
  this.dreg = view(Int32Array, memory, 684, 8)
472
-
473
453
  this.reg_pdpte = view(Int32Array, memory, 968, 8)
474
-
475
454
  this.svga_dirty_bitmap_min_offset = view(Uint32Array, memory, 716, 1)
476
455
  this.svga_dirty_bitmap_max_offset = view(Uint32Array, memory, 720, 1)
477
-
478
- this.fw_value = []
479
- this.fw_pointer = 0
480
- this.option_roms = []
481
-
482
- this.io = undefined!
483
-
484
- this.bus = bus
485
-
486
- this.set_tsc(0, 0)
487
-
488
- if (DEBUG) {
489
- this.seen_code = {}
490
- this.seen_code_uncompiled = {}
491
- }
492
456
  }
493
457
 
494
458
  mmap_read8(addr: number): number {
@@ -887,7 +851,7 @@ export class CPU {
887
851
  }
888
852
 
889
853
  resize_memory(new_size: number): void {
890
- const mem8_offset = this.mem8.byteOffset
854
+ const mem8_offset = this.mem8_offset
891
855
  const needed_total = mem8_offset + new_size
892
856
  const current_buffer = this.wasm_memory.buffer.byteLength
893
857
  if (needed_total > current_buffer) {
@@ -895,6 +859,7 @@ export class CPU {
895
859
  (needed_total - current_buffer) / WASM_PAGE_SIZE,
896
860
  )
897
861
  this.wasm_memory.grow(grow_pages)
862
+ this.rebuild_wasm_views()
898
863
  }
899
864
  this.mem8 = view(Uint8Array, this.wasm_memory, mem8_offset, new_size)
900
865
  this.mem32s = view(
@@ -907,7 +872,12 @@ export class CPU {
907
872
  }
908
873
 
909
874
  set_state(state: any[]): void {
910
- this.memory_size[0] = state[0]
875
+ const saved_memory_size = state[0]
876
+ if (saved_memory_size > this.memory_size[0]) {
877
+ this.resize_memory(saved_memory_size)
878
+ } else {
879
+ this.memory_size[0] = saved_memory_size
880
+ }
911
881
 
912
882
  if (this.mem8.length !== this.memory_size[0]) {
913
883
  console.warn(
@@ -1275,9 +1245,10 @@ export class CPU {
1275
1245
  'Expected uninitialised memory',
1276
1246
  )
1277
1247
 
1278
- this.memory_size[0] = size
1279
-
1280
1248
  const memory_offset = this.allocate_memory(size)
1249
+ this.rebuild_wasm_views()
1250
+ this.memory_size[0] = size
1251
+ this.mem8_offset = memory_offset
1281
1252
 
1282
1253
  this.mem8 = view(Uint8Array, this.wasm_memory, memory_offset, size)
1283
1254
  this.mem32s = view(
@@ -1586,6 +1557,25 @@ export class CPU {
1586
1557
  }
1587
1558
 
1588
1559
  this.debug_init()
1560
+
1561
+ // Rebuild all WASM memory views. During init, allocate_memory
1562
+ // and svga_allocate_memory may trigger memory.grow (from Rust),
1563
+ // which detaches the old ArrayBuffer and invalidates all views.
1564
+ this.rebuild_wasm_views()
1565
+ if (this.mem8_offset > 0) {
1566
+ this.mem8 = view(
1567
+ Uint8Array,
1568
+ this.wasm_memory,
1569
+ this.mem8_offset,
1570
+ this.memory_size[0],
1571
+ )
1572
+ this.mem32s = view(
1573
+ Uint32Array,
1574
+ this.wasm_memory,
1575
+ this.mem8_offset,
1576
+ this.memory_size[0] >> 2,
1577
+ )
1578
+ }
1589
1579
  }
1590
1580
 
1591
1581
  load_multiboot(buffer: ArrayBuffer): void {