Compare commits
6 commits
Author | SHA1 | Date | |
---|---|---|---|
c7a78029f9 | |||
dd6dbb9d79 | |||
0e2373957b | |||
1d942a585a | |||
9e8a810f45 | |||
df1a7129f4 |
10
Readme.md
10
Readme.md
|
@ -49,6 +49,16 @@ list of emulated hardware:
|
||||||
[KolibriOS](https://copy.sh/v86/?profile=kolibrios) —
|
[KolibriOS](https://copy.sh/v86/?profile=kolibrios) —
|
||||||
[QNX](https://copy.sh/v86/?profile=qnx)
|
[QNX](https://copy.sh/v86/?profile=qnx)
|
||||||
|
|
||||||
|
## Docs
|
||||||
|
|
||||||
|
[How it works](docs/how-it-works.md) —
|
||||||
|
[Networking](docs/networking.md) —
|
||||||
|
[Archlinux guest setup](docs/archlinux.md) —
|
||||||
|
[Windows 2000/XP guest setup](docs/windows-xp.md) —
|
||||||
|
[9p filesystem](docs/filesystem.md) —
|
||||||
|
[Linux rootfs on 9p](docs/linux-9p-image.md) —
|
||||||
|
[Profiling](docs/profiling.md)
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
Here's an overview of the operating systems supported in v86:
|
Here's an overview of the operating systems supported in v86:
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
A 9p filesystem is supported by the emulator, using a virtio transport. Using
|
A 9p filesystem is supported by the emulator, using a virtio transport. Using
|
||||||
it, files can be exchanged with the guest OS, see
|
it, files can be exchanged with the guest OS, see `create_file` and `read_file`
|
||||||
[`create_file`](/src/browser/starter.js#L1179-L1199)
|
in [`starter.js`](https://github.com/copy/v86/blob/master/src/browser/starter.js).
|
||||||
and
|
It can be enabled by passing the following options to `V86`:
|
||||||
[`read_file`](/src/browser/starter.js#L1209-L1228). It can
|
|
||||||
be enabled by passing the following options to `V86Starter`:
|
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
filesystem: {
|
filesystem: {
|
||||||
|
|
81
docs/how-it-works.md
Normal file
81
docs/how-it-works.md
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
Here's an overview of v86's workings. For details, check the
|
||||||
|
[source](https://github.com/copy/v86/tree/master/src).
|
||||||
|
|
||||||
|
The major limitations of WebAssembly are (for the purpose of making emulators with jit):
|
||||||
|
|
||||||
|
- structured control flow (no arbitrary jumps)
|
||||||
|
- no control over registers (you can't keep hardware registers in wasm locals across functions)
|
||||||
|
- no mmap (paging needs to be fully emulated)
|
||||||
|
- no patching
|
||||||
|
- module generation is fairly slow, but at least it's asynchronous, so other things can keep running
|
||||||
|
- there is some memory overhead per module, so you can't generate more than a few thousand
|
||||||
|
|
||||||
|
v86 has an interpreted mode, which collects entry points (targets of function
|
||||||
|
calls and indirect jumps). It also measures the hotness per page, so that
|
||||||
|
compilation is focused on code that is often executed. Once a page is
|
||||||
|
considered hot, code is generated for the entire page and up to `MAX_PAGES`
|
||||||
|
that are directly reachable from it.
|
||||||
|
|
||||||
|
v86 generates a single function with a big switch statement (brtable), to
|
||||||
|
ensure that all functions and targets of indirect jumps are reachable from
|
||||||
|
other modules. The remaining control flow is handled using the "stackifier"
|
||||||
|
algorithm (well-explained in
|
||||||
|
[this blog post](https://medium.com/leaningtech/solving-the-structured-control-flow-problem-once-and-for-all-5123117b1ee2)).
|
||||||
|
At the moment, there is no linking of wasm modules. The current module is
|
||||||
|
exited, and the main loop detects if a new module can be entered.
|
||||||
|
|
||||||
|
In practice, I found that browsers don't handle this structure (deep brtables,
|
||||||
|
with locals being used across the entire function) very well, and `MAX_PAGES`
|
||||||
|
has to be set to fairly low, otherwise memory usage blows up. It's likely that
|
||||||
|
improvements are possible (generating fewer entry points, splitting code across
|
||||||
|
multiple functions).
|
||||||
|
|
||||||
|
Code-generation happens in two passes. The first pass finds all basic block
|
||||||
|
boundaries, the second generates code for each basic block. Instruction
|
||||||
|
decoding is generated by a [set of
|
||||||
|
scripts](https://github.com/copy/v86/tree/master/gen) from a [table of
|
||||||
|
instructions](https://github.com/copy/v86/blob/master/gen/x86_table.js). It's
|
||||||
|
also used to [generate
|
||||||
|
tests](https://github.com/copy/v86/blob/master/tests/nasm/create_tests.js).
|
||||||
|
|
||||||
|
To handle paging, v86 generates code similar to this (see `gen_safe_read`):
|
||||||
|
|
||||||
|
```
|
||||||
|
entry <- tlb[addr >> 12 << 2]
|
||||||
|
if entry & MASK == TLB_VALID && (addr & 0xFFF) <= 0xFFC - bytes: goto fast
|
||||||
|
entry <- safe_read_jit_slow(addr, instruction_pointer)
|
||||||
|
if page_fault: goto exit-with-pagefault
|
||||||
|
fast: mem[(entry & ~0xFFF) ^ addr]
|
||||||
|
```
|
||||||
|
|
||||||
|
There is a 4 MB cache that acts like a tlb. It contains the physical address,
|
||||||
|
read-only bit, whether the page contains code (in order to invalidate it on
|
||||||
|
write) and whether the page points to mmio. Any of those cases are handled in
|
||||||
|
the slow path (`safe_read_jit_slow`), as well as walking the page tables and
|
||||||
|
triggering page faults. The fast path is taken in the vast majority of times.
|
||||||
|
|
||||||
|
The remaining code generation is mostly a straight-forward, 1-to-1 translation
|
||||||
|
of x86 to wasm. The only analysis done is to optimise generation of condional
|
||||||
|
jumps immediately after arithmetic instructions, e.g.:
|
||||||
|
|
||||||
|
```
|
||||||
|
cmp eax, 52
|
||||||
|
setb eax
|
||||||
|
```
|
||||||
|
|
||||||
|
becomes:
|
||||||
|
|
||||||
|
```
|
||||||
|
... // code for cmp
|
||||||
|
eax <- eax < 52
|
||||||
|
```
|
||||||
|
|
||||||
|
A lazy flag mechanism is used to speed arithmetic (applies to both jit and
|
||||||
|
interpreted mode, see
|
||||||
|
[`arith.rs`](https://github.com/copy/v86/blob/master/src/rust/cpu/arith.rs) and
|
||||||
|
[`misc_instr.rs`](https://github.com/copy/v86/blob/master/src/rust/cpu/misc_instr.rs)).
|
||||||
|
There's a wip that tries to elide most lazy-flags updates:
|
||||||
|
https://github.com/copy/v86/pull/466
|
||||||
|
|
||||||
|
FPU instructions are emulated using softfloat (very slow, but unfortunately
|
||||||
|
some code relies on 80 bit floats).
|
|
@ -1,7 +1,11 @@
|
||||||
|
# v86 networking
|
||||||
|
|
||||||
Emulating a network card is supported. It can be used by passing the
|
Emulating a network card is supported. It can be used by passing the
|
||||||
`network_relay_url` option to `V86Starter`. The url must point to a running
|
`network_relay_url` option to `V86`. The url must point to a running
|
||||||
WebSockets Proxy. The source code for WebSockets Proxy can be found at
|
WebSockets Proxy. The source code for WebSockets Proxy can be found at
|
||||||
https://github.com/benjamincburns/websockproxy.
|
[benjamincburns/websockproxy](https://github.com/benjamincburns/websockproxy).
|
||||||
|
An alternative, Node-based implementation is
|
||||||
|
[krishenriksen/node-relay](https://github.com/krishenriksen/node-relay).
|
||||||
|
|
||||||
The network card could also be controlled programatically, but this is
|
The network card could also be controlled programatically, but this is
|
||||||
currently not exposed.
|
currently not exposed.
|
||||||
|
@ -13,3 +17,31 @@ browser-compatible `WebSocket` constructor being present in the global scope.
|
||||||
throttling built-in by default which will degrade the networking.
|
throttling built-in by default which will degrade the networking.
|
||||||
`bellenottelling/websockproxy`docker image has this throttling removed via
|
`bellenottelling/websockproxy`docker image has this throttling removed via
|
||||||
[websockproxy/issues/4#issuecomment-317255890](https://github.com/benjamincburns/websockproxy/issues/4#issuecomment-317255890).
|
[websockproxy/issues/4#issuecomment-317255890](https://github.com/benjamincburns/websockproxy/issues/4#issuecomment-317255890).
|
||||||
|
|
||||||
|
### Interaction with state images
|
||||||
|
|
||||||
|
When using state images, v86 randomises the MAC address after the state has
|
||||||
|
been loaded, so that multiple VMs don't receive the same address. However, the
|
||||||
|
guest OS is not aware that the MAC address has changed, which prevents it from
|
||||||
|
sending and receiving packets correctly. There are several workarounds:
|
||||||
|
|
||||||
|
- Unload the network driver before saving the state. On Linux, unloading can be
|
||||||
|
done using `rmmod ne2k-pci` or `echo 0000:00:05.0 >
|
||||||
|
/sys/bus/pci/drivers/ne2k-pci/unbind` and loading (after the state has been
|
||||||
|
loaded) using `modprobe ne2k-pci` or `echo 0000:00:05.0 >
|
||||||
|
/sys/bus/pci/drivers/ne2k-pci/bind`
|
||||||
|
- Pass `preserve_mac_from_state_image: true` to the V86 constructor. This
|
||||||
|
causes MAC addresses to be shared between all VMs with the same state image.
|
||||||
|
- Pass `mac_address_translation: true` to the V86 constructor. This causes v86
|
||||||
|
to present the old MAC address to the guest OS, but translate it to a
|
||||||
|
randomised MAC address in outgoing packets (and vice-versa for incoming
|
||||||
|
packets). This mechanism currently only supports the ethernet, ipv4, dhcp and
|
||||||
|
arp protcols. See `translate_mac_address` in
|
||||||
|
[`src/ne2k.js`](https://github.com/copy/v86/blob/master/src/ne2k.js). This is
|
||||||
|
currently used in Windows, ReactOS and SerenityOS profiles.
|
||||||
|
- Some OSes don't cache the MAC address when the driver loads and therefore
|
||||||
|
don't need any of the above workarounds. This seems to be the case for Haiku,
|
||||||
|
OpenBSD and FreeBSD.
|
||||||
|
|
||||||
|
Note that the same applies to IP addresses, so a dhcp client should only be run
|
||||||
|
after the state has been loaded.
|
||||||
|
|
7
docs/profiling.md
Normal file
7
docs/profiling.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
v86 has a built-in profiler, which instruments generated code to count certain
|
||||||
|
events and types of instructions. It can be used by building with `make
|
||||||
|
debug-with-profiler` and opening debug.html.
|
||||||
|
|
||||||
|
For debugging networking, packet logging is available in the UI in both debug
|
||||||
|
and release builds. The resulting `traffic.hex` file can be loaded in Wireshark
|
||||||
|
using file -> import from hex -> tick direction indication, timestamp %s.%f.
|
|
@ -42,13 +42,15 @@
|
||||||
OpenBSD 6.6 base install. Restored from snapshot.</td></tr>
|
OpenBSD 6.6 base install. Restored from snapshot.</td></tr>
|
||||||
<tr id="start_9front"><td><a href="?profile=9front">9front</a> <small>4.4 MB</small></td><td>
|
<tr id="start_9front"><td><a href="?profile=9front">9front</a> <small>4.4 MB</small></td><td>
|
||||||
A Plan 9 fork.</td></tr>
|
A Plan 9 fork.</td></tr>
|
||||||
<tr id="start_haiku"><td><a href="?profile=haiku">Haiku</a> <small>46 MB</small></td><td>
|
<tr id="start_haiku"><td><a href="?profile=haiku">Haiku</a> <small>38 MB</small></td><td>
|
||||||
An open-source operating system inspired by BeOS. Restored from snapshot. Includes network support.</td></tr>
|
An open-source operating system inspired by BeOS. Restored from snapshot. Includes network support.</td></tr>
|
||||||
|
|
||||||
<tr id="start_serenity"><td><a href="?profile=serenity">SerenityOS</a> <small>11 MB</small></td><td>
|
<tr id="start_serenity"><td><a href="?profile=serenity">SerenityOS</a> <small>17 MB</small></td><td>
|
||||||
A graphical Unix-like operating system. Restored from snapshot.</td></tr>
|
A graphical Unix-like operating system. Restored from snapshot.</td></tr>
|
||||||
<tr id="start_helenos"><td><a href="?profile=helenos">HelenOS</a> <small>7.9 MB</small></td><td>
|
<tr id="start_helenos"><td><a href="?profile=helenos">HelenOS</a> <small>7.9 MB</small></td><td>
|
||||||
A graphical operating system based on a multiserver microkernel design</td></tr>
|
A graphical operating system based on a multiserver microkernel design</td></tr>
|
||||||
|
<tr id="start_fiwix"><td><a href="?profile=fiwix">FiwixOS</a> <small>15 MB</small></td><td>
|
||||||
|
A Unix-like OS written from scratch. Includes Doom.</td></tr>
|
||||||
<tr id="start_android"><td><a href="?profile=android">Android-x86</a> <small>42 MB</small></td><td>
|
<tr id="start_android"><td><a href="?profile=android">Android-x86</a> <small>42 MB</small></td><td>
|
||||||
An x86 port of the Android Open Source Project, version 1.6. Quite slow. Takes about 10 minutes to boot.</td></tr>
|
An x86 port of the Android Open Source Project, version 1.6. Quite slow. Takes about 10 minutes to boot.</td></tr>
|
||||||
|
|
||||||
|
|
|
@ -227,14 +227,14 @@
|
||||||
id: "serenity",
|
id: "serenity",
|
||||||
name: "SerenityOS",
|
name: "SerenityOS",
|
||||||
hda: {
|
hda: {
|
||||||
url: host + "serenity-v2.img",
|
url: host + "serenity-v3/.img.zst",
|
||||||
size: 700448768,
|
size: 734003200,
|
||||||
async: true,
|
async: true,
|
||||||
fixed_chunk_size: 1024 * 1024,
|
fixed_chunk_size: 1024 * 1024,
|
||||||
use_parts: !ON_LOCALHOST,
|
use_parts: true,
|
||||||
},
|
},
|
||||||
memory_size: 512 * 1024 * 1024,
|
memory_size: 512 * 1024 * 1024,
|
||||||
state: { url: host + "serenity_state-v3.bin.zst", },
|
state: { url: host + "serenity_state-v4.bin.zst", },
|
||||||
homepage: "https://serenityos.org/",
|
homepage: "https://serenityos.org/",
|
||||||
mac_address_translation: true,
|
mac_address_translation: true,
|
||||||
},
|
},
|
||||||
|
@ -242,38 +242,11 @@
|
||||||
id: "serenity-boot",
|
id: "serenity-boot",
|
||||||
name: "SerenityOS",
|
name: "SerenityOS",
|
||||||
hda: {
|
hda: {
|
||||||
url: host + "serenity-v2.img",
|
url: host + "serenity-v3/.img.zst",
|
||||||
size: 700448768,
|
size: 734003200,
|
||||||
async: true,
|
async: true,
|
||||||
fixed_chunk_size: 1024 * 1024,
|
fixed_chunk_size: 1024 * 1024,
|
||||||
use_parts: !ON_LOCALHOST,
|
use_parts: true,
|
||||||
},
|
|
||||||
memory_size: 512 * 1024 * 1024,
|
|
||||||
homepage: "https://serenityos.org/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "serenity-old",
|
|
||||||
name: "SerenityOS",
|
|
||||||
hda: {
|
|
||||||
url: host + "serenity.img",
|
|
||||||
size: 876 * 1024 * 1024,
|
|
||||||
async: true,
|
|
||||||
fixed_chunk_size: 1024 * 1024,
|
|
||||||
use_parts: !ON_LOCALHOST,
|
|
||||||
},
|
|
||||||
memory_size: 512 * 1024 * 1024,
|
|
||||||
state: { url: host + "serenity_state-v2.bin.zst", },
|
|
||||||
homepage: "https://serenityos.org/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "serenity-old-boot",
|
|
||||||
name: "SerenityOS",
|
|
||||||
hda: {
|
|
||||||
url: host + "serenity.img",
|
|
||||||
size: 876 * 1024 * 1024,
|
|
||||||
async: true,
|
|
||||||
fixed_chunk_size: 1024 * 1024,
|
|
||||||
use_parts: !ON_LOCALHOST,
|
|
||||||
},
|
},
|
||||||
memory_size: 512 * 1024 * 1024,
|
memory_size: 512 * 1024 * 1024,
|
||||||
homepage: "https://serenityos.org/",
|
homepage: "https://serenityos.org/",
|
||||||
|
@ -322,7 +295,7 @@
|
||||||
id: "fiwix",
|
id: "fiwix",
|
||||||
memory_size: 256 * 1024 * 1024,
|
memory_size: 256 * 1024 * 1024,
|
||||||
hda: {
|
hda: {
|
||||||
url: host + "fiwixos-3.2-i386.img",
|
url: host + "fiwixos-doom-3.2-i386.img",
|
||||||
size: 1024 * 1024 * 1024,
|
size: 1024 * 1024 * 1024,
|
||||||
async: true,
|
async: true,
|
||||||
fixed_chunk_size: 1024 * 1024,
|
fixed_chunk_size: 1024 * 1024,
|
||||||
|
@ -335,15 +308,13 @@
|
||||||
id: "haiku",
|
id: "haiku",
|
||||||
memory_size: 512 * 1024 * 1024,
|
memory_size: 512 * 1024 * 1024,
|
||||||
hda: {
|
hda: {
|
||||||
url: host + "haiku-v2.img",
|
url: host + "haiku-v3.img",
|
||||||
size: 1 * 1024 * 1024 * 1024,
|
size: 1 * 1024 * 1024 * 1024,
|
||||||
async: true,
|
async: true,
|
||||||
fixed_chunk_size: 1024 * 1024,
|
fixed_chunk_size: 1024 * 1024,
|
||||||
use_parts: !ON_LOCALHOST,
|
use_parts: !ON_LOCALHOST,
|
||||||
},
|
},
|
||||||
state: {
|
state: { url: host + "haiku_state-v3.bin.zst" },
|
||||||
url: host + "haiku_state-v2.bin.zst",
|
|
||||||
},
|
|
||||||
name: "Haiku",
|
name: "Haiku",
|
||||||
homepage: "https://www.haiku-os.org/",
|
homepage: "https://www.haiku-os.org/",
|
||||||
},
|
},
|
||||||
|
@ -351,7 +322,7 @@
|
||||||
id: "haiku-boot",
|
id: "haiku-boot",
|
||||||
memory_size: 512 * 1024 * 1024,
|
memory_size: 512 * 1024 * 1024,
|
||||||
hda: {
|
hda: {
|
||||||
url: host + "haiku-v2.img",
|
url: host + "haiku-v3.img",
|
||||||
size: 1 * 1024 * 1024 * 1024,
|
size: 1 * 1024 * 1024 * 1024,
|
||||||
async: true,
|
async: true,
|
||||||
fixed_chunk_size: 1024 * 1024,
|
fixed_chunk_size: 1024 * 1024,
|
||||||
|
|
|
@ -349,7 +349,9 @@ function ScreenAdapter(screen_container, bus)
|
||||||
graphic_screen.height = height;
|
graphic_screen.height = height;
|
||||||
|
|
||||||
// add some scaling to tiny resolutions
|
// add some scaling to tiny resolutions
|
||||||
if(width <= 640 && width * 2 < window.innerWidth && width * 2 < window.innerHeight)
|
if(width <= 640 &&
|
||||||
|
width * 2 < window.innerWidth * window.devicePixelRatio &&
|
||||||
|
height * 2 < window.innerHeight * window.devicePixelRatio)
|
||||||
{
|
{
|
||||||
base_scale = 2;
|
base_scale = 2;
|
||||||
}
|
}
|
||||||
|
|
|
@ -188,6 +188,7 @@ function V86Starter(options)
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
const { instance } = await WebAssembly.instantiate(bytes, env);
|
const { instance } = await WebAssembly.instantiate(bytes, env);
|
||||||
|
this.wasm_source = bytes;
|
||||||
resolve(instance.exports);
|
resolve(instance.exports);
|
||||||
}
|
}
|
||||||
catch(err)
|
catch(err)
|
||||||
|
@ -195,6 +196,7 @@ function V86Starter(options)
|
||||||
v86util.load_file(v86_bin_fallback, {
|
v86util.load_file(v86_bin_fallback, {
|
||||||
done: async bytes => {
|
done: async bytes => {
|
||||||
const { instance } = await WebAssembly.instantiate(bytes, env);
|
const { instance } = await WebAssembly.instantiate(bytes, env);
|
||||||
|
this.wasm_source = bytes;
|
||||||
resolve(instance.exports);
|
resolve(instance.exports);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -227,6 +229,9 @@ function V86Starter(options)
|
||||||
|
|
||||||
this.continue_init(emulator, options);
|
this.continue_init(emulator, options);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.zstd_worker = null;
|
||||||
|
this.zstd_worker_request_id = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
V86Starter.prototype.continue_init = async function(emulator, options)
|
V86Starter.prototype.continue_init = async function(emulator, options)
|
||||||
|
@ -365,7 +370,7 @@ V86Starter.prototype.continue_init = async function(emulator, options)
|
||||||
|
|
||||||
var files_to_load = [];
|
var files_to_load = [];
|
||||||
|
|
||||||
function add_file(name, file)
|
const add_file = (name, file) =>
|
||||||
{
|
{
|
||||||
if(!file)
|
if(!file)
|
||||||
{
|
{
|
||||||
|
@ -437,7 +442,7 @@ V86Starter.prototype.continue_init = async function(emulator, options)
|
||||||
|
|
||||||
if(file.use_parts)
|
if(file.use_parts)
|
||||||
{
|
{
|
||||||
buffer = new v86util.AsyncXHRPartfileBuffer(file.url, file.size, file.fixed_chunk_size);
|
buffer = new v86util.AsyncXHRPartfileBuffer(file.url, file.size, file.fixed_chunk_size, false, this.zstd_decompress_worker.bind(this));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -462,7 +467,7 @@ V86Starter.prototype.continue_init = async function(emulator, options)
|
||||||
{
|
{
|
||||||
dbg_log("Ignored file: url=" + file.url + " buffer=" + file.buffer);
|
dbg_log("Ignored file: url=" + file.url + " buffer=" + file.buffer);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if(options["state"])
|
if(options["state"])
|
||||||
{
|
{
|
||||||
|
@ -543,6 +548,12 @@ V86Starter.prototype.continue_init = async function(emulator, options)
|
||||||
v86util.load_file(f.url, {
|
v86util.load_file(f.url, {
|
||||||
done: function(result)
|
done: function(result)
|
||||||
{
|
{
|
||||||
|
if(f.url.endsWith(".zst") && f.name !== "initial_state")
|
||||||
|
{
|
||||||
|
dbg_assert(f.size, "A size must be provided for compressed images");
|
||||||
|
result = this.zstd_decompress(f.size, new Uint8Array(result));
|
||||||
|
}
|
||||||
|
|
||||||
put_on_settings.call(this, f.name, f.as_json ? result : new v86util.SyncBuffer(result));
|
put_on_settings.call(this, f.name, f.as_json ? result : new v86util.SyncBuffer(result));
|
||||||
cont(index + 1);
|
cont(index + 1);
|
||||||
}.bind(this),
|
}.bind(this),
|
||||||
|
@ -648,6 +659,107 @@ V86Starter.prototype.continue_init = async function(emulator, options)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} decompressed_size
|
||||||
|
* @param {Uint8Array} src
|
||||||
|
* @return {ArrayBuffer}
|
||||||
|
*/
|
||||||
|
V86Starter.prototype.zstd_decompress = function(decompressed_size, src)
|
||||||
|
{
|
||||||
|
const cpu = this.v86.cpu;
|
||||||
|
|
||||||
|
dbg_assert(!this.zstd_context);
|
||||||
|
this.zstd_context = cpu.zstd_create_ctx(src.length);
|
||||||
|
|
||||||
|
new Uint8Array(cpu.wasm_memory.buffer).set(src, cpu.zstd_get_src_ptr(this.zstd_context));
|
||||||
|
|
||||||
|
const ptr = cpu.zstd_read(this.zstd_context, decompressed_size);
|
||||||
|
const result = cpu.wasm_memory.buffer.slice(ptr, ptr + decompressed_size);
|
||||||
|
cpu.zstd_read_free(ptr, decompressed_size);
|
||||||
|
|
||||||
|
cpu.zstd_free_ctx(this.zstd_context);
|
||||||
|
this.zstd_context = null;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} decompressed_size
|
||||||
|
* @param {Uint8Array} src
|
||||||
|
* @return {Promise<ArrayBuffer>}
|
||||||
|
*/
|
||||||
|
V86Starter.prototype.zstd_decompress_worker = async function(decompressed_size, src)
|
||||||
|
{
|
||||||
|
if(!this.zstd_worker)
|
||||||
|
{
|
||||||
|
function the_worker()
|
||||||
|
{
|
||||||
|
let wasm;
|
||||||
|
|
||||||
|
globalThis.onmessage = function(e)
|
||||||
|
{
|
||||||
|
if(!wasm)
|
||||||
|
{
|
||||||
|
const env = Object.fromEntries([
|
||||||
|
"cpu_exception_hook", "hlt_op",
|
||||||
|
"microtick", "get_rand_int", "pic_acknowledge",
|
||||||
|
"io_port_read8", "io_port_read16", "io_port_read32",
|
||||||
|
"io_port_write8", "io_port_write16", "io_port_write32",
|
||||||
|
"mmap_read8", "mmap_read16", "mmap_read32",
|
||||||
|
"mmap_write8", "mmap_write16", "mmap_write32", "mmap_write64", "mmap_write128",
|
||||||
|
"codegen_finalize",
|
||||||
|
"jit_clear_func", "jit_clear_all_funcs",
|
||||||
|
].map(f => [f, () => console.error("zstd worker unexpectedly called " + f)]));
|
||||||
|
|
||||||
|
env["__indirect_function_table"] = new WebAssembly.Table({ element: "anyfunc", "initial": 1024 });
|
||||||
|
env["abort"] = () => { throw new Error("zstd worker aborted"); };
|
||||||
|
env["log_from_wasm"] = env["console_log_from_wasm"] = (off, len) => {
|
||||||
|
console.log(String.fromCharCode(...new Uint8Array(wasm.exports.memory.buffer, off, len)));
|
||||||
|
};
|
||||||
|
env["dbg_trace_from_wasm"] = () => console.trace();
|
||||||
|
|
||||||
|
wasm = new WebAssembly.Instance(new WebAssembly.Module(e.data), { "env": env });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { src, decompressed_size, id } = e.data;
|
||||||
|
const exports = wasm.exports;
|
||||||
|
|
||||||
|
const zstd_context = exports["zstd_create_ctx"](src.length);
|
||||||
|
new Uint8Array(exports.memory.buffer).set(src, exports["zstd_get_src_ptr"](zstd_context));
|
||||||
|
|
||||||
|
const ptr = exports["zstd_read"](zstd_context, decompressed_size);
|
||||||
|
const result = exports.memory.buffer.slice(ptr, ptr + decompressed_size);
|
||||||
|
exports["zstd_read_free"](ptr, decompressed_size);
|
||||||
|
|
||||||
|
exports["zstd_free_ctx"](zstd_context);
|
||||||
|
|
||||||
|
postMessage({ result, id }, [result]);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = URL.createObjectURL(new Blob(["(" + the_worker.toString() + ")()"], { type: "text/javascript" }));
|
||||||
|
this.zstd_worker = new Worker(url);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
this.zstd_worker.postMessage(this.wasm_source, [this.wasm_source]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const id = this.zstd_worker_request_id++;
|
||||||
|
const done = async e =>
|
||||||
|
{
|
||||||
|
if(e.data.id === id)
|
||||||
|
{
|
||||||
|
this.zstd_worker.removeEventListener("message", done);
|
||||||
|
dbg_assert(decompressed_size === e.data.result.byteLength);
|
||||||
|
resolve(e.data.result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.zstd_worker.addEventListener("message", done);
|
||||||
|
this.zstd_worker.postMessage({ src, decompressed_size, id }, [src.buffer]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
V86Starter.prototype.get_bzimage_initrd_from_filesystem = function(filesystem)
|
V86Starter.prototype.get_bzimage_initrd_from_filesystem = function(filesystem)
|
||||||
{
|
{
|
||||||
const root = (filesystem.read_dir("/") || []).map(x => "/" + x);
|
const root = (filesystem.read_dir("/") || []).map(x => "/" + x);
|
||||||
|
|
|
@ -381,20 +381,14 @@
|
||||||
* @param {number|undefined} fixed_chunk_size
|
* @param {number|undefined} fixed_chunk_size
|
||||||
* @param {boolean|undefined} partfile_alt_format
|
* @param {boolean|undefined} partfile_alt_format
|
||||||
*/
|
*/
|
||||||
function AsyncXHRPartfileBuffer(filename, size, fixed_chunk_size, partfile_alt_format)
|
function AsyncXHRPartfileBuffer(filename, size, fixed_chunk_size, partfile_alt_format, zstd_decompress)
|
||||||
{
|
{
|
||||||
const parts = filename.match(/(.*)(\..*)/);
|
const parts = filename.match(/\.[^\.]+(\.zst)?$/);
|
||||||
|
|
||||||
if(parts)
|
this.extension = parts ? parts[0] : "";
|
||||||
{
|
this.basename = filename.substring(0, filename.length - this.extension.length);
|
||||||
this.basename = parts[1];
|
|
||||||
this.extension = parts[2];
|
this.is_zstd = this.extension.endsWith(".zst");
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.basename = filename;
|
|
||||||
this.extension = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!this.basename.endsWith("/"))
|
if(!this.basename.endsWith("/"))
|
||||||
{
|
{
|
||||||
|
@ -407,6 +401,7 @@
|
||||||
this.byteLength = size;
|
this.byteLength = size;
|
||||||
this.fixed_chunk_size = fixed_chunk_size;
|
this.fixed_chunk_size = fixed_chunk_size;
|
||||||
this.partfile_alt_format = !!partfile_alt_format;
|
this.partfile_alt_format = !!partfile_alt_format;
|
||||||
|
this.zstd_decompress = zstd_decompress;
|
||||||
|
|
||||||
this.cache_reads = !!fixed_chunk_size; // TODO: could also be useful in other cases (needs testing)
|
this.cache_reads = !!fixed_chunk_size; // TODO: could also be useful in other cases (needs testing)
|
||||||
|
|
||||||
|
@ -478,29 +473,33 @@
|
||||||
|
|
||||||
if(block)
|
if(block)
|
||||||
{
|
{
|
||||||
const cur = i * this.fixed_chunk_size;
|
blocks.set(block, i * this.fixed_chunk_size);
|
||||||
blocks.set(block, cur);
|
|
||||||
finished++;
|
finished++;
|
||||||
if(finished === total_count)
|
if(finished === total_count)
|
||||||
{
|
{
|
||||||
const tmp_blocks = blocks.subarray(m_offset, m_offset + len);
|
fn(blocks.subarray(m_offset, m_offset + len));
|
||||||
fn(tmp_blocks);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
v86util.load_file(part_filename, {
|
v86util.load_file(part_filename, {
|
||||||
done: function done(buffer)
|
done: async function done(buffer)
|
||||||
{
|
{
|
||||||
const cur = i * this.fixed_chunk_size;
|
let block = new Uint8Array(buffer);
|
||||||
const block = new Uint8Array(buffer);
|
|
||||||
|
if(this.is_zstd)
|
||||||
|
{
|
||||||
|
const decompressed = await this.zstd_decompress(this.fixed_chunk_size, block);
|
||||||
|
block = new Uint8Array(decompressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks.set(block, i * this.fixed_chunk_size);
|
||||||
this.handle_read((start_index + i) * this.fixed_chunk_size, this.fixed_chunk_size|0, block);
|
this.handle_read((start_index + i) * this.fixed_chunk_size, this.fixed_chunk_size|0, block);
|
||||||
blocks.set(block, cur);
|
|
||||||
finished++;
|
finished++;
|
||||||
if(finished === total_count)
|
if(finished === total_count)
|
||||||
{
|
{
|
||||||
const tmp_blocks = blocks.subarray(m_offset, m_offset + len);
|
fn(blocks.subarray(m_offset, m_offset + len));
|
||||||
fn(tmp_blocks);
|
|
||||||
}
|
}
|
||||||
}.bind(this),
|
}.bind(this),
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue