Reimplement float-to-int conversions in the jiterpreter by generating a series of conditional checks that match what clang does, in order to work around the wasm conversion opcode being unspecified for nan/inf.
Fix bug in jiterpreter instrumentation that would suppress the dumped module blob and error message if an unhandled error occurred while generating a block.
Fixes #81900
}
}
+EMSCRIPTEN_KEEPALIVE int
+mono_jiterp_encode_leb_signed_boundary (unsigned char * destination, int bits, int sign) {
+ if (!destination)
+ return 0;
+
+ int64_t value;
+ switch (bits) {
+ case 32:
+ value = sign >= 0 ? INT_MAX : INT_MIN;
+ break;
+ case 64:
+ value = sign >= 0 ? INT64_MAX : INT64_MIN;
+ break;
+ default:
+ return 0;
+ }
+
+ return mono_jiterp_encode_leb64_ref(destination, &value, TRUE);
+}
+
// Many of the following functions implement various opcodes or provide support for opcodes
// so that jiterpreter traces don't have to inline dozens of wasm instructions worth of
// complex logic - these are designed to match interp.c
}
EMSCRIPTEN_KEEPALIVE int
-mono_jiterp_conv_ovf (void *dest, void *src, int opcode) {
+mono_jiterp_conv (void *dest, void *src, int opcode) {
switch (opcode) {
case MINT_CONV_OVF_I4_I8: {
gint64 val = *(gint64*)src;
}
return 0;
}
+
+ case MINT_CONV_OVF_I8_R8:
+ case MINT_CONV_OVF_I8_R4: {
+ double val;
+ if (opcode == MINT_CONV_OVF_I8_R4)
+ val = *(float*)src;
+ else
+ val = *(double*)src;
+
+ return mono_try_trunc_i64(val, dest);
+ }
}
// TODO: return 0 on success and a unique bailout code on failure?
[true, "mono_jiterp_get_offset_of_array_data", "number", []],
[false, "mono_jiterp_encode_leb52", "number", ["number", "number", "number"]],
[false, "mono_jiterp_encode_leb64_ref", "number", ["number", "number", "number"]],
+ [false, "mono_jiterp_encode_leb_signed_boundary", "number", ["number", "number", "number"]],
[true, "mono_jiterp_type_is_byref", "number", ["number"]],
[true, "mono_jiterp_get_size_of_stackval", "number", []],
[true, "mono_jiterp_parse_option", "number", ["string"]],
// Returns bytes written (or 0 if writing failed)
// Source is the address of a 64-bit int or uint
mono_jiterp_encode_leb64_ref(destination: VoidPtr, source: VoidPtr, valueIsSigned: number): number;
+ // Returns bytes written (or 0 if writing failed)
+ // bits is either 32 or 64 (the size of the value)
+ // sign is >= 0 for INTnn_MAX and < 0 for INTnn_MIN
+ mono_jiterp_encode_leb_signed_boundary(destination: VoidPtr, bits: number, sign: number): number;
mono_jiterp_type_is_byref(type: MonoType): number;
mono_jiterp_get_size_of_stackval(): number;
mono_jiterp_type_get_raw_value_size(type: MonoType): number;
return this.current.appendF64(value);
}
+ appendBoundaryValue (bits: number, sign: number) {
+ return this.current.appendBoundaryValue(bits, sign);
+ }
+
appendULeb (value: number | MintOpcodePtr) {
return this.current.appendULeb(<any>value);
}
getArrayView (fullCapacity?: boolean) {
if (this.stackSize > 1)
- throw new Error("Stack not empty");
+ throw new Error("Jiterpreter block stack not empty");
return this.stack[0].getArrayView(fullCapacity);
}
return result;
}
+ appendBoundaryValue (bits: number, sign: number) {
+ if (this.size + 8 >= this.capacity)
+ throw new Error("Buffer full");
+
+ const bytesWritten = cwraps.mono_jiterp_encode_leb_signed_boundary(<any>(this.buffer + this.size), bits, sign);
+ if (bytesWritten < 1)
+ throw new Error(`Failed to encode ${bits} bit boundary value with sign ${sign}`);
+ this.size += bytesWritten;
+ return bytesWritten;
+ }
+
appendULeb (value: number) {
if (this.size + 8 >= this.capacity)
throw new Error("Buffer full");
case MintOpcode.MINT_CONV_OVF_U4_I8:
case MintOpcode.MINT_CONV_OVF_I4_U8:
case MintOpcode.MINT_CONV_OVF_I4_R8:
+ case MintOpcode.MINT_CONV_OVF_I8_R8:
case MintOpcode.MINT_CONV_OVF_I4_R4:
+ case MintOpcode.MINT_CONV_OVF_I8_R4:
case MintOpcode.MINT_CONV_OVF_U4_I4:
builder.block();
// dest, src
append_ldloca(builder, getArgU16(ip, 1), 8, true);
append_ldloca(builder, getArgU16(ip, 2), 0);
builder.i32_const(opcode);
- builder.callImport("conv_ovf");
+ builder.callImport("conv");
// If the conversion succeeded, continue, otherwise bailout
builder.appendU8(WasmOpcode.br_if);
builder.appendULeb(0);
builder.endBlock();
break;
+ /*
+ * The native conversion opcodes for these are not specified for nan/inf, and v8
+ * chooses to throw, so we have to do some tricks to identify non-finite values
+ * and substitute INTnn_MIN, like clang would.
+ * This attempts to reproduce what clang does in -O3 with no special flags set:
+ *
+ * f64 -> i64
+ *
+ * block
+ * local.get 0
+ * f64.abs
+ * f64.const 0x1p63
+ * f64.lt
+ * i32.eqz
+ * br_if 0 # 0: down to label0
+ * local.get 0
+ * i64.trunc_f64_s
+ * return
+ * end_block # label0:
+ * i64.const -9223372036854775808
+ *
+ * f32 -> i32
+ *
+ * block
+ * local.get 0
+ * f32.abs
+ * f32.const 0x1p31
+ * f32.lt
+ * i32.eqz
+ * br_if 0 # 0: down to label3
+ * local.get 0
+ * i32.trunc_f32_s
+ * return
+ * end_block # label3:
+ * i32.const -2147483648
+ */
+ case MintOpcode.MINT_CONV_I4_R4:
+ case MintOpcode.MINT_CONV_I4_R8:
+ case MintOpcode.MINT_CONV_I8_R4:
+ case MintOpcode.MINT_CONV_I8_R8: {
+ const isF32 = (opcode === MintOpcode.MINT_CONV_I4_R4) ||
+ (opcode === MintOpcode.MINT_CONV_I8_R4),
+ isI64 = (opcode === MintOpcode.MINT_CONV_I8_R4) ||
+ (opcode === MintOpcode.MINT_CONV_I8_R8),
+ limit = isI64
+ ? 9223372036854775807 // this will round up to 0x1p63
+ : 2147483648, // this is 0x1p31 exactly
+ tempLocal = isF32 ? "temp_f32" : "temp_f64";
+
+ // Pre-load locals for the result store at the end
+ builder.local("pLocals");
+
+ // Load src
+ append_ldloc(builder, getArgU16(ip, 2), isF32 ? WasmOpcode.f32_load : WasmOpcode.f64_load);
+ builder.local(tempLocal, WasmOpcode.tee_local);
+
+ // Detect whether the value is within the representable range for the target type
+ builder.appendU8(isF32 ? WasmOpcode.f32_abs : WasmOpcode.f64_abs);
+ builder.appendU8(isF32 ? WasmOpcode.f32_const : WasmOpcode.f64_const);
+ if (isF32)
+ builder.appendF32(limit);
+ else
+ builder.appendF64(limit);
+ builder.appendU8(isF32 ? WasmOpcode.f32_lt : WasmOpcode.f64_lt);
+
+ // Select value via an if block that returns the result
+ builder.block(isI64 ? WasmValtype.i64 : WasmValtype.i32, WasmOpcode.if_);
+ // Value in range so truncate it to the appropriate type
+ builder.local(tempLocal);
+ builder.appendU8(floatToIntTable[opcode]);
+ builder.appendU8(WasmOpcode.else_);
+ // Value out of range so load the appropriate boundary value
+ builder.appendU8(isI64 ? WasmOpcode.i64_const : WasmOpcode.i32_const);
+ builder.appendBoundaryValue(isI64 ? 64 : 32, -1);
+ builder.endBlock();
+
+ append_stloc_tail(builder, getArgU16(ip, 1), isI64 ? WasmOpcode.i64_store : WasmOpcode.i32_store);
+
+ break;
+ }
+
case MintOpcode.MINT_ADD_MUL_I4_IMM:
case MintOpcode.MINT_ADD_MUL_I8_IMM: {
const isI32 = opcode === MintOpcode.MINT_ADD_MUL_I4_IMM;
// For debugging
if (emitPadding)
builder.appendU8(WasmOpcode.nop);
- } else
+ } else {
+ if (instrumentedTraceId)
+ console.log(`instrumented trace ${traceName} aborted for opcode ${opname} @${_ip}`);
record_abort(traceIp, _ip, traceName, opcode);
+ }
}
if (emitPadding)
// operator, lhsLoadOperator, rhsLoadOperator, storeOperator
type OpRec4 = [WasmOpcode, WasmOpcode, WasmOpcode, WasmOpcode];
+const floatToIntTable : { [opcode: number]: WasmOpcode } = {
+ [MintOpcode.MINT_CONV_I4_R4]: WasmOpcode.i32_trunc_s_f32,
+ [MintOpcode.MINT_CONV_I8_R4]: WasmOpcode.i64_trunc_s_f32,
+ [MintOpcode.MINT_CONV_I4_R8]: WasmOpcode.i32_trunc_s_f64,
+ [MintOpcode.MINT_CONV_I8_R8]: WasmOpcode.i64_trunc_s_f64,
+};
+
// thanks for making this as complex as possible, typescript
const unopTable : { [opcode: number]: OpRec3 | undefined } = {
[MintOpcode.MINT_CEQ0_I4]: [WasmOpcode.i32_eqz, WasmOpcode.i32_load, WasmOpcode.i32_store],
[MintOpcode.MINT_CONV_R8_R4]: [WasmOpcode.f64_promote_f32, WasmOpcode.f32_load, WasmOpcode.f64_store],
[MintOpcode.MINT_CONV_R4_R8]: [WasmOpcode.f32_demote_f64, WasmOpcode.f64_load, WasmOpcode.f32_store],
- [MintOpcode.MINT_CONV_I4_R4]: [WasmOpcode.i32_trunc_s_f32, WasmOpcode.f32_load, WasmOpcode.i32_store],
- [MintOpcode.MINT_CONV_I8_R4]: [WasmOpcode.i64_trunc_s_f32, WasmOpcode.f32_load, WasmOpcode.i64_store],
- [MintOpcode.MINT_CONV_I4_R8]: [WasmOpcode.i32_trunc_s_f64, WasmOpcode.f64_load, WasmOpcode.i32_store],
- [MintOpcode.MINT_CONV_I8_R8]: [WasmOpcode.i64_trunc_s_f64, WasmOpcode.f64_load, WasmOpcode.i64_store],
-
[MintOpcode.MINT_CONV_I8_I4]: [WasmOpcode.nop, WasmOpcode.i64_load32_s, WasmOpcode.i64_store],
[MintOpcode.MINT_CONV_I8_U4]: [WasmOpcode.nop, WasmOpcode.i64_load32_u, WasmOpcode.i64_store],
["newobj_i", "newobj_i", getRawCwrap("mono_jiterp_try_newobj_inlined")],
["ld_del_ptr", "ld_del_ptr", getRawCwrap("mono_jiterp_ld_delegate_method_ptr")],
["ldtsflda", "ldtsflda", getRawCwrap("mono_jiterp_ldtsflda")],
- ["conv_ovf", "conv_ovf", getRawCwrap("mono_jiterp_conv_ovf")],
+ ["conv", "conv", getRawCwrap("mono_jiterp_conv")],
["relop_fp", "relop_fp", getRawCwrap("mono_jiterp_relop_fp")],
["safepoint", "safepoint", getRawCwrap("mono_jiterp_auto_safepoint")],
["hashcode", "hashcode", getRawCwrap("mono_jiterp_get_hashcode")],
}, WasmValtype.void, true
);
builder.defineType(
- "conv_ovf", {
+ "conv", {
"destination": WasmValtype.i32,
"source": WasmValtype.i32,
"opcode": WasmValtype.i32,
"math_lhs32": WasmValtype.i32,
"math_rhs32": WasmValtype.i32,
"math_lhs64": WasmValtype.i64,
- "math_rhs64": WasmValtype.i64
- // "tempi64": WasmValtype.i64
+ "math_rhs64": WasmValtype.i64,
+ "temp_f32": WasmValtype.f32,
+ "temp_f64": WasmValtype.f64,
});
if (emitPadding) {
console.log(`// MONO_WASM: ${traceName} generated, blob follows //`);
let s = "", j = 0;
try {
+ // We may have thrown an uncaught exception while inside a block,
+ // so we need to pop it for getArrayView to work.
+ while (builder.activeBlocks > 0)
+ builder.endBlock();
+
if (builder.inSection)
builder.endSection();
} catch {