[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:
parent
1113057e3e
commit
936b61a209
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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) \
|
||||
|
@ -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),
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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));
|
||||
})();
|
||||
|
@ -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));
|
||||
|
@ -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)});
|
||||
|
Loading…
Reference in New Issue
Block a user