Skip to content

Commit

Permalink
[wasm] Make jiterpreter module size limit configurable + clean up tec…
Browse files Browse the repository at this point in the history
…hnical debt (#107318)

Fix spurious "block stack not empty" errors when an error occurs during jiterp codegen
Add missing overflow checks to blob builder appends
Make maximum trace size a runtime option
Move blob builder capacity to a named constant near the top of the file
  • Loading branch information
kg committed Sep 4, 2024
1 parent 6e68f92 commit 57a4b04
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 20 deletions.
2 changes: 1 addition & 1 deletion src/mono/browser/runtime/jiterpreter-interp-entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ function flush_wasm_entry_trampoline_jit_queue () {
;
}

const buf = builder.getArrayView();
const buf = builder.getArrayView(false, true);
for (let i = 0; i < buf.length; i++) {
const b = buf[i];
if (b < 0x10)
Expand Down
2 changes: 1 addition & 1 deletion src/mono/browser/runtime/jiterpreter-jit-call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ export function mono_interp_flush_jitcall_queue (): void {
;
}

const buf = builder.getArrayView();
const buf = builder.getArrayView(false, true);
for (let i = 0; i < buf.length; i++) {
const b = buf[i];
if (b < 0x10)
Expand Down
32 changes: 24 additions & 8 deletions src/mono/browser/runtime/jiterpreter-support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ import {
export const maxFailures = 2,
maxMemsetSize = 64,
maxMemmoveSize = 64,
shortNameBase = 36;
shortNameBase = 36,
// NOTE: This needs to be big enough to hold the maximum module size since there's no auto-growth
// support yet. If that becomes a problem, we should just make it growable
blobBuilderCapacity = 16 * 1024;

// uint16
export declare interface MintOpcodePtr extends NativePointer {
Expand Down Expand Up @@ -120,6 +123,8 @@ export class WasmBuilder {

clear (constantSlotCount: number) {
this.options = getOptions();
if (this.options.maxModuleSize >= blobBuilderCapacity)
throw new Error(`blobBuilderCapacity ${blobBuilderCapacity} is not large enough for jiterpreter-max-module-size of ${this.options.maxModuleSize}`);
this.stackSize = 1;
this.inSection = false;
this.inFunction = false;
Expand Down Expand Up @@ -920,8 +925,8 @@ export class WasmBuilder {
this.appendU8(WasmOpcode.i32_add);
}

getArrayView (fullCapacity?: boolean) {
if (this.stackSize > 1)
getArrayView (fullCapacity?: boolean, suppressDeepStackError?: boolean) {
if ((suppressDeepStackError !== true) && this.stackSize > 1)
throw new Error("Jiterpreter block stack not empty");
return this.stack[0].getArrayView(fullCapacity);
}
Expand All @@ -942,8 +947,9 @@ export class BlobBuilder {
textBuf = new Uint8Array(1024);

constructor () {
this.capacity = 16 * 1024;
this.capacity = blobBuilderCapacity;
this.buffer = <any>Module._malloc(this.capacity);
mono_assert(this.buffer, () => `Failed to allocate ${blobBuilderCapacity}b buffer for BlobBuilder`);
localHeapViewU8().fill(0, this.buffer, this.buffer + this.capacity);
this.size = 0;
this.clear();
Expand Down Expand Up @@ -1051,18 +1057,26 @@ export class BlobBuilder {
if (typeof (count) !== "number")
count = this.size;

if ((destination.size + count) >= destination.capacity)
throw new Error("Destination buffer full");

localHeapViewU8().copyWithin(destination.buffer + destination.size, this.buffer, this.buffer + count);
destination.size += count;
}

appendBytes (bytes: Uint8Array, count?: number) {
const result = this.size;
const heapU8 = localHeapViewU8();
const actualCount = (typeof (count) !== "number")
? bytes.length
: count;

if ((this.size + actualCount) >= this.capacity)
throw new Error("Buffer full");

if (bytes.buffer === heapU8.buffer) {
if (typeof (count) !== "number")
count = bytes.length;
heapU8.copyWithin(this.buffer + result, bytes.byteOffset, bytes.byteOffset + count);
this.size += count;
heapU8.copyWithin(this.buffer + result, bytes.byteOffset, bytes.byteOffset + actualCount);
this.size += actualCount;
} else {
if (typeof (count) === "number")
bytes = new Uint8Array(bytes.buffer, bytes.byteOffset, count);
Expand Down Expand Up @@ -1950,6 +1964,7 @@ export type JiterpreterOptions = {
wasmBytesLimit: number;
tableSize: number;
aotTableSize: number;
maxModuleSize: number;
}

const optionNames: { [jsName: string]: string } = {
Expand Down Expand Up @@ -1986,6 +2001,7 @@ const optionNames: { [jsName: string]: string } = {
"wasmBytesLimit": "jiterpreter-wasm-bytes-limit",
"tableSize": "jiterpreter-table-size",
"aotTableSize": "jiterpreter-aot-table-size",
"maxModuleSize": "jiterpreter-max-module-size",
};

let optionsVersion = -1;
Expand Down
7 changes: 2 additions & 5 deletions src/mono/browser/runtime/jiterpreter-trace-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import {
traceEip, nullCheckValidation,
traceNullCheckOptimizations,
nullCheckCaching, defaultTraceBackBranches,
maxCallHandlerReturnAddresses,
maxCallHandlerReturnAddresses, moduleHeaderSizeMargin,

mostRecentOptions,

Expand Down Expand Up @@ -275,10 +275,7 @@ export function generateWasmBody (
break;
}

// HACK: Browsers set a limit of 4KB, we lower it slightly since a single opcode
// might generate a ton of code and we generate a bit of an epilogue after
// we finish
const maxBytesGenerated = 3840,
const maxBytesGenerated = builder.options.maxModuleSize - moduleHeaderSizeMargin,
spaceLeft = maxBytesGenerated - builder.bytesGeneratedSoFar - builder.cfg.overheadBytes;
if (builder.size >= spaceLeft) {
// mono_log_info(`trace too big, estimated size is ${builder.size + builder.bytesGeneratedSoFar}`);
Expand Down
8 changes: 4 additions & 4 deletions src/mono/browser/runtime/jiterpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ export const
useFullNames = false,
// Use the mono_debug_count() API (set the COUNT=n env var) to limit the number of traces to compile
useDebugCount = false,
// Web browsers limit synchronous module compiles to 4KB
maxModuleSize = 4080;
// Subtracted from the maxModuleSize option value to make space for a typical header
moduleHeaderSizeMargin = 300;

export const callTargetCounts: { [method: number]: number } = {};

Expand Down Expand Up @@ -828,7 +828,7 @@ function generate_wasm (
mono_log_info(`${(<any>(builder.base)).toString(16)} ${methodFullName || traceName} generated ${buffer.length} byte(s) of wasm`);
modifyCounter(JiterpCounter.BytesGenerated, buffer.length);

if (buffer.length >= maxModuleSize) {
if (buffer.length >= builder.options.maxModuleSize) {
mono_log_warn(`Jiterpreter generated too much code (${buffer.length} bytes) for trace ${traceName}. Please report this issue.`);
return 0;
}
Expand Down Expand Up @@ -913,7 +913,7 @@ function generate_wasm (
;
}

const buf = builder.getArrayView();
const buf = builder.getArrayView(false, true);
for (let i = 0; i < buf.length; i++) {
const b = buf[i];
if (b < 0x10)
Expand Down
4 changes: 3 additions & 1 deletion src/mono/mono/utils/options-def.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ DEFINE_BOOL(jiterpreter_constant_propagation, "jiterpreter-constant-propagation"
// When compiling a jit_call wrapper, bypass sharedvt wrappers if possible by inlining their
// logic into the compiled wrapper and calling the target AOTed function with native call convention
DEFINE_BOOL(jiterpreter_direct_jit_call, "jiterpreter-direct-jit-calls", TRUE, "Bypass gsharedvt wrappers when compiling JIT call wrappers")
// any trace that doesn't have at least this many meaningful (non-nop) opcodes in it will be rejected
// when deciding whether to generate a trace, we sum the value of sequential opcodes that will fit into it
// and reject any trace entry point where the score is below this value
DEFINE_INT(jiterpreter_minimum_trace_value, "jiterpreter-minimum-trace-value", 18, "Reject traces that perform less than this amount of (approximate) work")
// ensure that we don't create trace entry points too close together
DEFINE_INT(jiterpreter_minimum_distance_between_traces, "jiterpreter-minimum-distance-between-traces", 4, "Don't insert entry points closer together than this")
Expand Down Expand Up @@ -175,6 +176,7 @@ DEFINE_INT(jiterpreter_table_size, "jiterpreter-table-size", 6 * 1024, "Size of
// to bloat to an unacceptable degree. In practice this is still better than nothing.
// FIXME: In the future if we find a way to reduce the number of unique tables we can raise this constant
DEFINE_INT(jiterpreter_aot_table_size, "jiterpreter-aot-table-size", 3 * 1024, "Size of the jiterpreter AOT trampoline function tables")
DEFINE_INT(jiterpreter_max_module_size, "jiterpreter-max-module-size", 4080, "Size limit for jiterpreter generated WASM modules")
#endif // HOST_BROWSER

#if defined(TARGET_WASM) || defined(TARGET_IOS) || defined(TARGET_TVOS) || defined (TARGET_MACCAT)
Expand Down

0 comments on commit 57a4b04

Please sign in to comment.