Skip to content

Commit 0714d32

Browse files
kgradekdoulik
authored andcommitted
[wasm] Make jiterpreter module size limit configurable + clean up technical debt (dotnet#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
1 parent 17407d4 commit 0714d32

File tree

6 files changed

+35
-20
lines changed

6 files changed

+35
-20
lines changed

src/mono/browser/runtime/jiterpreter-interp-entry.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ function flush_wasm_entry_trampoline_jit_queue () {
449449
;
450450
}
451451

452-
const buf = builder.getArrayView();
452+
const buf = builder.getArrayView(false, true);
453453
for (let i = 0; i < buf.length; i++) {
454454
const b = buf[i];
455455
if (b < 0x10)

src/mono/browser/runtime/jiterpreter-jit-call.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,7 @@ export function mono_interp_flush_jitcall_queue (): void {
519519
;
520520
}
521521

522-
const buf = builder.getArrayView();
522+
const buf = builder.getArrayView(false, true);
523523
for (let i = 0; i < buf.length; i++) {
524524
const b = buf[i];
525525
if (b < 0x10)

src/mono/browser/runtime/jiterpreter-support.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ import {
1717
export const maxFailures = 2,
1818
maxMemsetSize = 64,
1919
maxMemmoveSize = 64,
20-
shortNameBase = 36;
20+
shortNameBase = 36,
21+
// NOTE: This needs to be big enough to hold the maximum module size since there's no auto-growth
22+
// support yet. If that becomes a problem, we should just make it growable
23+
blobBuilderCapacity = 16 * 1024;
2124

2225
// uint16
2326
export declare interface MintOpcodePtr extends NativePointer {
@@ -120,6 +123,8 @@ export class WasmBuilder {
120123

121124
clear (constantSlotCount: number) {
122125
this.options = getOptions();
126+
if (this.options.maxModuleSize >= blobBuilderCapacity)
127+
throw new Error(`blobBuilderCapacity ${blobBuilderCapacity} is not large enough for jiterpreter-max-module-size of ${this.options.maxModuleSize}`);
123128
this.stackSize = 1;
124129
this.inSection = false;
125130
this.inFunction = false;
@@ -920,8 +925,8 @@ export class WasmBuilder {
920925
this.appendU8(WasmOpcode.i32_add);
921926
}
922927

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

944949
constructor () {
945-
this.capacity = 16 * 1024;
950+
this.capacity = blobBuilderCapacity;
946951
this.buffer = <any>Module._malloc(this.capacity);
952+
mono_assert(this.buffer, () => `Failed to allocate ${blobBuilderCapacity}b buffer for BlobBuilder`);
947953
localHeapViewU8().fill(0, this.buffer, this.buffer + this.capacity);
948954
this.size = 0;
949955
this.clear();
@@ -1051,18 +1057,26 @@ export class BlobBuilder {
10511057
if (typeof (count) !== "number")
10521058
count = this.size;
10531059

1060+
if ((destination.size + count) >= destination.capacity)
1061+
throw new Error("Destination buffer full");
1062+
10541063
localHeapViewU8().copyWithin(destination.buffer + destination.size, this.buffer, this.buffer + count);
10551064
destination.size += count;
10561065
}
10571066

10581067
appendBytes (bytes: Uint8Array, count?: number) {
10591068
const result = this.size;
10601069
const heapU8 = localHeapViewU8();
1070+
const actualCount = (typeof (count) !== "number")
1071+
? bytes.length
1072+
: count;
1073+
1074+
if ((this.size + actualCount) >= this.capacity)
1075+
throw new Error("Buffer full");
1076+
10611077
if (bytes.buffer === heapU8.buffer) {
1062-
if (typeof (count) !== "number")
1063-
count = bytes.length;
1064-
heapU8.copyWithin(this.buffer + result, bytes.byteOffset, bytes.byteOffset + count);
1065-
this.size += count;
1078+
heapU8.copyWithin(this.buffer + result, bytes.byteOffset, bytes.byteOffset + actualCount);
1079+
this.size += actualCount;
10661080
} else {
10671081
if (typeof (count) === "number")
10681082
bytes = new Uint8Array(bytes.buffer, bytes.byteOffset, count);
@@ -1950,6 +1964,7 @@ export type JiterpreterOptions = {
19501964
wasmBytesLimit: number;
19511965
tableSize: number;
19521966
aotTableSize: number;
1967+
maxModuleSize: number;
19531968
}
19541969

19551970
const optionNames: { [jsName: string]: string } = {
@@ -1986,6 +2001,7 @@ const optionNames: { [jsName: string]: string } = {
19862001
"wasmBytesLimit": "jiterpreter-wasm-bytes-limit",
19872002
"tableSize": "jiterpreter-table-size",
19882003
"aotTableSize": "jiterpreter-aot-table-size",
2004+
"maxModuleSize": "jiterpreter-max-module-size",
19892005
};
19902006

19912007
let optionsVersion = -1;

src/mono/browser/runtime/jiterpreter-trace-generator.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import {
3838
traceEip, nullCheckValidation,
3939
traceNullCheckOptimizations,
4040
nullCheckCaching, defaultTraceBackBranches,
41-
maxCallHandlerReturnAddresses,
41+
maxCallHandlerReturnAddresses, moduleHeaderSizeMargin,
4242

4343
mostRecentOptions,
4444

@@ -275,10 +275,7 @@ export function generateWasmBody (
275275
break;
276276
}
277277

278-
// HACK: Browsers set a limit of 4KB, we lower it slightly since a single opcode
279-
// might generate a ton of code and we generate a bit of an epilogue after
280-
// we finish
281-
const maxBytesGenerated = 3840,
278+
const maxBytesGenerated = builder.options.maxModuleSize - moduleHeaderSizeMargin,
282279
spaceLeft = maxBytesGenerated - builder.bytesGeneratedSoFar - builder.cfg.overheadBytes;
283280
if (builder.size >= spaceLeft) {
284281
// mono_log_info(`trace too big, estimated size is ${builder.size + builder.bytesGeneratedSoFar}`);

src/mono/browser/runtime/jiterpreter.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ export const
7272
useFullNames = false,
7373
// Use the mono_debug_count() API (set the COUNT=n env var) to limit the number of traces to compile
7474
useDebugCount = false,
75-
// Web browsers limit synchronous module compiles to 4KB
76-
maxModuleSize = 4080;
75+
// Subtracted from the maxModuleSize option value to make space for a typical header
76+
moduleHeaderSizeMargin = 300;
7777

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

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

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

916-
const buf = builder.getArrayView();
916+
const buf = builder.getArrayView(false, true);
917917
for (let i = 0; i < buf.length; i++) {
918918
const b = buf[i];
919919
if (b < 0x10)

src/mono/mono/utils/options-def.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ DEFINE_BOOL(jiterpreter_constant_propagation, "jiterpreter-constant-propagation"
140140
// When compiling a jit_call wrapper, bypass sharedvt wrappers if possible by inlining their
141141
// logic into the compiled wrapper and calling the target AOTed function with native call convention
142142
DEFINE_BOOL(jiterpreter_direct_jit_call, "jiterpreter-direct-jit-calls", TRUE, "Bypass gsharedvt wrappers when compiling JIT call wrappers")
143-
// any trace that doesn't have at least this many meaningful (non-nop) opcodes in it will be rejected
143+
// when deciding whether to generate a trace, we sum the value of sequential opcodes that will fit into it
144+
// and reject any trace entry point where the score is below this value
144145
DEFINE_INT(jiterpreter_minimum_trace_value, "jiterpreter-minimum-trace-value", 18, "Reject traces that perform less than this amount of (approximate) work")
145146
// ensure that we don't create trace entry points too close together
146147
DEFINE_INT(jiterpreter_minimum_distance_between_traces, "jiterpreter-minimum-distance-between-traces", 4, "Don't insert entry points closer together than this")
@@ -175,6 +176,7 @@ DEFINE_INT(jiterpreter_table_size, "jiterpreter-table-size", 6 * 1024, "Size of
175176
// to bloat to an unacceptable degree. In practice this is still better than nothing.
176177
// FIXME: In the future if we find a way to reduce the number of unique tables we can raise this constant
177178
DEFINE_INT(jiterpreter_aot_table_size, "jiterpreter-aot-table-size", 3 * 1024, "Size of the jiterpreter AOT trampoline function tables")
179+
DEFINE_INT(jiterpreter_max_module_size, "jiterpreter-max-module-size", 4080, "Size limit for jiterpreter generated WASM modules")
178180
#endif // HOST_BROWSER
179181

180182
#if defined(TARGET_WASM) || defined(TARGET_IOS) || defined(TARGET_TVOS) || defined (TARGET_MACCAT)

0 commit comments

Comments
 (0)