Commit ba84faec authored by Bill Budge's avatar Bill Budge Committed by Commit Bot

[wasm] Use builtins for Wasm <-> JS type conversion

- Adds builtins to convert between Int32/Float64 and JS Number.
  - WasmInt32ToHeapNumber (bypass SMI test)
  - WasmFloat64ToNumber
- Adds builtins to convert between Tagged and Int32/Float64.
  - WasmTaggedNonSmiToInt32 (bypass SMI test)
  - WasmTaggedToFloat64

- Uses these builtins in Wasm import and export wrappers instead of
  generating the equivalent code inline.

Results of running Wasm/import-export-wrappers.js Benchmark:
https://docs.google.com/document/d/1QIB0xnqdJFRsOJKQYZ8DZgzWn4WysybgugbcO0sYcQA/edit?usp=sharing

NOTE: CL will need to be rebased after linkage fix lands.

Bug: v8:10070

Change-Id: Ib34507fcd18bdf80938b5707310a5a4f76cdec72
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2099445Reviewed-by: 's avatarAndreas Haas <ahaas@chromium.org>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Reviewed-by: 's avatarClemens Backes <clemensb@chromium.org>
Commit-Queue: Bill Budge <bbudge@chromium.org>
Cr-Commit-Position: refs/heads/master@{#67292}
parent db61168a
......@@ -837,6 +837,10 @@ namespace internal {
/* Wasm */ \
ASM(WasmCompileLazy, Dummy) \
ASM(WasmDebugBreak, Dummy) \
TFC(WasmInt32ToHeapNumber, WasmInt32ToHeapNumber) \
TFC(WasmTaggedNonSmiToInt32, WasmTaggedNonSmiToInt32) \
TFC(WasmFloat64ToNumber, WasmFloat64ToNumber) \
TFC(WasmTaggedToFloat64, WasmTaggedToFloat64) \
TFC(WasmAtomicNotify, WasmAtomicNotify) \
TFC(WasmI32AtomicWait32, WasmI32AtomicWait32) \
TFC(WasmI32AtomicWait64, WasmI32AtomicWait64) \
......
......@@ -37,6 +37,27 @@ class WasmBuiltinsAssembler : public CodeStubAssembler {
}
};
TF_BUILTIN(WasmInt32ToHeapNumber, WasmBuiltinsAssembler) {
TNode<Int32T> val = UncheckedCast<Int32T>(Parameter(Descriptor::kValue));
Return(AllocateHeapNumberWithValue(ChangeInt32ToFloat64(val)));
}
TF_BUILTIN(WasmTaggedNonSmiToInt32, WasmBuiltinsAssembler) {
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
Return(
ChangeTaggedNonSmiToInt32(context, CAST(Parameter(Descriptor::kValue))));
}
TF_BUILTIN(WasmFloat64ToNumber, WasmBuiltinsAssembler) {
TNode<Float64T> val = UncheckedCast<Float64T>(Parameter(Descriptor::kValue));
Return(ChangeFloat64ToTagged(val));
}
TF_BUILTIN(WasmTaggedToFloat64, WasmBuiltinsAssembler) {
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
Return(ChangeTaggedToFloat64(context, CAST(Parameter(Descriptor::kValue))));
}
TF_BUILTIN(WasmStackGuard, WasmBuiltinsAssembler) {
TNode<WasmInstanceObject> instance = LoadInstanceFromFrame();
TNode<Context> context = LoadContextFromInstance(instance);
......
......@@ -5242,6 +5242,22 @@ void CodeStubAssembler::TaggedToWord32OrBigIntImpl(
}
}
TNode<Int32T> CodeStubAssembler::TruncateNumberToWord32(TNode<Number> number) {
TVARIABLE(Int32T, var_result);
Label done(this), if_heapnumber(this);
GotoIfNot(TaggedIsSmi(number), &if_heapnumber);
var_result = SmiToInt32(CAST(number));
Goto(&done);
BIND(&if_heapnumber);
TNode<Float64T> value = LoadHeapNumberValue(CAST(number));
var_result = Signed(TruncateFloat64ToWord32(value));
Goto(&done);
BIND(&done);
return var_result.value();
}
TNode<Int32T> CodeStubAssembler::TruncateHeapNumberValueToWord32(
TNode<HeapNumber> object) {
TNode<Float64T> value = LoadHeapNumberValue(object);
......@@ -5471,6 +5487,42 @@ TNode<Float64T> CodeStubAssembler::ChangeNumberToFloat64(TNode<Number> value) {
return result.value();
}
TNode<Int32T> CodeStubAssembler::ChangeTaggedNonSmiToInt32(
TNode<Context> context, TNode<HeapObject> input) {
return Select<Int32T>(
IsHeapNumber(input),
[=] {
return Signed(TruncateFloat64ToWord32(LoadHeapNumberValue(input)));
},
[=] {
return TruncateNumberToWord32(
CAST(CallBuiltin(Builtins::kNonNumberToNumber, context, input)));
});
}
TNode<Float64T> CodeStubAssembler::ChangeTaggedToFloat64(TNode<Context> context,
TNode<Object> input) {
TVARIABLE(Float64T, var_result);
Label end(this), not_smi(this);
GotoIfNot(TaggedIsSmi(input), &not_smi);
var_result = SmiToFloat64(CAST(input));
Goto(&end);
BIND(&not_smi);
var_result = Select<Float64T>(
IsHeapNumber(CAST(input)),
[=] { return LoadHeapNumberValue(CAST(input)); },
[=] {
return ChangeNumberToFloat64(
CAST(CallBuiltin(Builtins::kNonNumberToNumber, context, input)));
});
Goto(&end);
BIND(&end);
return var_result.value();
}
TNode<WordT> CodeStubAssembler::TimesSystemPointerSize(
SloppyTNode<WordT> value) {
return WordShl(value, kSystemPointerSizeLog2);
......
......@@ -2414,6 +2414,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
TVariable<BigInt>* var_maybe_bigint,
TVariable<Smi>* var_feedback);
TNode<Int32T> TruncateNumberToWord32(TNode<Number> value);
// Truncate the floating point value of a HeapNumber to an Int32.
TNode<Int32T> TruncateHeapNumberValueToWord32(TNode<HeapNumber> object);
......@@ -2429,6 +2430,11 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
TNode<Uint32T> ChangeNumberToUint32(TNode<Number> value);
TNode<Float64T> ChangeNumberToFloat64(TNode<Number> value);
TNode<Int32T> ChangeTaggedNonSmiToInt32(TNode<Context> context,
TNode<HeapObject> input);
TNode<Float64T> ChangeTaggedToFloat64(TNode<Context> context,
TNode<Object> input);
void TaggedToNumeric(TNode<Context> context, TNode<Object> value,
TVariable<Numeric>* var_numeric);
void TaggedToNumericWithFeedback(TNode<Context> context, TNode<Object> value,
......
......@@ -284,6 +284,14 @@ void RunMicrotasksEntryDescriptor::InitializePlatformSpecific(
data->InitializePlatformSpecific(0, nullptr);
}
void WasmFloat64ToNumberDescriptor::InitializePlatformSpecific(
CallInterfaceDescriptorData* data) {
// Work around using eax, whose register code is 0, and leads to the FP
// parameter being passed via xmm0, which is not allocatable on ia32.
Register registers[] = {ecx};
data->InitializePlatformSpecific(arraysize(registers), registers);
}
} // namespace internal
} // namespace v8
......
......@@ -85,6 +85,8 @@ void CallDescriptors::InitializeOncePerProcess() {
DCHECK(!AllocateDescriptor{}.HasContextParameter());
DCHECK(!AllocateHeapNumberDescriptor{}.HasContextParameter());
DCHECK(!AbortDescriptor{}.HasContextParameter());
DCHECK(!WasmInt32ToHeapNumberDescriptor{}.HasContextParameter());
DCHECK(!WasmFloat64ToNumberDescriptor{}.HasContextParameter());
}
void CallDescriptors::TearDown() {
......@@ -375,6 +377,29 @@ void ArrayNArgumentsConstructorDescriptor::InitializePlatformSpecific(
data->InitializePlatformSpecific(arraysize(registers), registers);
}
void WasmInt32ToHeapNumberDescriptor::InitializePlatformSpecific(
CallInterfaceDescriptorData* data) {
DefaultInitializePlatformSpecific(data, kParameterCount);
}
void WasmTaggedNonSmiToInt32Descriptor::InitializePlatformSpecific(
CallInterfaceDescriptorData* data) {
DefaultInitializePlatformSpecific(data, kParameterCount);
}
#if !V8_TARGET_ARCH_IA32
// We need a custom descriptor on ia32 to avoid using xmm0.
void WasmFloat64ToNumberDescriptor::InitializePlatformSpecific(
CallInterfaceDescriptorData* data) {
DefaultInitializePlatformSpecific(data, kParameterCount);
}
#endif // !V8_TARGET_ARCH_IA32
void WasmTaggedToFloat64Descriptor::InitializePlatformSpecific(
CallInterfaceDescriptorData* data) {
DefaultInitializePlatformSpecific(data, kParameterCount);
}
void WasmMemoryGrowDescriptor::InitializePlatformSpecific(
CallInterfaceDescriptorData* data) {
DefaultInitializePlatformSpecific(data, kParameterCount);
......
......@@ -90,6 +90,10 @@ namespace internal {
V(TypeConversionStackParameter) \
V(Typeof) \
V(Void) \
V(WasmInt32ToHeapNumber) \
V(WasmTaggedNonSmiToInt32) \
V(WasmFloat64ToNumber) \
V(WasmTaggedToFloat64) \
V(WasmAtomicNotify) \
V(WasmI32AtomicWait32) \
V(WasmI32AtomicWait64) \
......@@ -1310,6 +1314,38 @@ class RunMicrotasksDescriptor final : public CallInterfaceDescriptor {
static Register MicrotaskQueueRegister();
};
class WasmInt32ToHeapNumberDescriptor final : public CallInterfaceDescriptor {
public:
DEFINE_PARAMETERS_NO_CONTEXT(kValue)
DEFINE_RESULT_AND_PARAMETER_TYPES(MachineType::AnyTagged(), // result
MachineType::Int32()) // value
DECLARE_DESCRIPTOR(WasmInt32ToHeapNumberDescriptor, CallInterfaceDescriptor)
};
class WasmTaggedNonSmiToInt32Descriptor final : public CallInterfaceDescriptor {
public:
DEFINE_PARAMETERS(kValue)
DEFINE_RESULT_AND_PARAMETER_TYPES(MachineType::Int32(), // result
MachineType::AnyTagged()) // value
DECLARE_DESCRIPTOR(WasmTaggedNonSmiToInt32Descriptor, CallInterfaceDescriptor)
};
class WasmFloat64ToNumberDescriptor final : public CallInterfaceDescriptor {
public:
DEFINE_PARAMETERS_NO_CONTEXT(kValue)
DEFINE_RESULT_AND_PARAMETER_TYPES(MachineType::AnyTagged(), // result
MachineType::Float64()) // value
DECLARE_DESCRIPTOR(WasmFloat64ToNumberDescriptor, CallInterfaceDescriptor)
};
class WasmTaggedToFloat64Descriptor final : public CallInterfaceDescriptor {
public:
DEFINE_PARAMETERS(kValue)
DEFINE_RESULT_AND_PARAMETER_TYPES(MachineType::Float64(), // result
MachineType::AnyTagged()) // value
DECLARE_DESCRIPTOR(WasmTaggedToFloat64Descriptor, CallInterfaceDescriptor)
};
class WasmMemoryGrowDescriptor final : public CallInterfaceDescriptor {
public:
DEFINE_PARAMETERS_NO_CONTEXT(kNumPages)
......
......@@ -435,6 +435,13 @@ void CodeAssembler::Return(TNode<WordT> value) {
return raw_assembler()->Return(value);
}
void CodeAssembler::Return(TNode<Float64T> value) {
DCHECK_EQ(1, raw_assembler()->call_descriptor()->ReturnCount());
DCHECK_EQ(MachineType::Float64(),
raw_assembler()->call_descriptor()->GetReturnType(0));
return raw_assembler()->Return(value);
}
void CodeAssembler::Return(TNode<WordT> value1, TNode<WordT> value2) {
DCHECK_EQ(2, raw_assembler()->call_descriptor()->ReturnCount());
DCHECK_EQ(
......
......@@ -571,6 +571,7 @@ class V8_EXPORT_PRIVATE CodeAssembler {
void Return(TNode<Int32T> value);
void Return(TNode<Uint32T> value);
void Return(TNode<WordT> value);
void Return(TNode<Float64T> value);
void Return(TNode<WordT> value1, TNode<WordT> value2);
void PopAndReturn(Node* pop, Node* value);
......
This diff is collapsed.
......@@ -50,6 +50,10 @@ struct WasmModule;
FOREACH_WASM_TRAPREASON(VTRAP) \
V(WasmCompileLazy) \
V(WasmDebugBreak) \
V(WasmInt32ToHeapNumber) \
V(WasmTaggedNonSmiToInt32) \
V(WasmFloat64ToNumber) \
V(WasmTaggedToFloat64) \
V(WasmAtomicNotify) \
V(WasmI32AtomicWait32) \
V(WasmI32AtomicWait64) \
......
......@@ -117,9 +117,38 @@ TEST(TestLinkageStubCall) {
CHECK_EQ(1, static_cast<int>(call_descriptor->ReturnCount()));
CHECK_EQ(Operator::kNoProperties, call_descriptor->properties());
CHECK_EQ(false, call_descriptor->IsJSFunctionCall());
CHECK_EQ(call_descriptor->GetParameterType(0), MachineType::AnyTagged());
CHECK_EQ(call_descriptor->GetReturnType(0), MachineType::AnyTagged());
// TODO(titzer): test linkage creation for outgoing stub calls.
}
TEST(TestFPLinkageStubCall) {
Isolate* isolate = CcTest::InitIsolateOnce();
Zone zone(isolate->allocator(), ZONE_NAME);
Callable callable =
Builtins::CallableFor(isolate, Builtins::kWasmFloat64ToNumber);
OptimizedCompilationInfo info(ArrayVector("test"), &zone, Code::STUB);
auto call_descriptor = Linkage::GetStubCallDescriptor(
&zone, callable.descriptor(), 0, CallDescriptor::kNoFlags,
Operator::kNoProperties);
CHECK(call_descriptor);
CHECK_EQ(0, static_cast<int>(call_descriptor->StackParameterCount()));
CHECK_EQ(1, static_cast<int>(call_descriptor->ParameterCount()));
CHECK_EQ(1, static_cast<int>(call_descriptor->ReturnCount()));
CHECK_EQ(Operator::kNoProperties, call_descriptor->properties());
CHECK_EQ(false, call_descriptor->IsJSFunctionCall());
CHECK_EQ(call_descriptor->GetInputType(1), MachineType::Float64());
CHECK(call_descriptor->GetInputLocation(1).IsRegister());
CHECK_EQ(call_descriptor->GetInputLocation(1).GetLocation(),
kFPReturnRegister0.code());
CHECK_EQ(call_descriptor->GetReturnType(0), MachineType::AnyTagged());
CHECK(call_descriptor->GetReturnLocation(0).IsRegister());
CHECK_EQ(call_descriptor->GetReturnLocation(0).GetLocation(),
kReturnRegister0.code());
}
} // namespace compiler
} // namespace internal
} // namespace v8
......@@ -3710,6 +3710,193 @@ TEST(InstructionSchedulingCallerSavedRegisters) {
FLAG_turbo_instruction_scheduling = old_turbo_instruction_scheduling;
}
TEST(WasmInt32ToHeapNumber) {
Isolate* isolate(CcTest::InitIsolateOnce());
int32_t test_values[] = {
// Smi values.
1,
0,
-1,
kSmiMaxValue,
kSmiMinValue,
// Test integers that can't be Smis (only possible if Smis are 31 bits).
#if defined(V8_HOST_ARCH_32_BIT) || defined(V8_31BIT_SMIS_ON_64BIT_ARCH)
kSmiMaxValue + 1,
kSmiMinValue - 1,
#endif
};
// FunctionTester can't handle Wasm type arguments, so for each test value,
// build a function with the arguments baked in, then generate a no-argument
// function to call.
const int kNumParams = 1;
for (size_t i = 0; i < arraysize(test_values); ++i) {
int32_t test_value = test_values[i];
CodeAssemblerTester asm_tester(isolate, kNumParams);
CodeStubAssembler m(asm_tester.state());
Node* context = m.Parameter(kNumParams + 1);
const TNode<Int32T> arg = m.Int32Constant(test_value);
const TNode<Object> call_result =
m.CallBuiltin(Builtins::kWasmInt32ToHeapNumber, context, arg);
m.Return(call_result);
FunctionTester ft(asm_tester.GenerateCode(), kNumParams);
Handle<Object> result = ft.Call().ToHandleChecked();
CHECK(result->IsNumber());
Handle<Object> expected(isolate->factory()->NewNumber(test_value));
CHECK(result->StrictEquals(*expected));
}
}
int32_t NumberToInt32(Handle<Object> number) {
if (number->IsSmi()) {
return Smi::ToInt(*number);
}
if (number->IsHeapNumber()) {
double num = HeapNumber::cast(*number).value();
return DoubleToInt32(num);
}
UNREACHABLE();
}
TEST(WasmTaggedNonSmiToInt32) {
Isolate* isolate(CcTest::InitIsolateOnce());
Factory* factory = isolate->factory();
HandleScope scope(isolate);
Handle<Object> test_values[] = {
// No Smis here; the builtin can't handle them correctly.
factory->NewNumber(-0.0),
factory->NewNumber(1.5),
factory->NewNumber(-1.5),
factory->NewNumber(2 * static_cast<double>(kSmiMaxValue)),
factory->NewNumber(2 * static_cast<double>(kSmiMinValue)),
factory->NewNumber(std::numeric_limits<double>::infinity()),
factory->NewNumber(-std::numeric_limits<double>::infinity()),
factory->NewNumber(-std::numeric_limits<double>::quiet_NaN()),
};
const int kNumParams = 2;
CodeAssemblerTester asm_tester(isolate, kNumParams);
CodeStubAssembler m(asm_tester.state());
Node* context = m.Parameter(kNumParams + 2);
const TNode<Object> arg = m.CAST(m.Parameter(0));
int32_t result = 0;
Node* base = m.IntPtrConstant(reinterpret_cast<intptr_t>(&result));
Node* value = m.CallBuiltin(Builtins::kWasmTaggedNonSmiToInt32, context, arg);
m.StoreNoWriteBarrier(MachineRepresentation::kWord32, base, value);
m.Return(m.UndefinedConstant());
FunctionTester ft(asm_tester.GenerateCode(), kNumParams);
for (size_t i = 0; i < arraysize(test_values); ++i) {
Handle<Object> test_value = test_values[i];
ft.Call(test_value);
int32_t expected = NumberToInt32(test_value);
CHECK_EQ(result, expected);
}
}
TEST(WasmFloat64ToNumber) {
Isolate* isolate(CcTest::InitIsolateOnce());
double test_values[] = {
// Smi values.
1,
0,
-1,
kSmiMaxValue,
kSmiMinValue,
// Non-Smi values.
-0.0,
1.5,
std::numeric_limits<double>::quiet_NaN(),
std::numeric_limits<double>::infinity(),
};
// FunctionTester can't handle Wasm type arguments, so for each test value,
// build a function with the arguments baked in, then generate a no-argument
// function to call.
const int kNumParams = 1;
for (size_t i = 0; i < arraysize(test_values); ++i) {
double test_value = test_values[i];
CodeAssemblerTester asm_tester(isolate, kNumParams);
CodeStubAssembler m(asm_tester.state());
Node* context = m.Parameter(kNumParams + 1);
const TNode<Float64T> arg = m.Float64Constant(test_value);
const TNode<Object> call_result =
m.CallBuiltin(Builtins::kWasmFloat64ToNumber, context, arg);
m.Return(call_result);
FunctionTester ft(asm_tester.GenerateCode(), kNumParams);
Handle<Object> result = ft.Call().ToHandleChecked();
CHECK(result->IsNumber());
Handle<Object> expected(isolate->factory()->NewNumber(test_value));
CHECK(result->StrictEquals(*expected) ||
(std::isnan(test_value) && std::isnan(result->Number())));
CHECK_EQ(result->IsSmi(), expected->IsSmi());
}
}
double NumberToFloat64(Handle<Object> number) {
if (number->IsSmi()) {
return Smi::ToInt(*number);
}
if (number->IsHeapNumber()) {
return HeapNumber::cast(*number).value();
}
UNREACHABLE();
}
TEST(WasmTaggedToFloat64) {
Isolate* isolate(CcTest::InitIsolateOnce());
Factory* factory = isolate->factory();
HandleScope scope(isolate);
Handle<Object> test_values[] = {
// Smi values.
handle(Smi::FromInt(1), isolate),
handle(Smi::FromInt(0), isolate),
handle(Smi::FromInt(-1), isolate),
handle(Smi::FromInt(kSmiMaxValue), isolate),
handle(Smi::FromInt(kSmiMinValue), isolate),
// Test some non-Smis.
factory->NewNumber(-0.0),
factory->NewNumber(1.5),
factory->NewNumber(-1.5),
factory->NewNumber(2 * kSmiMaxValue),
factory->NewNumber(2 * kSmiMinValue),
factory->NewNumber(std::numeric_limits<double>::infinity()),
factory->NewNumber(-std::numeric_limits<double>::infinity()),
factory->NewNumber(-std::numeric_limits<double>::quiet_NaN()),
};
const int kNumParams = 1;
CodeAssemblerTester asm_tester(isolate, kNumParams);
CodeStubAssembler m(asm_tester.state());
Node* context = m.Parameter(kNumParams + 2);
const TNode<Object> arg = m.CAST(m.Parameter(0));
double result = 0;
Node* base = m.IntPtrConstant(reinterpret_cast<intptr_t>(&result));
Node* value = m.CallBuiltin(Builtins::kWasmTaggedToFloat64, context, arg);
m.StoreNoWriteBarrier(MachineRepresentation::kFloat64, base, value);
m.Return(m.UndefinedConstant());
FunctionTester ft(asm_tester.GenerateCode(), kNumParams);
for (size_t i = 0; i < arraysize(test_values); ++i) {
Handle<Object> test_value = test_values[i];
ft.Call(test_value);
double expected = NumberToFloat64(test_value);
if (std::isnan(expected)) {
CHECK(std::isnan(result));
} else {
CHECK_EQ(result, expected);
}
}
}
} // namespace compiler
} // namespace internal
} // namespace v8
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment