[wasm-gc] Canonicalize JS Numbers as i31ref at the boundary

JS numbers flowing into Wasm as i31ref should be canonicalized at the
boundary. In-range numbers get canonicalized to Smis, and out-of-range
numbers to HeapNumbers. This way, casting to i31ref, or checking for
i31ref when casting to other types, is reduced to a Smi check.

Bug: v8:7748
Change-Id: Icd2bbca7870c094f32ddc9cba1d2be16207e80d1
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4008345
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Cr-Commit-Position: refs/heads/main@{#84219}
This commit is contained in:
Manos Koukoutos 2022-11-11 15:00:36 +01:00 committed by V8 LUCI CQ
parent 1113057e3e
commit 936b61a209
14 changed files with 237 additions and 103 deletions

View File

@ -59,6 +59,8 @@ extern runtime WasmStringViewWtf8Encode(
Context, WasmInstanceObject, Smi, ByteArray, Number, Number, Number): JSAny;
extern runtime WasmStringViewWtf8Slice(
Context, ByteArray, Number, Number): String;
extern runtime WasmJSToWasmObject(
Context, WasmInstanceObject, JSAny, Smi): JSAny;
}
namespace unsafe {
@ -67,10 +69,8 @@ extern macro Allocate(intptr, constexpr AllocationFlag): HeapObject;
}
namespace wasm {
const kAnyTableType: constexpr int31
const kAnyType: constexpr int31
generates 'wasm::kWasmAnyRef.raw_bit_field()';
const kAnyNonNullTableType: constexpr int31
generates 'wasm::kWasmAnyNonNullableRef.raw_bit_field()';
const kMaxPolymorphism:
constexpr int31 generates 'wasm::kMaxPolymorphism';
@ -1151,28 +1151,11 @@ builtin WasmStringViewIterSlice(
string::SubString(string, Convert<uintptr>(start), Convert<uintptr>(end));
}
transitioning builtin WasmExternInternalize(implicit context: Context)(
externObject: JSAny): JSAny {
const innerObject =
WasmGetOwnProperty(externObject, WasmWrappedObjectSymbolConstant());
if (innerObject == Undefined) {
return externObject;
}
return innerObject;
}
builtin WasmExternInternalize(externObject: JSAny): JSAny {
const instance = LoadInstanceFromFrame();
const context = LoadContextFromInstance(instance);
transitioning builtin WasmExternExternalize(implicit context: Context)(
anyObject: JSAny): JSAny {
typeswitch (anyObject) {
case (wasmArray: WasmArray): {
return WasmAllocateObjectWrapper(wasmArray);
}
case (wasmStruct: WasmStruct): {
return WasmAllocateObjectWrapper(wasmStruct);
}
case (JSAny): {
return anyObject;
}
}
tail runtime::WasmJSToWasmObject(
context, instance, externObject, SmiConstant(kAnyType));
}
}

View File

@ -1021,8 +1021,14 @@ Node* WasmGraphBuilder::Unop(wasm::WasmOpcode opcode, Node* input,
return BuildAsmjsLoadMem(MachineType::Float32(), input);
case wasm::kExprF64AsmjsLoadMem:
return BuildAsmjsLoadMem(MachineType::Float64(), input);
case wasm::kExprExternInternalize:
return gasm_->WasmExternInternalize(input);
case wasm::kExprExternInternalize: {
// TODO(7748): Either add fast path for non-numbers, or implement
// entirely in TF.
Node* parameters[] = {GetInstance(), input,
mcgraph()->IntPtrConstant(IntToSmi(static_cast<int>(
wasm::kWasmAnyRef.raw_bit_field())))};
return BuildCallToRuntime(Runtime::kWasmJSToWasmObject, parameters, 3);
}
case wasm::kExprExternExternalize:
return gasm_->WasmExternExternalize(input);
default:
@ -6213,41 +6219,50 @@ Node* WasmGraphBuilder::StringViewIterSlice(Node* view, CheckForNull null_check,
Operator::kEliminatable, view, codepoints);
}
// 1 bit V8 Smi tag, 31 bits V8 Smi shift, 1 bit i31ref high-bit truncation.
constexpr int kI31To32BitSmiShift = 33;
Node* WasmGraphBuilder::I31New(Node* input) {
if (SmiValuesAre31Bits()) {
if constexpr (SmiValuesAre31Bits()) {
return gasm_->Word32Shl(input, gasm_->BuildSmiShiftBitsConstant32());
} else {
DCHECK(SmiValuesAre32Bits());
// Set the topmost bit to sign-extend the second bit. This way,
// interpretation in JS (if this value escapes there) will be the same as
// i31.get_s.
input = gasm_->BuildChangeInt32ToIntPtr(input);
return gasm_->WordSar(
gasm_->WordShl(input,
gasm_->IntPtrConstant(kSmiShiftSize + kSmiTagSize + 1)),
gasm_->IntPtrConstant(1));
}
DCHECK(SmiValuesAre32Bits());
input = gasm_->BuildChangeInt32ToIntPtr(input);
return gasm_->WordShl(input, gasm_->IntPtrConstant(kI31To32BitSmiShift));
}
Node* WasmGraphBuilder::I31GetS(Node* input, CheckForNull null_check,
wasm::WasmCodePosition position) {
if (null_check == kWithNullCheck) input = AssertNotNull(input, position);
if (SmiValuesAre31Bits()) {
if constexpr (SmiValuesAre31Bits()) {
input = gasm_->BuildTruncateIntPtrToInt32(input);
return gasm_->Word32SarShiftOutZeros(input,
gasm_->BuildSmiShiftBitsConstant32());
} else {
DCHECK(SmiValuesAre32Bits());
// Topmost bit is already sign-extended.
return gasm_->BuildTruncateIntPtrToInt32(gasm_->WordSar(
input, gasm_->IntPtrConstant(kSmiShiftSize + kSmiTagSize)));
}
DCHECK(SmiValuesAre32Bits());
return gasm_->BuildTruncateIntPtrToInt32(
gasm_->WordSar(input, gasm_->IntPtrConstant(kI31To32BitSmiShift)));
}
Node* WasmGraphBuilder::I31GetU(Node* input, CheckForNull null_check,
wasm::WasmCodePosition position) {
if (null_check == kWithNullCheck) input = AssertNotNull(input, position);
if (SmiValuesAre31Bits()) {
if constexpr (SmiValuesAre31Bits()) {
input = gasm_->BuildTruncateIntPtrToInt32(input);
return gasm_->Word32Shr(input, gasm_->BuildSmiShiftBitsConstant32());
} else {
DCHECK(SmiValuesAre32Bits());
// We need to remove the topmost bit of the 32-bit Smi.
return gasm_->BuildTruncateIntPtrToInt32(
gasm_->WordShr(gasm_->WordShl(input, gasm_->IntPtrConstant(1)),
gasm_->IntPtrConstant(kSmiShiftSize + kSmiTagSize + 1)));
}
DCHECK(SmiValuesAre32Bits());
return gasm_->BuildTruncateIntPtrToInt32(
gasm_->WordShr(input, gasm_->IntPtrConstant(kI31To32BitSmiShift)));
}
Node* WasmGraphBuilder::SetType(Node* node, wasm::ValueType type) {
@ -6600,9 +6615,9 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
case wasm::HeapType::kNone:
case wasm::HeapType::kNoFunc:
case wasm::HeapType::kNoExtern:
case wasm::HeapType::kAny:
case wasm::HeapType::kI31:
UNREACHABLE();
case wasm::HeapType::kAny:
case wasm::HeapType::kFunc:
case wasm::HeapType::kStruct:
case wasm::HeapType::kArray:

View File

@ -293,12 +293,10 @@ Reduction WasmGCLowering::ReduceTypeGuard(Node* node) {
}
Reduction WasmGCLowering::ReduceWasmExternInternalize(Node* node) {
DCHECK_EQ(node->opcode(), IrOpcode::kWasmExternInternalize);
Node* object = NodeProperties::GetValueInput(node, 0);
// TODO(7748): Canonicalize HeapNumbers.
ReplaceWithValue(node, object);
node->Kill();
return Replace(object);
// TODO(7748): This is not used right now. Either use the
// WasmExternInternalize operator and implement its lowering here, or remove
// it entirely.
UNREACHABLE();
}
// TODO(7748): WasmExternExternalize is a no-op. Consider removing it.

View File

@ -121,9 +121,10 @@ Object ThrowWasmError(Isolate* isolate, MessageTemplate message,
// type; if the check succeeds, returns the object in its wasm representation;
// otherwise throws a type error.
RUNTIME_FUNCTION(Runtime_WasmJSToWasmObject) {
// This code is called from wrappers, so the "thread is wasm" flag is not set.
DCHECK_IMPLIES(trap_handler::IsTrapHandlerEnabled(),
!trap_handler::IsThreadInWasm());
// TODO(manoskouk): Use {SaveAndClearThreadInWasmFlag} in runtime-internal.cc
// and runtime-strings.cc.
bool thread_in_wasm = trap_handler::IsThreadInWasm();
if (thread_in_wasm) trap_handler::ClearThreadInWasm();
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
// 'raw_instance' can be either a WasmInstanceObject or undefined.
@ -145,9 +146,13 @@ RUNTIME_FUNCTION(Runtime_WasmJSToWasmObject) {
bool success = internal::wasm::JSToWasmObject(isolate, module, value, type,
&error_message)
.ToHandle(&result);
if (success) return *result;
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kWasmTrapJSTypeError));
Object ret = success ? *result
: isolate->Throw(*isolate->factory()->NewTypeError(
MessageTemplate::kWasmTrapJSTypeError));
if (thread_in_wasm && !isolate->has_pending_exception()) {
trap_handler::SetThreadInWasm();
}
return ret;
}
RUNTIME_FUNCTION(Runtime_WasmMemoryGrow) {

View File

@ -1839,9 +1839,16 @@ class LiftoffCompiler {
__ PushRegister(kI32, dst);
return;
}
case kExprExternInternalize:
// TODO(7748): Canonicalize heap numbers.
case kExprExternInternalize: {
LiftoffRegList pinned;
LiftoffRegister input = pinned.set(__ PopToRegister());
LiftoffAssembler::VarState input_state(kRefNull, input, 0);
CallRuntimeStub(WasmCode::kWasmExternInternalize,
MakeSig::Returns(kRef).Params(kRef), {input_state},
decoder->position());
__ PushRegister(kRef, LiftoffRegister(kReturnRegister0));
return;
}
case kExprExternExternalize:
// This is a no-op.
return;
@ -5822,18 +5829,19 @@ class LiftoffCompiler {
__ PushRegister(kRef, result);
}
// 1 bit Smi tag, 31 bits Smi shift, 1 bit i31ref high-bit truncation.
constexpr static int kI31To32BitSmiShift = 33;
void I31New(FullDecoder* decoder, const Value& input, Value* result) {
LiftoffRegister src = __ PopToRegister();
LiftoffRegister dst = __ GetUnusedRegister(kGpReg, {src}, {});
if (SmiValuesAre31Bits()) {
if constexpr (SmiValuesAre31Bits()) {
static_assert(kSmiTag == 0);
__ emit_i32_shli(dst.gp(), src.gp(), kSmiTagSize);
} else {
DCHECK(SmiValuesAre32Bits());
__ emit_i64_shli(dst, src, kI31To32BitSmiShift);
// Set the topmost bit to sign-extend the second bit. This way,
// interpretation in JS (if this value escapes there) will be the same as
// i31.get_s.
__ emit_i64_shli(dst, src, kSmiTagSize + kSmiShiftSize + 1);
__ emit_i64_sari(dst, dst, 1);
}
__ PushRegister(kRef, dst);
}
@ -5843,11 +5851,12 @@ class LiftoffCompiler {
LiftoffRegister src = pinned.set(__ PopToRegister());
MaybeEmitNullCheck(decoder, src.gp(), pinned, input.type);
LiftoffRegister dst = __ GetUnusedRegister(kGpReg, {src}, {});
if (SmiValuesAre31Bits()) {
if constexpr (SmiValuesAre31Bits()) {
__ emit_i32_sari(dst.gp(), src.gp(), kSmiTagSize);
} else {
DCHECK(SmiValuesAre32Bits());
__ emit_i64_sari(dst, src, kI31To32BitSmiShift);
// Topmost bit is already sign-extended.
__ emit_i64_sari(dst, src, kSmiTagSize + kSmiShiftSize);
}
__ PushRegister(kI32, dst);
}
@ -5857,11 +5866,13 @@ class LiftoffCompiler {
LiftoffRegister src = pinned.set(__ PopToRegister());
MaybeEmitNullCheck(decoder, src.gp(), pinned, input.type);
LiftoffRegister dst = __ GetUnusedRegister(kGpReg, {src}, {});
if (SmiValuesAre31Bits()) {
if constexpr (SmiValuesAre31Bits()) {
__ emit_i32_shri(dst.gp(), src.gp(), kSmiTagSize);
} else {
DCHECK(SmiValuesAre32Bits());
__ emit_i64_shri(dst, src, kI31To32BitSmiShift);
// Remove topmost bit.
__ emit_i64_shli(dst, src, 1);
__ emit_i64_shri(dst, dst, kSmiTagSize + kSmiShiftSize + 1);
}
__ PushRegister(kI32, dst);
}

View File

@ -306,9 +306,13 @@ void ConstantExpressionInterface::RttCanon(FullDecoder* decoder,
void ConstantExpressionInterface::I31New(FullDecoder* decoder,
const Value& input, Value* result) {
if (!generate_value()) return;
Address raw = static_cast<Address>(input.runtime_value.to_i32());
// 33 = 1 (Smi tag) + 31 (Smi shift) + 1 (i31ref high-bit truncation).
Address shifted = raw << (SmiValuesAre31Bits() ? 1 : 33);
Address raw = input.runtime_value.to_i32();
// We have to craft the Smi manually because we accept out-of-bounds inputs.
// For 32-bit Smi builds, set the topmost bit to sign-extend the second bit.
// This way, interpretation in JS (if this value escapes there) will be the
// same as i31.get_s.
intptr_t shifted =
static_cast<intptr_t>(raw << (kSmiTagSize + kSmiShiftSize + 1)) >> 1;
result->runtime_value =
WasmValue(handle(Smi(shifted), isolate_), wasm::kWasmI31Ref.AsNonNull());
}

View File

@ -722,9 +722,6 @@ constexpr ValueType kWasmNullFuncRef = ValueType::RefNull(HeapType::kNoFunc);
// Constants used by the generic js-to-wasm wrapper.
constexpr int kWasmValueKindBitsMask = (1u << ValueType::kKindBits) - 1;
// This is used in wasm.tq.
constexpr ValueType kWasmAnyNonNullableRef = ValueType::Ref(HeapType::kAny);
#define FOREACH_WASMVALUE_CTYPES(V) \
V(kI32, int32_t) \
V(kI64, int64_t) \

View File

@ -148,8 +148,7 @@ struct WasmModule;
V(WasmStringViewIterAdvance) \
V(WasmStringViewIterRewind) \
V(WasmStringViewIterSlice) \
V(WasmExternInternalize) \
V(WasmExternExternalize)
V(WasmExternInternalize)
// Sorted, disjoint and non-overlapping memory regions. A region is of the
// form [start, end). So there's no [start, end), [end, other_end),

View File

@ -2199,6 +2199,37 @@ Handle<AsmWasmData> AsmWasmData::New(
return result;
}
namespace {
constexpr int32_t kInt31MaxValue = 0x3fffffff;
constexpr int32_t kInt31MinValue = -kInt31MaxValue - 1;
// Tries to canonicalize a HeapNumber to an i31ref Smi. Returns the original
// HeapNumber if it fails.
Handle<Object> CanonicalizeHeapNumber(Handle<Object> number, Isolate* isolate) {
double double_value = Handle<HeapNumber>::cast(number)->value();
if (double_value >= kInt31MinValue && double_value <= kInt31MaxValue &&
!IsMinusZero(double_value) &&
double_value == FastI2D(FastD2I(double_value))) {
return handle(Smi::FromInt(FastD2I(double_value)), isolate);
}
return number;
}
// Tries to canonicalize a Smi into an i31 Smi. Returns a HeapNumber if it
// fails.
Handle<Object> CanonicalizeSmi(Handle<Object> smi, Isolate* isolate) {
if constexpr (SmiValuesAre31Bits()) return smi;
int32_t value = Handle<Smi>::cast(smi)->value();
if (value <= kInt31MaxValue && value >= kInt31MinValue) {
return smi;
} else {
return isolate->factory()->NewHeapNumber(value);
}
}
} // namespace
namespace wasm {
MaybeHandle<Object> JSToWasmObject(Isolate* isolate, const WasmModule* module,
Handle<Object> value, ValueType expected,
@ -2224,8 +2255,6 @@ MaybeHandle<Object> JSToWasmObject(Isolate* isolate, const WasmModule* module,
}
V8_FALLTHROUGH;
case kRef: {
// TODO(7748): Allow all in-range numbers for i31. Make sure to convert
// Smis to i31refs if needed.
// TODO(7748): Streamline interaction of undefined and (ref any).
HeapType::Representation repr = expected.heap_representation();
switch (repr) {
@ -2249,6 +2278,10 @@ MaybeHandle<Object> JSToWasmObject(Isolate* isolate, const WasmModule* module,
return {};
}
case HeapType::kAny: {
if (value->IsSmi()) return CanonicalizeSmi(value, isolate);
if (value->IsHeapNumber()) {
return CanonicalizeHeapNumber(value, isolate);
}
if (!value->IsNull(isolate)) return value;
*error_message = "null is not allowed for (ref any)";
return {};
@ -2271,18 +2304,31 @@ MaybeHandle<Object> JSToWasmObject(Isolate* isolate, const WasmModule* module,
return {};
}
case HeapType::kEq: {
if (value->IsSmi() || value->IsWasmStruct() || value->IsWasmArray()) {
if (value->IsSmi()) {
Handle<Object> truncated = CanonicalizeSmi(value, isolate);
if (truncated->IsSmi()) return truncated;
} else if (value->IsHeapNumber()) {
Handle<Object> truncated = CanonicalizeHeapNumber(value, isolate);
if (truncated->IsSmi()) return truncated;
} else if (value->IsWasmStruct() || value->IsWasmArray()) {
return value;
}
*error_message =
"eqref object must be null (if nullable) or a wasm "
"i31/struct/array";
"eqref object must be null (if nullable), or a wasm "
"struct/array, or a Number that fits in i31ref range";
return {};
}
case HeapType::kI31: {
if (value->IsSmi()) return value;
if (value->IsSmi()) {
Handle<Object> truncated = CanonicalizeSmi(value, isolate);
if (truncated->IsSmi()) return truncated;
} else if (value->IsHeapNumber()) {
Handle<Object> truncated = CanonicalizeHeapNumber(value, isolate);
if (truncated->IsSmi()) return truncated;
}
*error_message =
"i31ref object must be null (if nullable) or a wasm i31";
"i31ref object must be null (if nullable) or a Number that fits "
"in i31ref range";
return {};
}
case HeapType::kString:

View File

@ -8,20 +8,20 @@ Module instantiated.
Tables populated.
Setting breakpoint
{
columnNumber : 246
columnNumber : 260
lineNumber : 0
scriptId : <scriptId>
}
Paused:
Script wasm://wasm/739f5f0a byte offset 246: Wasm opcode 0x01 (kExprNop)
Script wasm://wasm/8fd0ec76 byte offset 260: Wasm opcode 0x01 (kExprNop)
Scope:
at $main (0:246):
at $main (0:260):
- scope (wasm-expression-stack):
stack:
- scope (local):
$anyref_local: Struct ((ref $type0))
$anyref_local2: Array ((ref $type1))
$anyref_local_i31: null (anyref)
$anyref_local_i31: 30 (anyref)
$anyref_local_null: null (anyref)
- scope (module):
instance: exports: "exported_ref_table" (Table), "exported_func_table" (Table), "fill_tables" (Function), "main" (Function)
@ -31,7 +31,7 @@ at $main (0:246):
tables:
$import.any_table: 0: Array(2) (anyref), 1: Struct ((ref $type0)), 2: null (anyref)
$import.func_table: 0: function () { [native code] } (funcref), 1: function $my_func() { [native code] } (funcref), 2: null (funcref)
$exported_ref_table: 0: Struct ((ref $type0)), 1: Array ((ref $type1)), 2: null (anyref), 3: null (anyref)
$exported_ref_table: 0: Struct ((ref $type0)), 1: Array ((ref $type1)), 2: 30 (anyref), 3: null (anyref)
$exported_func_table: 0: function external_fct() { [native code] } (funcref), 1: function $my_func() { [native code] } (funcref), 2: null (funcref)
at (anonymous) (0:17):
- scope (global):

View File

@ -88,13 +88,8 @@ async function instantiateWasm() {
...wasmI32Const(1), ...wasmI32Const(20), ...wasmI32Const(21),
kGCPrefix, kExprArrayNewFixed, array_type, 2,
kExprTableSet, ref_table.index,
// TODO(7748): Reactivate this test when JS interop between i31refs and
// JS SMIs is fixed. The problem right now is the 33-bit shift for i31ref
// values on non-pointer-compressed platforms, which means i31refs and
// Smis have different encodings there but it's impossible to tell them
// apart.
// ...wasmI32Const(2), ...wasmI32Const(30),
// kGCPrefix, kExprI31New, kExprTableSet, ref_table.index,
...wasmI32Const(2), ...wasmI32Const(30),
kGCPrefix, kExprI31New, kExprTableSet, ref_table.index,
// Fill imported any table.
...wasmI32Const(1),
@ -121,12 +116,9 @@ async function instantiateWasm() {
...wasmI32Const(21),
kGCPrefix, kExprArrayNewFixed, array_type, 1,
kExprLocalSet, 1,
// Set local anyref_local_i31.
// TODO(7748): Reactivate this test when JS interop between i31refs and JS
// SMIs is fixed (same issue as above).
// ...wasmI32Const(30),
// kGCPrefix, kExprI31New,
// kExprLocalSet, 2,
...wasmI32Const(30),
kGCPrefix, kExprI31New,
kExprLocalSet, 2,
kExprNop,
];
let main = builder.addFunction('main', kSig_v_v)

View File

@ -54,3 +54,88 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
assertEquals(42, instance.exports.i31_null(0));
assertTraps(kTrapNullDereference, () => instance.exports.i31_null(1));
})();
(function I31RefJS() {
print(arguments.callee.name);
var builder = new WasmModuleBuilder();
builder.addFunction("roundtrip", makeSig([kWasmExternRef], [kWasmExternRef]))
.addBody([kExprLocalGet, 0, kGCPrefix, kExprExternInternalize,
kGCPrefix, kExprExternExternalize])
.exportFunc();
builder.addFunction("signed", makeSig([kWasmExternRef], [kWasmI32]))
.addBody([kExprLocalGet, 0, kGCPrefix, kExprExternInternalize,
kGCPrefix, kExprRefCast, kI31RefCode, kGCPrefix, kExprI31GetS])
.exportFunc();
builder.addFunction("unsigned", makeSig([kWasmExternRef], [kWasmI32]))
.addBody([kExprLocalGet, 0, kGCPrefix, kExprExternInternalize,
kGCPrefix, kExprRefCast, kI31RefCode, kGCPrefix, kExprI31GetU])
.exportFunc();
builder.addFunction("new", makeSig([kWasmI32], [kWasmExternRef]))
.addBody([kExprLocalGet, 0, kGCPrefix, kExprI31New,
kGCPrefix, kExprExternExternalize])
.exportFunc();
let instance = builder.instantiate();
assertEquals(0, instance.exports.roundtrip(0));
assertEquals(0, instance.exports.signed(0));
assertEquals(0, instance.exports.unsigned(0));
assertEquals(0, instance.exports.new(0));
assertEquals(123, instance.exports.roundtrip(123));
assertEquals(123, instance.exports.signed(123));
assertEquals(123, instance.exports.unsigned(123));
assertEquals(123, instance.exports.new(123));
// Max value.
assertEquals(0x3fffffff, instance.exports.roundtrip(0x3fffffff));
assertEquals(0x3fffffff, instance.exports.signed(0x3fffffff));
assertEquals(0x3fffffff, instance.exports.unsigned(0x3fffffff));
assertEquals(0x3fffffff, instance.exports.new(0x3fffffff));
// Double number.
assertEquals(1234.567, instance.exports.roundtrip(1234.567));
assertTraps(kTrapIllegalCast, () => instance.exports.signed(1234.567));
assertTraps(kTrapIllegalCast, () => instance.exports.unsigned(1234.567));
// Out-of-bounds positive integer.
assertEquals(0x40000000, instance.exports.roundtrip(0x40000000));
assertTraps(kTrapIllegalCast, () => instance.exports.signed(0x40000000));
assertTraps(kTrapIllegalCast, () => instance.exports.unsigned(0x40000000));
assertEquals(-0x40000000, instance.exports.new(0x40000000));
// Out-of-bounds negative integer.
assertEquals(-0x40000001, instance.exports.roundtrip(-0x40000001));
assertTraps(kTrapIllegalCast, () => instance.exports.signed(-0x40000001));
assertTraps(kTrapIllegalCast, () => instance.exports.unsigned(-0x40000001));
assertEquals(0x3fffffff, instance.exports.new(-0x40000001));
// Sign/zero extention.
assertEquals(-2, instance.exports.roundtrip(-2));
assertEquals(-2, instance.exports.signed(-2));
assertEquals(0x7ffffffe, instance.exports.unsigned(-2));
assertEquals(-2, instance.exports.new(-2));
// Min value.
assertEquals(-0x40000000, instance.exports.roundtrip(-0x40000000));
assertEquals(-0x40000000, instance.exports.signed(-0x40000000));
assertEquals(0x40000000, instance.exports.unsigned(-0x40000000));
assertEquals(-0x40000000, instance.exports.new(-0x40000000));
assertEquals(NaN, instance.exports.roundtrip(NaN));
assertTraps(kTrapIllegalCast, () => instance.exports.signed(NaN));
assertTraps(kTrapIllegalCast, () => instance.exports.unsigned(NaN));
assertEquals(-0, instance.exports.roundtrip(-0));
assertTraps(kTrapIllegalCast, () => instance.exports.signed(-0));
assertTraps(kTrapIllegalCast, () => instance.exports.unsigned(-0));
assertEquals(Infinity, instance.exports.roundtrip(Infinity));
assertTraps(kTrapIllegalCast, () => instance.exports.signed(Infinity));
assertTraps(kTrapIllegalCast, () => instance.exports.unsigned(Infinity));
assertEquals(-Infinity, instance.exports.roundtrip(-Infinity));
assertTraps(kTrapIllegalCast, () => instance.exports.signed(-Infinity));
assertTraps(kTrapIllegalCast, () => instance.exports.unsigned(-Infinity));
})();

View File

@ -221,6 +221,5 @@ for (let type of ["struct", "i31", "array"]) {
// Differently to structs and arrays, the i31 value is directly accessible in
// JavaScript. Similarly, a JS smi can be internalized as an i31ref.
// TODO(7748): Fix i31 interop with disabled pointer compression.
// assertEquals(12345, instance.exports.i31_externalize(12345));
// assertEquals([12345, 0], instance.exports.i31_internalize(12345));
assertEquals(12345, instance.exports.i31_externalize(12345));
assertEquals([12345, 0], instance.exports.i31_internalize(12345));

View File

@ -1718,7 +1718,7 @@ TEST_F(FunctionBodyDecoderTest, ReturnCallWithSubtype) {
WASM_FEATURE_SCOPE(return_call);
auto sig = MakeSig::Returns(kWasmAnyRef);
auto callee_sig = MakeSig::Returns(kWasmAnyNonNullableRef);
auto callee_sig = MakeSig::Returns(kWasmAnyRef.AsNonNull());
builder.AddFunction(&callee_sig);
ExpectValidates(&sig, {WASM_RETURN_CALL_FUNCTION0(0)});