Commit 9eba2d85 authored by Maya Lekova's avatar Maya Lekova Committed by Commit Bot

[fastcall] Add fast API testing facilities to d8

This CL provides the minimum necessary functionality to expose fast API
for testing in mjsunit, exposing the fast path for fuzzing. It exposes
a d8.test.fast_c_api with an `add_all` method, which exercises primitive
types. On x64, all integer and floating point types are supported. On
other platforms currently only 32-bit integers are included in the test.

Design doc:
https://docs.google.com/document/d/1KUKPfXkSRZTA2gMwaWbpQKlYfw0C-T6AE3XzC4viHbo/

Bug: chromium:1052746
Change-Id: Icc824199a26dd2abd2b869f5483a39d38e4dce3e
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2749154Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Reviewed-by: 's avatarGeorg Neis <neis@chromium.org>
Reviewed-by: 's avatarSathya Gunasekaran  <gsathya@chromium.org>
Commit-Queue: Maya Lekova <mslekova@chromium.org>
Cr-Commit-Position: refs/heads/master@{#73670}
parent cbbf6cc1
......@@ -5486,6 +5486,7 @@ v8_executable("d8") {
"src/d8/d8-js.cc",
"src/d8/d8-platforms.cc",
"src/d8/d8-platforms.h",
"src/d8/d8-test.cc",
"src/d8/d8.cc",
"src/d8/d8.h",
]
......
......@@ -187,6 +187,7 @@
#include <stddef.h>
#include <stdint.h>
#include <tuple>
#include <type_traits>
#include "v8config.h" // NOLINT(build/include_directory)
......
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/d8/d8.h"
#include "include/v8-fast-api-calls.h"
// This file exposes a d8.test.fast_c_api object, which adds testing facility
// for writing mjsunit tests that exercise fast API calls. The fast_c_api object
// contains an `add_all` method with the following signature:
// double add_all(bool /*should_fallback*/, int32_t, uint32_t,
// int64_t, uint64_t, float, double), that is wired as a "fast API" method.
// The fast_c_api object also supports querying the number of fast/slow calls
// and resetting these counters.
// Make sure to sync the following with src/compiler/globals.h.
#if defined(V8_TARGET_ARCH_X64)
#define V8_ENABLE_FP_PARAMS_IN_C_LINKAGE
#endif
namespace v8 {
namespace {
class FastCApiObject {
public:
static double AddAllFastCallback(ApiObject receiver, bool should_fallback,
int32_t arg_i32, uint32_t arg_u32,
int64_t arg_i64, uint64_t arg_u64,
float arg_f32, double arg_f64,
FastApiCallbackOptions& options) {
Value* receiver_value = reinterpret_cast<Value*>(&receiver);
CHECK(receiver_value->IsObject());
FastCApiObject* self = UnwrapObject(Object::Cast(receiver_value));
self->fast_call_count_++;
if (should_fallback) {
options.fallback = 1;
return 0;
}
return static_cast<double>(arg_i32) + static_cast<double>(arg_u32) +
static_cast<double>(arg_i64) + static_cast<double>(arg_u64) +
static_cast<double>(arg_f32) + arg_f64;
}
static void AddAllSlowCallback(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
FastCApiObject* self = UnwrapObject(*args.This());
self->slow_call_count_++;
HandleScope handle_scope(isolate);
double sum = 0;
if (args.Length() > 1) {
sum += args[1]->Int32Value(isolate->GetCurrentContext()).FromJust();
}
if (args.Length() > 2) {
sum += args[2]->Uint32Value(isolate->GetCurrentContext()).FromJust();
}
if (args.Length() > 3) {
sum += args[3]->IntegerValue(isolate->GetCurrentContext()).FromJust();
}
if (args.Length() > 4) {
sum += args[4]->IntegerValue(isolate->GetCurrentContext()).FromJust();
}
if (args.Length() > 5) {
sum += args[5]->NumberValue(isolate->GetCurrentContext()).FromJust();
} else {
sum += std::numeric_limits<double>::quiet_NaN();
}
if (args.Length() > 6) {
sum += args[6]->NumberValue(isolate->GetCurrentContext()).FromJust();
} else {
sum += std::numeric_limits<double>::quiet_NaN();
}
args.GetReturnValue().Set(Number::New(isolate, sum));
}
static int Add32BitIntFastCallback(ApiObject receiver, bool should_fallback,
int32_t arg_i32, uint32_t arg_u32,
FastApiCallbackOptions& options) {
Value* receiver_value = reinterpret_cast<Value*>(&receiver);
CHECK(receiver_value->IsObject());
FastCApiObject* self = UnwrapObject(Object::Cast(receiver_value));
self->fast_call_count_++;
if (should_fallback) {
options.fallback = 1;
return 0;
}
return arg_i32 + arg_u32;
}
static void Add32BitIntSlowCallback(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
FastCApiObject* self = UnwrapObject(*args.This());
self->slow_call_count_++;
HandleScope handle_scope(isolate);
double sum = 0;
if (args.Length() > 1) {
sum += args[1]->Int32Value(isolate->GetCurrentContext()).FromJust();
}
if (args.Length() > 2) {
sum += args[2]->Uint32Value(isolate->GetCurrentContext()).FromJust();
}
args.GetReturnValue().Set(Number::New(isolate, sum));
}
static void FastCallCount(const FunctionCallbackInfo<Value>& args) {
FastCApiObject* self = UnwrapObject(*args.This());
args.GetReturnValue().Set(
Number::New(args.GetIsolate(), self->fast_call_count()));
}
static void SlowCallCount(const FunctionCallbackInfo<Value>& args) {
FastCApiObject* self = UnwrapObject(*args.This());
args.GetReturnValue().Set(
Number::New(args.GetIsolate(), self->slow_call_count()));
}
static void ResetCounts(const FunctionCallbackInfo<Value>& args) {
FastCApiObject* self = UnwrapObject(*args.This());
self->reset_counts();
args.GetReturnValue().Set(Undefined(args.GetIsolate()));
}
static void SupportsFPParams(const FunctionCallbackInfo<Value>& info) {
FastCApiObject* self = UnwrapObject(*info.This());
info.GetReturnValue().Set(self->supports_fp_params_);
}
int fast_call_count() const { return fast_call_count_; }
int slow_call_count() const { return slow_call_count_; }
void reset_counts() {
fast_call_count_ = 0;
slow_call_count_ = 0;
}
static const int kV8WrapperObjectIndex = 1;
private:
static FastCApiObject* UnwrapObject(Object* object) {
i::Address addr = *reinterpret_cast<i::Address*>(object);
auto instance_type = i::Internals::GetInstanceType(addr);
if (instance_type != i::Internals::kJSObjectType &&
instance_type != i::Internals::kJSApiObjectType &&
instance_type != i::Internals::kJSSpecialApiObjectType) {
return nullptr;
}
FastCApiObject* wrapped = reinterpret_cast<FastCApiObject*>(
object->GetAlignedPointerFromInternalField(kV8WrapperObjectIndex));
CHECK_NOT_NULL(wrapped);
return wrapped;
}
int fast_call_count_ = 0, slow_call_count_ = 0;
#ifdef V8_ENABLE_FP_PARAMS_IN_C_LINKAGE
bool supports_fp_params_ = true;
#else // V8_ENABLE_FP_PARAMS_IN_C_LINKAGE
bool supports_fp_params_ = false;
#endif // V8_ENABLE_FP_PARAMS_IN_C_LINKAGE
};
// The object is statically initialized for simplicity, typically the embedder
// will take care of managing their C++ objects lifetime.
FastCApiObject kFastCApiObject;
} // namespace
void CreateObject(const FunctionCallbackInfo<Value>& info) {
Local<Object> api_object = info.Holder();
api_object->SetAlignedPointerInInternalField(
FastCApiObject::kV8WrapperObjectIndex,
reinterpret_cast<void*>(&kFastCApiObject));
api_object->SetAccessorProperty(
String::NewFromUtf8Literal(info.GetIsolate(), "supports_fp_params"),
FunctionTemplate::New(info.GetIsolate(), FastCApiObject::SupportsFPParams)
->GetFunction(api_object->GetCreationContext().ToLocalChecked())
.ToLocalChecked());
}
Local<FunctionTemplate> Shell::CreateTestFastCApiTemplate(Isolate* isolate) {
Local<FunctionTemplate> api_obj_ctor =
FunctionTemplate::New(isolate, CreateObject);
Local<Signature> signature = Signature::New(isolate, api_obj_ctor);
{
CFunction add_all_c_func =
CFunction::Make(FastCApiObject::AddAllFastCallback);
api_obj_ctor->PrototypeTemplate()->Set(
isolate, "add_all",
FunctionTemplate::New(isolate, FastCApiObject::AddAllSlowCallback,
Local<Value>(), signature, 1,
ConstructorBehavior::kThrow,
SideEffectType::kHasSideEffect, &add_all_c_func));
CFunction add_32bit_int_c_func =
CFunction::Make(FastCApiObject::Add32BitIntFastCallback);
api_obj_ctor->PrototypeTemplate()->Set(
isolate, "add_32bit_int",
FunctionTemplate::New(
isolate, FastCApiObject::Add32BitIntSlowCallback, Local<Value>(),
signature, 1, ConstructorBehavior::kThrow,
SideEffectType::kHasSideEffect, &add_32bit_int_c_func));
api_obj_ctor->PrototypeTemplate()->Set(
isolate, "fast_call_count",
FunctionTemplate::New(isolate, FastCApiObject::FastCallCount,
Local<Value>(), signature));
api_obj_ctor->PrototypeTemplate()->Set(
isolate, "slow_call_count",
FunctionTemplate::New(isolate, FastCApiObject::SlowCallCount,
Local<Value>(), signature));
api_obj_ctor->PrototypeTemplate()->Set(
isolate, "reset_counts",
FunctionTemplate::New(isolate, FastCApiObject::ResetCounts,
Local<Value>(), signature));
}
api_obj_ctor->InstanceTemplate()->SetInternalFieldCount(
FastCApiObject::kV8WrapperObjectIndex + 1);
return api_obj_ctor;
}
} // namespace v8
......@@ -2662,6 +2662,7 @@ Local<ObjectTemplate> Shell::CreateTestRunnerTemplate(Isolate* isolate) {
// installed on the global object can be hidden with the --omit-quit flag
// (e.g. on asan bots).
test_template->Set(isolate, "quit", FunctionTemplate::New(isolate, Quit));
return test_template;
}
......@@ -2717,6 +2718,11 @@ Local<ObjectTemplate> Shell::CreateD8Template(Isolate* isolate) {
test_template->Set(
isolate, "verifySourcePositions",
FunctionTemplate::New(isolate, TestVerifySourcePositions));
if (i::FLAG_turbo_fast_api_calls) {
test_template->Set(isolate, "fast_c_api",
Shell::CreateTestFastCApiTemplate(isolate));
}
d8_template->Set(isolate, "test", test_template);
}
return d8_template;
......
......@@ -632,6 +632,7 @@ class Shell : public i::AllStatic {
static Local<ObjectTemplate> CreatePerformanceTemplate(Isolate* isolate);
static Local<ObjectTemplate> CreateRealmTemplate(Isolate* isolate);
static Local<ObjectTemplate> CreateD8Template(Isolate* isolate);
static Local<FunctionTemplate> CreateTestFastCApiTemplate(Isolate* isolate);
static MaybeLocal<Context> CreateRealm(
const v8::FunctionCallbackInfo<v8::Value>& args, int index,
......
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file excercises basic fast API calls and enables fuzzing of this
// functionality.
// Flags: --turbo-fast-api-calls --allow-natives-syntax --opt
// --always-opt is disabled because we rely on particular feedback for
// optimizing to the fastest path.
// Flags: --no-always-opt
const fast_c_api = new d8.test.fast_c_api();
// ----------- add_all -----------
// `add_all` has the following signature:
// double add_all(bool /*should_fallback*/, int32_t, uint32_t,
// int64_t, uint64_t, float, double)
const max_safe_float = 2**24 - 1;
const add_all_result = -42 + 45 + Number.MIN_SAFE_INTEGER + Number.MAX_SAFE_INTEGER +
max_safe_float * 0.5 + Math.PI;
function add_all(should_fallback = false) {
return fast_c_api.add_all(should_fallback,
-42, 45, Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER,
max_safe_float * 0.5, Math.PI);
}
%PrepareFunctionForOptimization(add_all);
assertEquals(add_all_result, add_all());
%OptimizeFunctionOnNextCall(add_all);
if (fast_c_api.supports_fp_params) {
// Test that regular call hits the fast path.
fast_c_api.reset_counts();
assertEquals(add_all_result, add_all());
assertEquals(1, fast_c_api.fast_call_count());
assertEquals(0, fast_c_api.slow_call_count());
// Test fallback to slow path.
fast_c_api.reset_counts();
assertEquals(add_all_result, add_all(true));
assertEquals(1, fast_c_api.fast_call_count());
assertEquals(1, fast_c_api.slow_call_count());
// Test that no fallback hits the fast path again.
fast_c_api.reset_counts();
assertEquals(add_all_result, add_all());
assertEquals(1, fast_c_api.fast_call_count());
assertEquals(0, fast_c_api.slow_call_count());
} else {
// Test that calling with unsupported types hits the slow path.
fast_c_api.reset_counts();
assertEquals(add_all_result, add_all());
assertEquals(0, fast_c_api.fast_call_count());
assertEquals(1, fast_c_api.slow_call_count());
}
// ----------- Test add_all signature mismatche -----------
function add_all_mismatch() {
return fast_c_api.add_all(false /*should_fallback*/,
45, -42, Number.MAX_SAFE_INTEGER, max_safe_float * 0.5,
Number.MIN_SAFE_INTEGER, Math.PI);
}
%PrepareFunctionForOptimization(add_all_mismatch);
const add_all_mismatch_result = add_all_mismatch();
%OptimizeFunctionOnNextCall(add_all_mismatch);
fast_c_api.reset_counts();
assertEquals(add_all_mismatch_result, add_all_mismatch());
assertEquals(1, fast_c_api.slow_call_count());
assertEquals(0, fast_c_api.fast_call_count());
// If the function was ever optimized to the fast path, it should
// have been deoptimized due to the argument types mismatch. If it
// wasn't optimized due to lack of support for FP params, it will
// stay optimized.
if (fast_c_api.supports_fp_params) {
assertUnoptimized(add_all_mismatch);
}
// ----------- add_32bit_int -----------
// `add_32bit_int` has the following signature:
// int add_32bit_int(bool /*should_fallback*/, int32_t, uint32_t)
const add_32bit_int_result = -42 + 45;
function add_32bit_int(should_fallback = false) {
return fast_c_api.add_32bit_int(should_fallback, -42, 45);
}
%PrepareFunctionForOptimization(add_32bit_int);
assertEquals(add_32bit_int_result, add_32bit_int());
%OptimizeFunctionOnNextCall(add_32bit_int);
// Test that regular call hits the fast path.
fast_c_api.reset_counts();
assertEquals(add_32bit_int_result, add_32bit_int());
assertEquals(1, fast_c_api.fast_call_count());
assertEquals(0, fast_c_api.slow_call_count());
// Test fallback to slow path.
fast_c_api.reset_counts();
assertEquals(add_32bit_int_result, add_32bit_int(true));
assertEquals(1, fast_c_api.fast_call_count());
assertEquals(1, fast_c_api.slow_call_count());
// Test that no fallback hits the fast path again.
fast_c_api.reset_counts();
assertEquals(add_32bit_int_result, add_32bit_int());
assertEquals(1, fast_c_api.fast_call_count());
assertEquals(0, fast_c_api.slow_call_count());
// ----------- Test various signature mismatches -----------
function add_32bit_int_mismatch(arg0, arg1, arg2, arg3) {
return fast_c_api.add_32bit_int(arg0, arg1, arg2, arg3);
}
%PrepareFunctionForOptimization(add_32bit_int_mismatch);
assertEquals(add_32bit_int_result, add_32bit_int_mismatch(false, -42, 45));
%OptimizeFunctionOnNextCall(add_32bit_int_mismatch);
// Test that passing extra argument stays on the fast path.
fast_c_api.reset_counts();
assertEquals(add_32bit_int_result, add_32bit_int_mismatch(false, -42, 45, -42));
assertEquals(1, fast_c_api.fast_call_count());
// Test that passing wrong argument types stays on the fast path.
fast_c_api.reset_counts();
assertEquals(Math.round(-42 + 3.14), add_32bit_int_mismatch(false, -42, 3.14));
assertEquals(1, fast_c_api.fast_call_count());
// Test that passing too few argument falls down the slow path,
// because it's an argument type mismatch (undefined vs. int).
fast_c_api.reset_counts();
assertEquals(-42, add_32bit_int_mismatch(false, -42));
assertEquals(1, fast_c_api.slow_call_count());
assertEquals(0, fast_c_api.fast_call_count());
assertUnoptimized(add_32bit_int_mismatch);
// Test that the function can be optimized again.
%PrepareFunctionForOptimization(add_32bit_int_mismatch);
%OptimizeFunctionOnNextCall(add_32bit_int_mismatch);
fast_c_api.reset_counts();
assertEquals(add_32bit_int_result, add_32bit_int_mismatch(false, -42, 45));
assertEquals(1, fast_c_api.fast_call_count());
......@@ -350,6 +350,9 @@
'regexp-tier-up-multiple': [SKIP],
'regress/regress-996234': [SKIP],
# This test relies on TurboFan being enabled.
'compiler/fast-api-calls': [SKIP],
# These tests check that we can trace the compiler.
'tools/compiler-trace-flags': [SKIP],
......
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