@aptre/v86 0.5.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/lib/9p.ts CHANGED
@@ -311,7 +311,7 @@ export class Virtio9p {
311
311
 
312
312
  const state = { offset: 0 }
313
313
  const header = marshall.Unmarshall(['w', 'b', 'h'], buffer, state)
314
- let size = header[0]
314
+ let size
315
315
  const id = header[1]
316
316
  const tag = header[2]
317
317
 
@@ -382,7 +382,7 @@ export class Virtio9p {
382
382
  )
383
383
 
384
384
  if (ret < 0) {
385
- let error_message = ''
385
+ let error_message: string
386
386
  if (ret === -EPERM)
387
387
  error_message = 'Operation not permitted'
388
388
  else {
@@ -984,7 +984,7 @@ export class Virtio9p {
984
984
  newname,
985
985
  )
986
986
  if (ret < 0) {
987
- let error_message = ''
987
+ let error_message: string
988
988
  if (ret === -ENOENT)
989
989
  error_message = 'No such file or directory'
990
990
  else if (ret === -EPERM)
@@ -1040,7 +1040,7 @@ export class Virtio9p {
1040
1040
  }
1041
1041
  const ret = this.fs.Unlink(this.fids[dirfd].inodeid, name)
1042
1042
  if (ret < 0) {
1043
- let error_message = ''
1043
+ let error_message: string
1044
1044
  if (ret === -ENOTEMPTY)
1045
1045
  error_message = 'Directory not empty'
1046
1046
  else if (ret === -EPERM)
package/lib/filesystem.ts CHANGED
@@ -1213,7 +1213,7 @@ export class FS {
1213
1213
  let parentid = -1
1214
1214
  let id = 0
1215
1215
  let forward_path: string | null = null
1216
- let i = 0
1216
+ let i
1217
1217
  for (i = 0; i < n; i++) {
1218
1218
  parentid = id
1219
1219
  id = this.Search(parentid, walk[i])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aptre/v86",
3
- "version": "0.5.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",
@@ -56,20 +56,20 @@
56
56
  "./{src,lib,gen,tests}/**/*.{ts,js}": "prettier --config .prettierrc.yaml --write"
57
57
  },
58
58
  "devDependencies": {
59
- "@eslint/js": "^9.27.0",
59
+ "@eslint/js": "^10.0.0",
60
60
  "@types/node": "^25.5.0",
61
61
  "@typescript/native-preview": "^7.0.0-dev.20250601",
62
- "esbuild": "^0.25.4",
63
- "eslint": "^9.27.0",
62
+ "esbuild": "^0.27.0",
63
+ "eslint": "^10.0.0",
64
64
  "eslint-config-prettier": "^10.1.5",
65
65
  "eslint-plugin-unused-imports": "^4.4.1",
66
- "globals": "^16.1.0",
66
+ "globals": "^17.0.0",
67
67
  "husky": "^9.1.7",
68
68
  "lint-staged": "^16.0.0",
69
69
  "prettier": "^3.5.3",
70
70
  "rimraf": "^6.0.1",
71
- "typescript": "^5.8.3",
71
+ "typescript": "^6.0.0",
72
72
  "typescript-eslint": "^8.32.1",
73
- "vitest": "^3.1.4"
73
+ "vitest": "^4.0.0"
74
74
  }
75
75
  }
@@ -45,21 +45,11 @@ type FileDescriptor = any
45
45
 
46
46
  type AutoStep = any
47
47
 
48
- class FileExistsError {
49
- message: string
48
+ class FileNotFoundError extends Error {
50
49
  constructor(message?: string) {
51
- this.message = message || 'File already exists'
50
+ super(message || 'File not found')
52
51
  }
53
52
  }
54
- FileExistsError.prototype = Error.prototype
55
-
56
- class FileNotFoundError {
57
- message: string
58
- constructor(message?: string) {
59
- this.message = message || 'File not found'
60
- }
61
- }
62
- FileNotFoundError.prototype = Error.prototype
63
53
 
64
54
  /**
65
55
  * Constructor for emulator instances.
@@ -115,14 +105,31 @@ export class V86 {
115
105
 
116
106
  let cpu: any
117
107
 
118
- 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
+ })
119
125
 
120
126
  const wasm_table = new WebAssembly.Table({
121
127
  element: 'anyfunc',
122
128
  initial: WASM_TABLE_SIZE + WASM_TABLE_OFFSET,
123
129
  })
124
130
 
125
- const wasm_shared_funcs = {
131
+ const wasm_shared_funcs: Record<string, any> = {
132
+ memory: wasm_memory,
126
133
  cpu_exception_hook: (n: number) => this.cpu_exception_hook(n),
127
134
 
128
135
  run_hardware_timers: function (a: any, t: any) {
@@ -231,8 +238,6 @@ export class V86 {
231
238
 
232
239
  if (!wasm_fn) {
233
240
  wasm_fn = (env: any) => {
234
- /* global __dirname */
235
-
236
241
  return new Promise((resolve) => {
237
242
  let v86_bin = DEBUG ? 'v86-debug.wasm' : 'v86.wasm'
238
243
  let v86_bin_fallback = 'v86-fallback.wasm'
@@ -243,12 +248,11 @@ export class V86 {
243
248
  'v86.wasm',
244
249
  'v86-fallback.wasm',
245
250
  )
246
- } else if (
247
- typeof window === 'undefined' &&
248
- typeof __dirname === 'string'
249
- ) {
250
- v86_bin = __dirname + '/' + v86_bin
251
- v86_bin_fallback = __dirname + '/' + v86_bin_fallback
251
+ } else if (typeof window === 'undefined') {
252
+ // Node/Bun: resolve WASM relative to project root build/
253
+ const root = new URL('../../', import.meta.url).pathname
254
+ v86_bin = root + 'build/' + v86_bin
255
+ v86_bin_fallback = root + 'build/' + v86_bin_fallback
252
256
  } else {
253
257
  v86_bin = 'build/' + v86_bin
254
258
  v86_bin_fallback = 'build/' + v86_bin_fallback
@@ -293,12 +297,12 @@ export class V86 {
293
297
  }
294
298
 
295
299
  wasm_fn({ env: wasm_shared_funcs }).then((exports: WasmExports) => {
296
- wasm_memory = exports.memory
297
300
  exports['rust_init']()
298
301
 
299
302
  const emulator = (this.v86 = new v86(this.emulator_bus, {
300
303
  exports,
301
304
  wasm_table,
305
+ wasm_memory,
302
306
  }))
303
307
  cpu = emulator.cpu
304
308
 
@@ -356,7 +360,9 @@ export class V86 {
356
360
  settings.mac_address_translation = options.mac_address_translation
357
361
  settings.cpuid_level = options.cpuid_level
358
362
  settings.virtio_balloon = options.virtio_balloon
363
+ settings.virtio_mem = options.virtio_mem
359
364
  settings.virtio_console = !!options.virtio_console
365
+ settings.virtio_v86fs = !!options.virtio_v86fs
360
366
 
361
367
  const relay_url =
362
368
  options.network_relay_url ||
@@ -967,6 +973,30 @@ export class V86 {
967
973
  this.v86.run()
968
974
  }
969
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
+
970
1000
  /**
971
1001
  * Stop emulation. Do nothing if emulator is not running. Can be asynchronous.
972
1002
  */
package/src/const.ts CHANGED
@@ -120,6 +120,8 @@ export const WASM_TABLE_SIZE = 900
120
120
 
121
121
  export const WASM_TABLE_OFFSET = 1024
122
122
 
123
+ export const WASM_PAGE_SIZE = 65536
124
+
123
125
  export const MIXER_CHANNEL_LEFT = 0
124
126
  export const MIXER_CHANNEL_RIGHT = 1
125
127
  export const MIXER_CHANNEL_BOTH = 2
package/src/cpu.ts CHANGED
@@ -46,6 +46,7 @@ import {
46
46
  FLAG_DIRECTION,
47
47
  FLAG_OVERFLOW,
48
48
  FLAG_PARITY,
49
+ WASM_PAGE_SIZE,
49
50
  } from './const.js'
50
51
  import { h, view, pads, Bitmap, dump_file } from './lib.js'
51
52
  import { dbg_assert, dbg_log } from './log.js'
@@ -67,6 +68,8 @@ import { IDEController } from './ide.js'
67
68
  import { VirtioNet } from './virtio_net.js'
68
69
  import { VGAScreen } from './vga.js'
69
70
  import { VirtioBalloon } from './virtio_balloon.js'
71
+ import { VirtioMem } from './virtio_mem.js'
72
+ import { VirtioV86FS } from './virtio_v86fs.js'
70
73
  import { Virtio9p, Virtio9pHandler, Virtio9pProxy } from '../lib/9p.js'
71
74
 
72
75
  import { load_kernel } from './kernel.js'
@@ -129,6 +132,8 @@ interface CPUDevices {
129
132
  virtio_console: VirtioConsole
130
133
  virtio_net: VirtioNet
131
134
  virtio_balloon: VirtioBalloon
135
+ virtio_mem: VirtioMem
136
+ virtio_v86fs: VirtioV86FS
132
137
  }
133
138
 
134
139
  interface OptionRom {
@@ -139,6 +144,7 @@ interface OptionRom {
139
144
  interface WasmModule {
140
145
  exports: Record<string, any>
141
146
  wasm_table: WebAssembly.Table
147
+ wasm_memory: WebAssembly.Memory
142
148
  }
143
149
 
144
150
  export class CPU {
@@ -149,6 +155,7 @@ export class CPU {
149
155
  memory_size!: Int32Array
150
156
 
151
157
  mem8: Uint8Array
158
+ mem8_offset: number = 0
152
159
  mem32s: Int32Array
153
160
 
154
161
  segment_is_null!: Uint8Array
@@ -268,7 +275,6 @@ export class CPU {
268
275
  get_eflags!: () => number
269
276
  handle_irqs!: () => void
270
277
  main_loop!: () => number
271
- reboot_internal!: () => void
272
278
  set_jit_config!: (a: number, b: number) => void
273
279
 
274
280
  read8!: (addr: number) => number
@@ -338,153 +344,115 @@ export class CPU {
338
344
  this.name = 'cpu'
339
345
  this.stop_idling = stop_idling
340
346
  this.wm = wm
347
+ this.wasm_memory = wm.wasm_memory
341
348
  this.wasm_patch()
342
349
  this.create_jit_imports()
343
350
 
344
- const memory = this.wm.exports['memory']
351
+ this.mem8 = new Uint8Array(0)
352
+ this.mem32s = new Int32Array(this.mem8.buffer)
345
353
 
346
- this.wasm_memory = memory
354
+ this.rebuild_wasm_views()
347
355
 
348
- this.memory_size = view(Uint32Array, memory, 812, 1)
356
+ // @ts-expect-error Devices are populated during init()
357
+ this.devices = {}
349
358
 
350
- this.mem8 = new Uint8Array(0)
351
- this.mem32s = new Int32Array(this.mem8.buffer)
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 = []
352
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)
353
405
  this.segment_is_null = view(Uint8Array, memory, 724, 8)
354
406
  this.segment_offsets = view(Int32Array, memory, 736, 8)
355
407
  this.segment_limits = view(Uint32Array, memory, 768, 8)
356
408
  this.segment_access_bytes = view(Uint8Array, memory, 512, 8)
357
-
358
- // Whether or not in protected mode
359
409
  this.protected_mode = view(Int32Array, memory, 800, 1)
360
-
361
410
  this.idtr_size = view(Int32Array, memory, 564, 1)
362
411
  this.idtr_offset = view(Int32Array, memory, 568, 1)
363
-
364
- // global descriptor table register
365
412
  this.gdtr_size = view(Int32Array, memory, 572, 1)
366
413
  this.gdtr_offset = view(Int32Array, memory, 576, 1)
367
-
368
414
  this.tss_size_32 = view(Int32Array, memory, 1128, 1)
369
-
370
- // whether or not a page fault occured
371
415
  this.page_fault = view(Uint32Array, memory, 540, 8)
372
-
373
416
  this.cr = view(Int32Array, memory, 580, 8)
374
-
375
- // current privilege level
376
417
  this.cpl = view(Uint8Array, memory, 612, 1)
377
-
378
- // current operand/address size
379
418
  this.is_32 = view(Int32Array, memory, 804, 1)
380
-
381
419
  this.stack_size_32 = view(Int32Array, memory, 808, 1)
382
-
383
- // Was the last instruction a hlt?
384
420
  this.in_hlt = view(Uint8Array, memory, 616, 1)
385
-
386
421
  this.last_virt_eip = view(Int32Array, memory, 620, 1)
387
422
  this.eip_phys = view(Int32Array, memory, 624, 1)
388
-
389
423
  this.sysenter_cs = view(Int32Array, memory, 636, 1)
390
-
391
424
  this.sysenter_esp = view(Int32Array, memory, 640, 1)
392
-
393
425
  this.sysenter_eip = view(Int32Array, memory, 644, 1)
394
-
395
426
  this.prefixes = view(Int32Array, memory, 648, 1)
396
-
397
427
  this.flags = view(Int32Array, memory, 120, 1)
398
-
399
- // bitmap of flags which are not updated in the flags variable
400
- // changed by arithmetic instructions, so only relevant to arithmetic flags
401
428
  this.flags_changed = view(Int32Array, memory, 100, 1)
402
-
403
- // enough infos about the last arithmetic operation to compute eflags
404
429
  this.last_op_size = view(Int32Array, memory, 96, 1)
405
430
  this.last_op1 = view(Int32Array, memory, 104, 1)
406
431
  this.last_result = view(Int32Array, memory, 112, 1)
407
-
408
- this.current_tsc = view(Uint32Array, memory, 960, 2) // 64 bit
409
-
410
- // @ts-expect-error Devices are populated during init()
411
- this.devices = {}
412
-
432
+ this.current_tsc = view(Uint32Array, memory, 960, 2)
413
433
  this.instruction_pointer = view(Int32Array, memory, 556, 1)
414
434
  this.previous_ip = view(Int32Array, memory, 560, 1)
415
-
416
- // configured by guest
417
435
  this.apic_enabled = view(Uint8Array, memory, 548, 1)
418
- // configured when the emulator starts (changes bios initialisation)
419
436
  this.acpi_enabled = view(Uint8Array, memory, 552, 1)
420
-
421
- // managed in io.js
422
- this.memory_map_read8 = []
423
- this.memory_map_write8 = []
424
- this.memory_map_read32 = []
425
- this.memory_map_write32 = []
426
-
427
- this.bios = {
428
- main: null,
429
- vga: null,
430
- }
431
-
432
437
  this.instruction_counter = view(Uint32Array, memory, 664, 1)
433
-
434
- // registers
435
438
  this.reg32 = view(Int32Array, memory, 64, 8)
436
-
437
439
  this.fpu_st = view(Int32Array, memory, 1152, 4 * 8)
438
-
439
440
  this.fpu_stack_empty = view(Uint8Array, memory, 816, 1)
440
- this.fpu_stack_empty[0] = 0xff
441
441
  this.fpu_stack_ptr = view(Uint8Array, memory, 1032, 1)
442
- this.fpu_stack_ptr[0] = 0
443
-
444
442
  this.fpu_control_word = view(Uint16Array, memory, 1036, 1)
445
- this.fpu_control_word[0] = 0x37f
446
443
  this.fpu_status_word = view(Uint16Array, memory, 1040, 1)
447
- this.fpu_status_word[0] = 0
448
444
  this.fpu_ip = view(Int32Array, memory, 1048, 1)
449
- this.fpu_ip[0] = 0
450
445
  this.fpu_ip_selector = view(Int32Array, memory, 1052, 1)
451
- this.fpu_ip_selector[0] = 0
452
446
  this.fpu_opcode = view(Int32Array, memory, 1044, 1)
453
- this.fpu_opcode[0] = 0
454
447
  this.fpu_dp = view(Int32Array, memory, 1056, 1)
455
- this.fpu_dp[0] = 0
456
448
  this.fpu_dp_selector = view(Int32Array, memory, 1060, 1)
457
- this.fpu_dp_selector[0] = 0
458
-
459
449
  this.reg_xmm32s = view(Int32Array, memory, 832, 8 * 4)
460
-
461
450
  this.mxcsr = view(Int32Array, memory, 824, 1)
462
-
463
- // segment registers, tr and ldtr
464
451
  this.sreg = view(Uint16Array, memory, 668, 8)
465
-
466
- // debug registers
467
452
  this.dreg = view(Int32Array, memory, 684, 8)
468
-
469
453
  this.reg_pdpte = view(Int32Array, memory, 968, 8)
470
-
471
454
  this.svga_dirty_bitmap_min_offset = view(Uint32Array, memory, 716, 1)
472
455
  this.svga_dirty_bitmap_max_offset = view(Uint32Array, memory, 720, 1)
473
-
474
- this.fw_value = []
475
- this.fw_pointer = 0
476
- this.option_roms = []
477
-
478
- this.io = undefined!
479
-
480
- this.bus = bus
481
-
482
- this.set_tsc(0, 0)
483
-
484
- if (DEBUG) {
485
- this.seen_code = {}
486
- this.seen_code_uncompiled = {}
487
- }
488
456
  }
489
457
 
490
458
  mmap_read8(addr: number): number {
@@ -576,7 +544,7 @@ export class CPU {
576
544
 
577
545
  const jit_imports = Object.create(null)
578
546
 
579
- jit_imports['m'] = this.wm.exports['memory']
547
+ jit_imports['m'] = this.wasm_memory
580
548
 
581
549
  for (const name of Object.keys(this.wm.exports)) {
582
550
  if (
@@ -611,8 +579,6 @@ export class CPU {
611
579
 
612
580
  this.main_loop = get_import('main_loop')
613
581
 
614
- this.reboot_internal = get_import('reboot_internal')
615
-
616
582
  this.set_jit_config = get_import('set_jit_config')
617
583
 
618
584
  this.read8 = get_import('read8')
@@ -812,6 +778,8 @@ export class CPU {
812
778
  state[86] = this.last_result
813
779
  state[87] = this.fpu_status_word
814
780
  state[88] = this.mxcsr
781
+ state[89] = this.devices.virtio_mem
782
+ state[90] = this.devices.virtio_v86fs
815
783
 
816
784
  return state
817
785
  }
@@ -882,8 +850,34 @@ export class CPU {
882
850
  )
883
851
  }
884
852
 
853
+ resize_memory(new_size: number): void {
854
+ const mem8_offset = this.mem8_offset
855
+ const needed_total = mem8_offset + new_size
856
+ const current_buffer = this.wasm_memory.buffer.byteLength
857
+ if (needed_total > current_buffer) {
858
+ const grow_pages = Math.ceil(
859
+ (needed_total - current_buffer) / WASM_PAGE_SIZE,
860
+ )
861
+ this.wasm_memory.grow(grow_pages)
862
+ this.rebuild_wasm_views()
863
+ }
864
+ this.mem8 = view(Uint8Array, this.wasm_memory, mem8_offset, new_size)
865
+ this.mem32s = view(
866
+ Int32Array,
867
+ this.wasm_memory,
868
+ mem8_offset,
869
+ new_size >> 2,
870
+ )
871
+ this.memory_size[0] = new_size
872
+ }
873
+
885
874
  set_state(state: any[]): void {
886
- 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
+ }
887
881
 
888
882
  if (this.mem8.length !== this.memory_size[0]) {
889
883
  console.warn(
@@ -1007,6 +1001,10 @@ export class CPU {
1007
1001
  this.devices.virtio_net.set_state(state[83])
1008
1002
  if (this.devices.virtio_balloon)
1009
1003
  this.devices.virtio_balloon.set_state(state[84])
1004
+ if (this.devices.virtio_mem && state[89])
1005
+ this.devices.virtio_mem.set_state(state[89])
1006
+ if (this.devices.virtio_v86fs && state[90])
1007
+ this.devices.virtio_v86fs.set_state(state[90])
1010
1008
 
1011
1009
  this.fw_value = state[62]
1012
1010
 
@@ -1247,9 +1245,10 @@ export class CPU {
1247
1245
  'Expected uninitialised memory',
1248
1246
  )
1249
1247
 
1250
- this.memory_size[0] = size
1251
-
1252
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
1253
1252
 
1254
1253
  this.mem8 = view(Uint8Array, this.wasm_memory, memory_offset, size)
1255
1254
  this.mem32s = view(
@@ -1521,6 +1520,19 @@ export class CPU {
1521
1520
  device_bus,
1522
1521
  )
1523
1522
  }
1523
+ if (settings.virtio_mem) {
1524
+ const mem_cfg = settings.virtio_mem
1525
+ this.devices.virtio_mem = new VirtioMem(
1526
+ this,
1527
+ device_bus,
1528
+ mem_cfg.region_addr,
1529
+ mem_cfg.region_size,
1530
+ mem_cfg.block_size,
1531
+ )
1532
+ }
1533
+ if (settings.virtio_v86fs) {
1534
+ this.devices.virtio_v86fs = new VirtioV86FS(this, device_bus)
1535
+ }
1524
1536
 
1525
1537
  this.devices.sb16 = new SB16(this, device_bus)
1526
1538
  }
@@ -1545,6 +1557,25 @@ export class CPU {
1545
1557
  }
1546
1558
 
1547
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
+ }
1548
1579
  }
1549
1580
 
1550
1581
  load_multiboot(buffer: ArrayBuffer): void {
@@ -1858,7 +1889,6 @@ export class CPU {
1858
1889
  cpu.write32(multiboot_data + 4, ramdisk_top) // mod_end
1859
1890
  cpu.write32(multiboot_data + 8, 0) // string
1860
1891
  cpu.write32(multiboot_data + 12, 0) // reserved
1861
- multiboot_data += 16
1862
1892
 
1863
1893
  dbg_assert(ramdisk_top < cpu.memory_size[0])
1864
1894
 
package/src/pci.ts CHANGED
@@ -13,7 +13,7 @@ interface PCICpu {
13
13
  io: IO
14
14
  device_raise_irq(irq: number): void
15
15
  device_lower_irq(irq: number): void
16
- reboot_internal(): void
16
+ reboot(): void
17
17
  }
18
18
 
19
19
  // Mirrors IOPortEntry from io.ts (not exported there).
@@ -177,7 +177,7 @@ export class PCI {
177
177
  (out_byte & 0x06) === 0x06
178
178
  ) {
179
179
  dbg_log('CPU reboot via PCI')
180
- cpu.reboot_internal()
180
+ cpu.reboot()
181
181
  return
182
182
  }
183
183
 
@@ -304,6 +304,7 @@ export class PCI {
304
304
  0x00,
305
305
  0x00,
306
306
  0x00,
307
+ 0x00,
307
308
  PAM0,
308
309
  0x00,
309
310
  0x00,
@@ -711,7 +712,11 @@ export class PCI {
711
712
 
712
713
  // convert bytewise notation from lspci to double words
713
714
  const space = new Int32Array(64)
714
- space.set(new Int32Array(new Uint8Array(device.pci_space).buffer))
715
+ const pci_bytes = new Uint8Array(device.pci_space)
716
+ const aligned_len = pci_bytes.length & ~3
717
+ if (aligned_len > 0) {
718
+ space.set(new Int32Array(pci_bytes.buffer, 0, aligned_len >> 2))
719
+ }
715
720
  this.device_spaces[device_id] = space
716
721
  this.devices[device_id] = device
717
722
 
package/src/ps2.ts CHANGED
@@ -11,7 +11,7 @@ interface PS2Cpu {
11
11
  io: IO
12
12
  device_raise_irq(irq: number): void
13
13
  device_lower_irq(irq: number): void
14
- reboot_internal(): void
14
+ reboot(): void
15
15
  }
16
16
 
17
17
  type PS2State = [
@@ -808,7 +808,7 @@ export class PS2 {
808
808
  break
809
809
  case 0xfe:
810
810
  dbg_log('CPU reboot via PS2')
811
- this.cpu.reboot_internal()
811
+ this.cpu.reboot()
812
812
  break
813
813
  default:
814
814
  dbg_log(