Implement asynchronous compilation of wasm modules
Add a callback after codegen_finalize to create entry. Mark entries as pending while they compile
This commit is contained in:
parent
01b4ed844f
commit
caf3a9e1f3
|
@ -224,7 +224,9 @@ function V86Starter(options)
|
|||
},
|
||||
"_get_time": Date.now,
|
||||
|
||||
"_codegen_finalize": (cache_index, start, end) => cpu.codegen_finalize(cache_index, start, end),
|
||||
"_codegen_finalize": (wasm_table_index, start, end, first_opcode, state_flags, page_dirtiness) => {
|
||||
cpu.codegen_finalize(wasm_table_index, start, end, first_opcode, state_flags, page_dirtiness);
|
||||
},
|
||||
"_coverage_log": (fn_name_offset, num_blocks, visited_block) => {
|
||||
coverage_logger.log(fn_name_offset, num_blocks, visited_block);
|
||||
},
|
||||
|
|
67
src/cpu.js
67
src/cpu.js
|
@ -242,11 +242,17 @@ function CPU(bus, wm, codegen, coverage_logger)
|
|||
CPU.prototype.create_jit_imports = function()
|
||||
{
|
||||
// Set this.jit_imports as generated WASM modules will expect
|
||||
const imports = {
|
||||
"e": {
|
||||
"m": this.wm.memory,
|
||||
},
|
||||
};
|
||||
|
||||
/** @constructor */
|
||||
function JITImports()
|
||||
{
|
||||
// put all imports that change here
|
||||
this["next_block_branched"] = null;
|
||||
this["next_block_not_branched"] = null;
|
||||
}
|
||||
|
||||
// put all imports that don't change on the prototype
|
||||
JITImports.prototype["m"] = this.wm.memory;
|
||||
|
||||
const exports = this.wm.instance.exports;
|
||||
|
||||
|
@ -257,10 +263,10 @@ CPU.prototype.create_jit_imports = function()
|
|||
continue;
|
||||
}
|
||||
|
||||
imports["e"][name.slice(1)] = exports[name];
|
||||
JITImports.prototype[name.slice(1)] = exports[name];
|
||||
}
|
||||
|
||||
this.jit_imports = imports;
|
||||
this.jit_imports = new JITImports();
|
||||
};
|
||||
|
||||
CPU.prototype.set_jit_import = function(function_index, wasm_index)
|
||||
|
@ -279,7 +285,7 @@ CPU.prototype.set_jit_import = function(function_index, wasm_index)
|
|||
}
|
||||
dbg_assert(function_name);
|
||||
|
||||
this.jit_imports["e"][function_name] = fn;
|
||||
this.jit_imports[function_name] = fn;
|
||||
};
|
||||
|
||||
CPU.prototype.wasm_patch = function(wm)
|
||||
|
@ -1262,14 +1268,12 @@ if(PROFILING)
|
|||
var seen_code = {};
|
||||
var seen_code_uncompiled = {};
|
||||
|
||||
CPU.prototype.codegen_finalize = function(cache_index, start, end)
|
||||
CPU.prototype.codegen_finalize = function(wasm_table_index, start, end, first_opcode, state_flags, page_dirtiness)
|
||||
{
|
||||
dbg_assert(cache_index >= 0 && cache_index < WASM_TABLE_SIZE);
|
||||
dbg_assert(wasm_table_index >= 0 && wasm_table_index < WASM_TABLE_SIZE);
|
||||
//dbg_log("finalize");
|
||||
const code = this.codegen.get_module_code();
|
||||
|
||||
let module;
|
||||
|
||||
if(DEBUG)
|
||||
{
|
||||
if(DUMP_GENERATED_WASM && !seen_code[start])
|
||||
|
@ -1295,31 +1299,32 @@ CPU.prototype.codegen_finalize = function(cache_index, start, end)
|
|||
|
||||
this.debug.dump_code(this.is_32[0] ? 1 : 0, buffer, start);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
module = new WebAssembly.Module(code);
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
//debugger;
|
||||
// Make a copy of jit_imports, since some imports change and
|
||||
// WebAssembly.instantiate looks them up asynchronously
|
||||
const jit_imports = new this.jit_imports.constructor();
|
||||
jit_imports["next_block_branched"] = this.jit_imports["next_block_branched"];
|
||||
jit_imports["next_block_not_branched"] = this.jit_imports["next_block_not_branched"];
|
||||
|
||||
const result = WebAssembly.instantiate(code, { "e": jit_imports }).then(result => {
|
||||
const f = result["instance"].exports["f"];
|
||||
|
||||
this.wm.exports["_codegen_finalize_finished"](
|
||||
wasm_table_index, start, first_opcode, state_flags, page_dirtiness);
|
||||
|
||||
// The following will throw if f isn't an exported function
|
||||
this.wm.imports["env"].table.set(wasm_table_index, f);
|
||||
});
|
||||
|
||||
if(DEBUG)
|
||||
{
|
||||
result.catch(e => {
|
||||
console.log(e);
|
||||
debugger;
|
||||
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
module = new WebAssembly.Module(code);
|
||||
}
|
||||
|
||||
const instance = new WebAssembly.Instance(module, this.jit_imports);
|
||||
const f = instance.exports["f"];
|
||||
|
||||
// The following will throw if f isn't an exported function
|
||||
this.wm.imports["env"].table.set(cache_index, f);
|
||||
};
|
||||
|
||||
CPU.prototype.log_uncompiled_code = function(start, end)
|
||||
|
|
|
@ -18,9 +18,9 @@
|
|||
#include "shared.h"
|
||||
|
||||
#if DEBUG
|
||||
struct code_cache jit_cache_arr[WASM_TABLE_SIZE] = {{0, 0, {0}, 0, 0, 0, false}};
|
||||
struct code_cache jit_cache_arr[WASM_TABLE_SIZE] = {{0, 0, {0}, 0, 0, 0, 0, false}};
|
||||
#else
|
||||
struct code_cache jit_cache_arr[WASM_TABLE_SIZE] = {{0, 0, 0, false}};
|
||||
struct code_cache jit_cache_arr[WASM_TABLE_SIZE] = {{0, 0, 0, 0, false}};
|
||||
#endif
|
||||
|
||||
uint32_t jit_jump = 0;
|
||||
|
@ -645,7 +645,7 @@ static void jit_generate(int32_t address_hash, uint32_t phys_addr, uint32_t page
|
|||
profiler_start(P_GEN_INSTR);
|
||||
profiler_stat_increment(S_COMPILE);
|
||||
|
||||
int32_t start_addr = *instruction_pointer;
|
||||
int32_t virt_start_addr = *instruction_pointer;
|
||||
|
||||
// don't immediately retry to compile
|
||||
hot_code_addresses[address_hash] = 0;
|
||||
|
@ -728,7 +728,7 @@ static void jit_generate(int32_t address_hash, uint32_t phys_addr, uint32_t page
|
|||
end_addr = *eip_phys ^ *instruction_pointer;
|
||||
len++;
|
||||
}
|
||||
while(!was_jump && len < 50 && !is_near_end_of_page(*instruction_pointer));
|
||||
while(!was_jump && !is_near_end_of_page(*instruction_pointer));
|
||||
|
||||
#if ENABLE_JIT_NONFAULTING_OPTIMZATION
|
||||
// When the block ends in a non-jump instruction, we may have uncommitted updates still
|
||||
|
@ -742,6 +742,9 @@ static void jit_generate(int32_t address_hash, uint32_t phys_addr, uint32_t page
|
|||
// no page was crossed
|
||||
assert(((end_addr ^ phys_addr) & ~0xFFF) == 0);
|
||||
|
||||
jit_jump = false;
|
||||
assert(*prefixes == 0);
|
||||
|
||||
// at this point no exceptions can be raised
|
||||
|
||||
if(!ENABLE_JIT_ALWAYS && JIT_MIN_BLOCK_LENGTH != 0 && len < JIT_MIN_BLOCK_LENGTH)
|
||||
|
@ -749,22 +752,19 @@ static void jit_generate(int32_t address_hash, uint32_t phys_addr, uint32_t page
|
|||
// abort, block is too short to be considered useful for compilation
|
||||
profiler_stat_increment(S_CACHE_SKIPPED);
|
||||
profiler_end(P_GEN_INSTR);
|
||||
*instruction_pointer = start_addr;
|
||||
*instruction_pointer = virt_start_addr;
|
||||
return;
|
||||
}
|
||||
|
||||
gen_increment_timestamp_counter(len);
|
||||
gen_finish();
|
||||
|
||||
struct code_cache* entry = create_cache_entry(phys_addr);
|
||||
|
||||
jit_jump = false;
|
||||
gen_finish();
|
||||
codegen_finalize(entry->wasm_table_index, entry->start_addr, end_addr);
|
||||
assert(*prefixes == 0);
|
||||
|
||||
entry->start_addr = phys_addr;
|
||||
entry->state_flags = pack_current_state_flags();
|
||||
entry->group_status = page_dirtiness;
|
||||
entry->pending = true;
|
||||
|
||||
#if DEBUG
|
||||
assert(first_opcode != -1);
|
||||
|
@ -773,13 +773,43 @@ static void jit_generate(int32_t address_hash, uint32_t phys_addr, uint32_t page
|
|||
entry->len = len;
|
||||
#endif
|
||||
|
||||
// start execution at the newly generated code
|
||||
*instruction_pointer = start_addr;
|
||||
// will call codegen_finalize_finished asynchronously when finished
|
||||
codegen_finalize(
|
||||
entry->wasm_table_index, phys_addr, end_addr,
|
||||
first_opcode, entry->state_flags, page_dirtiness);
|
||||
|
||||
// start execution at the beginning
|
||||
*instruction_pointer = virt_start_addr;
|
||||
|
||||
profiler_stat_increment(S_COMPILE_SUCCESS);
|
||||
profiler_end(P_GEN_INSTR);
|
||||
}
|
||||
|
||||
void codegen_finalize_finished(
|
||||
int32_t wasm_table_index, uint32_t phys_addr, uint32_t end_addr,
|
||||
int32_t first_opcode, cached_state_flags state_flags, uint32_t page_dirtiness)
|
||||
{
|
||||
if(page_dirtiness == group_dirtiness[phys_addr >> DIRTY_ARR_SHIFT])
|
||||
{
|
||||
struct code_cache* entry = &jit_cache_arr[wasm_table_index];
|
||||
|
||||
// XXX: May end up in this branch when entry is overwritten due to full
|
||||
// cache, causing the assertions below to fail
|
||||
|
||||
// sanity check that we're looking at the right entry
|
||||
assert(entry->pending);
|
||||
assert(entry->group_status == page_dirtiness);
|
||||
assert(entry->state_flags == state_flags);
|
||||
assert(entry->start_addr == phys_addr);
|
||||
|
||||
entry->pending = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// the page has been written, drop this entry
|
||||
}
|
||||
}
|
||||
|
||||
static struct code_cache* find_cache_entry(uint32_t phys_addr)
|
||||
{
|
||||
#pragma clang loop unroll_count(CODE_CACHE_SEARCH_SIZE)
|
||||
|
@ -809,7 +839,9 @@ struct code_cache* find_link_block_target(int32_t target)
|
|||
uint32_t phys_target = *eip_phys ^ target;
|
||||
struct code_cache* entry = find_cache_entry(phys_target);
|
||||
|
||||
if(entry && entry->group_status == group_dirtiness[entry->start_addr >> DIRTY_ARR_SHIFT])
|
||||
if(entry &&
|
||||
!entry->pending &&
|
||||
entry->group_status == group_dirtiness[entry->start_addr >> DIRTY_ARR_SHIFT])
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
|
@ -882,7 +914,7 @@ void cycle_internal()
|
|||
const bool JIT_DONT_USE_CACHE = false;
|
||||
const bool JIT_COMPILE_ONLY_AFTER_JUMP = true;
|
||||
|
||||
if(!JIT_DONT_USE_CACHE && entry && entry->group_status == page_dirtiness)
|
||||
if(!JIT_DONT_USE_CACHE && entry && entry->group_status == page_dirtiness && !entry->pending)
|
||||
{
|
||||
profiler_start(P_RUN_FROM_CACHE);
|
||||
profiler_stat_increment(S_RUN_FROM_CACHE);
|
||||
|
@ -915,7 +947,16 @@ void cycle_internal()
|
|||
bool did_jump = !JIT_COMPILE_ONLY_AFTER_JUMP || jit_jump;
|
||||
const int32_t address_hash = jit_hot_hash(phys_addr);
|
||||
|
||||
// exists | pending | written -> should generate
|
||||
// -------+---------+---------++---------------------
|
||||
// 0 | x | x -> yes
|
||||
// 1 | 0 | 0 -> impossible (handled above)
|
||||
// 1 | 1 | 0 -> no
|
||||
// 1 | 0 | 1 -> yes
|
||||
// 1 | 1 | 1 -> yes
|
||||
|
||||
if(
|
||||
(!entry || entry->group_status != page_dirtiness) &&
|
||||
!is_near_end_of_page(phys_addr) && (
|
||||
ENABLE_JIT_ALWAYS ||
|
||||
(did_jump && ++hot_code_addresses[address_hash] > JIT_THRESHOLD)
|
||||
|
|
|
@ -53,6 +53,7 @@ struct code_cache {
|
|||
|
||||
uint16_t wasm_table_index;
|
||||
cached_state_flags state_flags;
|
||||
bool pending;
|
||||
};
|
||||
#if DEBUG
|
||||
#else
|
||||
|
|
|
@ -22,7 +22,7 @@ extern int32_t mmap_read8(uint32_t);
|
|||
extern int32_t set_cr0(int32_t);
|
||||
extern int32_t verr(int32_t);
|
||||
extern int32_t verw(int32_t);
|
||||
extern void codegen_finalize(int32_t, int32_t, int32_t);
|
||||
extern void codegen_finalize(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t);
|
||||
extern void log_uncompiled_code(int32_t, int32_t);
|
||||
extern void cpl_changed(void);
|
||||
extern void cpuid(void);
|
||||
|
|
Loading…
Reference in a new issue