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:
Fabian 2018-03-04 14:36:09 -06:00
parent 01b4ed844f
commit caf3a9e1f3
5 changed files with 96 additions and 47 deletions

View file

@ -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);
},

View file

@ -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)

View file

@ -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)

View file

@ -53,6 +53,7 @@ struct code_cache {
uint16_t wasm_table_index;
cached_state_flags state_flags;
bool pending;
};
#if DEBUG
#else

View file

@ -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);