[fastcall] Support external pointers in fast api calls

Bug: chromium:1052746

Change-Id: I3de37ca453b640b7f714e585848ccd068dd9ddbc
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3957815
Commit-Queue: Maya Lekova <mslekova@chromium.org>
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Reviewed-by: Samuel Groß <saelo@chromium.org>
Reviewed-by: Maya Lekova <mslekova@chromium.org>
Cr-Commit-Position: refs/heads/main@{#84597}
This commit is contained in:
Aapo Alasuutari 2022-12-01 15:30:10 +02:00 committed by V8 LUCI CQ
parent 90fe7dc9ce
commit 744570e583
18 changed files with 471 additions and 7 deletions

View File

@ -44,6 +44,7 @@ CodeWeavers, Inc. <*@codeweavers.com>
Alibaba, Inc. <*@alibaba-inc.com>
SiFive, Inc. <*@sifive.com>
Aapo Alasuutari <aapo.alasuutari@gmail.com>
Aaron Bieber <deftly@gmail.com>
Aaron O'Mullan <aaron.omullan@gmail.com>
Abdulla Kamar <abdulla.kamar@gmail.com>

View File

@ -247,6 +247,7 @@ class CTypeInfo {
kUint64,
kFloat32,
kFloat64,
kPointer,
kV8Value,
kSeqOneByteString,
kApiObject, // This will be deprecated once all users have
@ -435,6 +436,7 @@ struct AnyCType {
uint64_t uint64_value;
float float_value;
double double_value;
void* pointer_value;
Local<Object> object_value;
Local<Array> sequence_value;
const FastApiTypedArray<uint8_t>* uint8_ta_value;
@ -620,6 +622,7 @@ class CFunctionInfoImpl : public CFunctionInfo {
kReturnType == CTypeInfo::Type::kUint32 ||
kReturnType == CTypeInfo::Type::kFloat32 ||
kReturnType == CTypeInfo::Type::kFloat64 ||
kReturnType == CTypeInfo::Type::kPointer ||
kReturnType == CTypeInfo::Type::kAny,
"64-bit int, string and api object values are not currently "
"supported return types.");
@ -658,13 +661,14 @@ struct CTypeInfoTraits {};
#define PRIMITIVE_C_TYPES(V) \
V(bool, kBool) \
V(uint8_t, kUint8) \
V(int32_t, kInt32) \
V(uint32_t, kUint32) \
V(int64_t, kInt64) \
V(uint64_t, kUint64) \
V(float, kFloat32) \
V(double, kFloat64) \
V(uint8_t, kUint8)
V(void*, kPointer)
// Same as above, but includes deprecated types for compatibility.
#define ALL_C_TYPES(V) \
@ -698,13 +702,13 @@ PRIMITIVE_C_TYPES(DEFINE_TYPE_INFO_TRAITS)
};
#define TYPED_ARRAY_C_TYPES(V) \
V(uint8_t, kUint8) \
V(int32_t, kInt32) \
V(uint32_t, kUint32) \
V(int64_t, kInt64) \
V(uint64_t, kUint64) \
V(float, kFloat32) \
V(double, kFloat64) \
V(uint8_t, kUint8)
V(double, kFloat64)
TYPED_ARRAY_C_TYPES(SPECIALIZE_GET_TYPE_INFO_HELPER_FOR_TA)

View File

@ -402,6 +402,9 @@ constexpr uint64_t kAllExternalPointerTypeTags[] = {
V(kForeignForeignAddressTag, TAG(10)) \
V(kNativeContextMicrotaskQueueTag, TAG(11)) \
V(kEmbedderDataSlotPayloadTag, TAG(12)) \
/* This tag essentially stands for a `void*` pointer in the V8 API, and */ \
/* it is the Embedder's responsibility to ensure type safety (against */ \
/* substitution) and lifetime validity of these objects. */ \
V(kExternalObjectValueTag, TAG(13)) \
V(kCallHandlerInfoCallbackTag, TAG(14)) \
V(kAccessorInfoGetterTag, TAG(15)) \

View File

@ -353,6 +353,19 @@ FUNCTION_REFERENCE(delete_handle_scope_extensions,
FUNCTION_REFERENCE(ephemeron_key_write_barrier_function,
Heap::EphemeronKeyWriteBarrierFromCode)
ExternalPointerHandle AllocateAndInitializeExternalPointerTableEntry(
Isolate* isolate, Address pointer) {
#ifdef V8_ENABLE_SANDBOX
return isolate->external_pointer_table().AllocateAndInitializeEntry(
isolate, pointer, kExternalObjectValueTag);
#else
return 0;
#endif // V8_ENABLE_SANDBOX
}
FUNCTION_REFERENCE(allocate_and_initialize_external_pointer_table_entry,
AllocateAndInitializeExternalPointerTableEntry)
FUNCTION_REFERENCE(get_date_field_function, JSDate::GetField)
ExternalReference ExternalReference::date_cache_stamp(Isolate* isolate) {

View File

@ -119,6 +119,8 @@ class StatsCounter;
V(address_of_shared_string_table_flag, "v8_flags.shared_string_table") \
V(address_of_the_hole_nan, "the_hole_nan") \
V(address_of_uint32_bias, "uint32_bias") \
V(allocate_and_initialize_external_pointer_table_entry, \
"AllocateAndInitializeExternalPointerTableEntry") \
V(baseline_pc_for_bytecode_offset, "BaselinePCForBytecodeOffset") \
V(baseline_pc_for_next_executed_bytecode, \
"BaselinePCForNextExecutedBytecode") \

View File

@ -314,6 +314,8 @@ class MachineType {
return MachineType::Float32();
case CTypeInfo::Type::kFloat64:
return MachineType::Float64();
case CTypeInfo::Type::kPointer:
return MachineType::Pointer();
case CTypeInfo::Type::kV8Value:
case CTypeInfo::Type::kSeqOneByteString:
case CTypeInfo::Type::kApiObject:

View File

@ -155,6 +155,35 @@ FieldAccess AccessBuilder::ForJSCollectionIteratorIndex() {
return access;
}
// static
FieldAccess AccessBuilder::ForJSExternalObjectValue() {
FieldAccess access = {
kTaggedBase,
JSExternalObject::kValueOffset,
MaybeHandle<Name>(),
MaybeHandle<Map>(),
Type::ExternalPointer(),
MachineType::Pointer(),
kNoWriteBarrier,
"JSExternalObjectValue",
ConstFieldInfo::None(),
false,
kExternalObjectValueTag,
};
return access;
}
#ifdef V8_ENABLE_SANDBOX
// static
FieldAccess AccessBuilder::ForJSExternalObjectPointerHandle() {
FieldAccess access = {
kTaggedBase, JSExternalObject::kValueOffset, MaybeHandle<Name>(),
MaybeHandle<Map>(), TypeCache::Get()->kUint32, MachineType::Uint32(),
kNoWriteBarrier, "JSExternalObjectPointerHandle"};
return access;
}
#endif
// static
FieldAccess AccessBuilder::ForJSFunctionPrototypeOrInitialMap() {
FieldAccess access = {

View File

@ -74,6 +74,15 @@ class V8_EXPORT_PRIVATE AccessBuilder final
// Provides access to JSCollectionIterator::index() field.
static FieldAccess ForJSCollectionIteratorIndex();
// Provides access to an ExternalPointer through the JSExternalObject::value()
// field.
static FieldAccess ForJSExternalObjectValue();
#ifdef V8_ENABLE_SANDBOX
// Provides access to JSExternalObject::value() field.
static FieldAccess ForJSExternalObjectPointerHandle();
#endif
// Provides access to JSFunction::prototype_or_initial_map() field.
static FieldAccess ForJSFunctionPrototypeOrInitialMap();

View File

@ -25,6 +25,7 @@
#include "src/compiler/schedule.h"
#include "src/heap/factory-inl.h"
#include "src/objects/heap-number.h"
#include "src/objects/js-objects.h"
#include "src/objects/oddball.h"
#include "src/objects/ordered-hash-table.h"
#include "src/objects/turbofan-types.h"
@ -319,6 +320,8 @@ class EffectControlLinearizer {
// Pass {bitfield} = {digit} = nullptr to construct the canoncial 0n BigInt.
Node* BuildAllocateBigInt(Node* bitfield, Node* digit);
Node* BuildAllocateJSExternalObject(Node* pointer);
void TransitionElementsTo(Node* node, Node* array, ElementsKind from,
ElementsKind to);
@ -5491,6 +5494,38 @@ Node* EffectControlLinearizer::AdaptFastCallArgument(
case CTypeInfo::Type::kFloat32: {
return __ TruncateFloat64ToFloat32(node);
}
case CTypeInfo::Type::kPointer: {
// Check that the value is a HeapObject.
Node* const value_is_smi = ObjectIsSmi(node);
__ GotoIf(value_is_smi, if_error);
auto if_null = __ MakeDeferredLabel();
auto done = __ MakeLabel(MachineType::PointerRepresentation());
// Check if the value is null
__ GotoIf(__ TaggedEqual(node, __ NullConstant()), &if_null);
{
// Check that the value is a JSExternalObject.
Node* const is_external =
__ TaggedEqual(__ LoadField(AccessBuilder::ForMap(), node),
__ ExternalObjectMapConstant());
__ GotoIfNot(is_external, if_error);
Node* external_pointer =
__ LoadField(AccessBuilder::ForJSExternalObjectValue(), node);
__ Goto(&done, external_pointer);
}
// Value is null, signifying a null pointer.
__ Bind(&if_null);
{ __ Goto(&done, __ IntPtrConstant(0)); }
__ Bind(&done);
return done.PhiAt(0);
}
case CTypeInfo::Type::kSeqOneByteString: {
// Check that the value is a HeapObject.
Node* value_is_smi = ObjectIsSmi(node);
@ -5740,6 +5775,8 @@ Node* EffectControlLinearizer::LowerFastApiCall(Node* node) {
case CTypeInfo::Type::kFloat64:
return ChangeFloat64ToTagged(
c_call_result, CheckForMinusZeroMode::kCheckForMinusZero);
case CTypeInfo::Type::kPointer:
return BuildAllocateJSExternalObject(c_call_result);
case CTypeInfo::Type::kSeqOneByteString:
case CTypeInfo::Type::kV8Value:
case CTypeInfo::Type::kApiObject:
@ -7155,6 +7192,58 @@ Node* EffectControlLinearizer::BuildAllocateBigInt(Node* bitfield,
return result;
}
Node* EffectControlLinearizer::BuildAllocateJSExternalObject(Node* pointer) {
auto if_null = __ MakeDeferredLabel();
auto done = __ MakeLabel(MachineRepresentation::kTagged);
// Check if the pointer is a null pointer
__ GotoIf(__ WordEqual(pointer, __ IntPtrConstant(0)), &if_null);
{
Node* external =
__ Allocate(AllocationType::kYoung,
__ IntPtrConstant(JSExternalObject::kHeaderSize));
__ StoreField(AccessBuilder::ForMap(), external,
__ ExternalObjectMapConstant());
Node* empty_fixed_array = __ HeapConstant(factory()->empty_fixed_array());
__ StoreField(AccessBuilder::ForJSObjectPropertiesOrHash(), external,
empty_fixed_array);
__ StoreField(AccessBuilder::ForJSObjectElements(), external,
empty_fixed_array);
#ifdef V8_ENABLE_SANDBOX
Node* const isolate_ptr =
__ ExternalConstant(ExternalReference::isolate_address(isolate()));
MachineSignature::Builder builder(graph()->zone(), 1, 2);
builder.AddReturn(MachineType::Uint32());
builder.AddParam(MachineType::Pointer());
builder.AddParam(MachineType::Pointer());
Node* allocate_and_initialize_external_pointer_table_entry =
__ ExternalConstant(
ExternalReference::
allocate_and_initialize_external_pointer_table_entry());
auto call_descriptor =
Linkage::GetSimplifiedCDescriptor(graph()->zone(), builder.Build());
Node* handle = __ Call(common()->Call(call_descriptor),
allocate_and_initialize_external_pointer_table_entry,
isolate_ptr, pointer);
__ StoreField(AccessBuilder::ForJSExternalObjectPointerHandle(), external,
handle);
#else
__ StoreField(AccessBuilder::ForJSExternalObjectValue(), external, pointer);
#endif // V8_ENABLE_SANDBOX
__ Goto(&done, external);
}
// Pointer is null, convert to a null
__ Bind(&if_null);
{ __ Goto(&done, __ NullConstant()); }
__ Bind(&done);
return done.PhiAt(0);
}
#undef __
void LinearizeEffectControl(JSGraph* graph, Schedule* schedule, Zone* temp_zone,

View File

@ -31,6 +31,7 @@ ElementsKind GetTypedArrayElementsKind(CTypeInfo::Type type) {
case CTypeInfo::Type::kVoid:
case CTypeInfo::Type::kSeqOneByteString:
case CTypeInfo::Type::kBool:
case CTypeInfo::Type::kPointer:
case CTypeInfo::Type::kV8Value:
case CTypeInfo::Type::kApiObject:
case CTypeInfo::Type::kAny:

View File

@ -142,6 +142,7 @@ class Reducer;
V(BigIntMap, Map) \
V(BooleanMap, Map) \
V(EmptyString, String) \
V(ExternalObjectMap, Map) \
V(False, Boolean) \
V(FixedArrayMap, Map) \
V(FixedDoubleArrayMap, Map) \

View File

@ -187,6 +187,9 @@ DEFINE_GETTER(
graph()->zone()->New<ZoneVector<MachineType>>(0, graph()->zone()),
SparseInputMask(SparseInputMask::kEndMarker << 1))))
DEFINE_GETTER(ExternalObjectMapConstant,
HeapConstant(factory()->external_map()))
#undef DEFINE_GETTER
#undef GET_CACHED_FIELD

View File

@ -107,7 +107,8 @@ class V8_EXPORT_PRIVATE JSGraph : public MachineGraph {
V(MinusOneConstant) \
V(NaNConstant) \
V(EmptyStateValues) \
V(SingleDeadTypedStateValues)
V(SingleDeadTypedStateValues) \
V(ExternalObjectMapConstant)
// Cached global node accessor methods.
#define DECLARE_GETTER(name) Node* name();

View File

@ -5,6 +5,7 @@
#include "src/compiler/memory-lowering.h"
#include "src/codegen/interface-descriptors-inl.h"
#include "src/common/globals.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/linkage.h"
#include "src/compiler/node-matchers.h"
@ -595,8 +596,9 @@ Reduction MemoryLowering::ReduceStoreField(Node* node,
AllocationState const* state) {
DCHECK_EQ(IrOpcode::kStoreField, node->opcode());
FieldAccess const& access = FieldAccessOf(node->op());
// External pointer must never be stored by optimized code.
DCHECK(!access.type.Is(Type::ExternalPointer()));
// External pointer must never be stored by optimized code when sandbox is
// turned on
DCHECK(!access.type.Is(Type::ExternalPointer()) || !V8_ENABLE_SANDBOX_BOOL);
// SandboxedPointers are not currently stored by optimized code.
DCHECK(!access.type.Is(Type::SandboxedPointer()));
// Bounded size fields are not currently stored by optimized code.

View File

@ -1960,6 +1960,7 @@ class RepresentationSelector {
case CTypeInfo::Type::kFloat32:
case CTypeInfo::Type::kFloat64:
return UseInfo::CheckedNumberAsFloat64(kDistinguishZeros, feedback);
case CTypeInfo::Type::kPointer:
case CTypeInfo::Type::kV8Value:
case CTypeInfo::Type::kSeqOneByteString:
case CTypeInfo::Type::kApiObject:

View File

@ -941,7 +941,8 @@ OpIndex GraphBuilder::Process(
OpIndex value = Map(node->InputAt(1));
FieldAccess const& access = FieldAccessOf(node->op());
// External pointer must never be stored by optimized code.
DCHECK(!access.type.Is(Type::ExternalPointer()));
DCHECK(!access.type.Is(Type::ExternalPointer()) ||
!V8_ENABLE_SANDBOX_BOOL);
// SandboxedPointers are not currently stored by optimized code.
DCHECK(!access.type.Is(Type::SandboxedPointer()));

View File

@ -1029,6 +1029,132 @@ class FastCApiObject {
args.GetIsolate()->ThrowError("should be unreachable from wasm");
}
static void AssertIsExternal(const FunctionCallbackInfo<Value>& args) {
FastCApiObject* self = UnwrapObject(args.This());
CHECK_SELF_OR_THROW();
Local<Value> value = args[0];
if (!value->IsExternal()) {
args.GetIsolate()->ThrowError("Did not get an external.");
}
}
static void* GetPointerFastCallback(Local<Object> receiver,
FastApiCallbackOptions& options) {
FastCApiObject* self = UnwrapObject(receiver);
CHECK_SELF_OR_FALLBACK(nullptr);
self->fast_call_count_++;
return static_cast<void*>(self);
}
static void GetPointerSlowCallback(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
FastCApiObject* self = UnwrapObject(args.This());
CHECK_SELF_OR_THROW();
self->slow_call_count_++;
args.GetReturnValue().Set(External::New(isolate, static_cast<void*>(self)));
}
static void* GetNullPointerFastCallback(Local<Object> receiver,
FastApiCallbackOptions& options) {
FastCApiObject* self = UnwrapObject(receiver);
CHECK_SELF_OR_FALLBACK(nullptr);
self->fast_call_count_++;
return nullptr;
}
static void GetNullPointerSlowCallback(
const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
FastCApiObject* self = UnwrapObject(args.This());
CHECK_SELF_OR_THROW();
self->slow_call_count_++;
args.GetReturnValue().Set(v8::Null(isolate));
}
static void* PassPointerFastCallback(Local<Object> receiver, void* pointer,
FastApiCallbackOptions& options) {
FastCApiObject* self = UnwrapObject(receiver);
CHECK_SELF_OR_FALLBACK(nullptr);
self->fast_call_count_++;
return pointer;
}
static void PassPointerSlowCallback(const FunctionCallbackInfo<Value>& args) {
FastCApiObject* self = UnwrapObject(args.This());
CHECK_SELF_OR_THROW();
self->slow_call_count_++;
if (args.Length() != 1) {
args.GetIsolate()->ThrowError(
"Invalid number of arguments, expected one.");
return;
}
Local<Value> maybe_external = args[0].As<Value>();
if (maybe_external->IsNull()) {
args.GetReturnValue().Set(maybe_external);
return;
}
if (!maybe_external->IsExternal()) {
args.GetIsolate()->ThrowError("Did not get an external.");
return;
}
Local<External> external = args[0].As<External>();
args.GetReturnValue().Set(external);
}
static bool ComparePointersFastCallback(Local<Object> receiver,
void* pointer_a, void* pointer_b,
FastApiCallbackOptions& options) {
FastCApiObject* self = UnwrapObject(receiver);
CHECK_SELF_OR_FALLBACK(false);
self->fast_call_count_++;
return pointer_a == pointer_b;
}
static void ComparePointersSlowCallback(
const FunctionCallbackInfo<Value>& args) {
FastCApiObject* self = UnwrapObject(args.This());
CHECK_SELF_OR_THROW();
self->slow_call_count_++;
if (args.Length() != 2) {
args.GetIsolate()->ThrowError(
"Invalid number of arguments, expected two.");
return;
}
Local<Value> value_a = args[0];
Local<Value> value_b = args[1];
void* pointer_a;
if (value_a->IsNull()) {
pointer_a = nullptr;
} else {
pointer_a = value_a.As<External>()->Value();
}
void* pointer_b;
if (value_b->IsNull()) {
pointer_b = nullptr;
} else {
pointer_b = value_b.As<External>()->Value();
}
args.GetReturnValue().Set(pointer_a == pointer_b);
}
static void FastCallCount(const FunctionCallbackInfo<Value>& args) {
FastCApiObject* self = UnwrapObject(args.This());
CHECK_SELF_OR_THROW();
@ -1464,6 +1590,46 @@ Local<FunctionTemplate> Shell::CreateTestFastCApiTemplate(Isolate* isolate) {
Local<Signature>(), 1, ConstructorBehavior::kThrow,
SideEffectType::kHasSideEffect, &test_wasm_memory_c_func));
api_obj_ctor->PrototypeTemplate()->Set(
isolate, "assert_is_external",
FunctionTemplate::New(isolate, FastCApiObject::AssertIsExternal,
Local<Value>(), signature, 1,
ConstructorBehavior::kThrow,
SideEffectType::kHasSideEffect, nullptr));
CFunction get_pointer_c_func =
CFunction::Make(FastCApiObject::GetPointerFastCallback);
api_obj_ctor->PrototypeTemplate()->Set(
isolate, "get_pointer",
FunctionTemplate::New(
isolate, FastCApiObject::GetPointerSlowCallback, Local<Value>(),
signature, 1, ConstructorBehavior::kThrow,
SideEffectType::kHasSideEffect, &get_pointer_c_func));
CFunction get_null_pointer_c_func =
CFunction::Make(FastCApiObject::GetNullPointerFastCallback);
api_obj_ctor->PrototypeTemplate()->Set(
isolate, "get_null_pointer",
FunctionTemplate::New(
isolate, FastCApiObject::GetNullPointerSlowCallback, Local<Value>(),
signature, 1, ConstructorBehavior::kThrow,
SideEffectType::kHasSideEffect, &get_null_pointer_c_func));
CFunction pass_pointer_c_func =
CFunction::Make(FastCApiObject::PassPointerFastCallback);
api_obj_ctor->PrototypeTemplate()->Set(
isolate, "pass_pointer",
FunctionTemplate::New(
isolate, FastCApiObject::PassPointerSlowCallback, Local<Value>(),
signature, 1, ConstructorBehavior::kThrow,
SideEffectType::kHasSideEffect, &pass_pointer_c_func));
CFunction compare_pointers_c_func =
CFunction::Make(FastCApiObject::ComparePointersFastCallback);
api_obj_ctor->PrototypeTemplate()->Set(
isolate, "compare_pointers",
FunctionTemplate::New(
isolate, FastCApiObject::ComparePointersSlowCallback,
Local<Value>(), signature, 1, ConstructorBehavior::kThrow,
SideEffectType::kHasSideEffect, &compare_pointers_c_func));
api_obj_ctor->PrototypeTemplate()->Set(
isolate, "fast_call_count",
FunctionTemplate::New(

View File

@ -0,0 +1,136 @@
// Copyright 2022 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 --expose-fast-api --allow-natives-syntax --turbofan
// --always-turbofan is disabled because we rely on particular feedback for
// optimizing to the fastest path.
// Flags: --no-always-turbofan
// The test relies on optimizing/deoptimizing at predictable moments, so
// it's not suitable for deoptimization fuzzing.
// Flags: --deopt-every-n-times=0
assertThrows(() => d8.test.FastCAPI());
const fast_c_api = new d8.test.FastCAPI();
// ---------- Test external pointer passing -----------
function reset_counts() {
return fast_c_api.reset_counts();
}
function fast_call_count() {
return fast_c_api.fast_call_count();
}
function slow_call_count() {
return fast_c_api.slow_call_count();
}
function assertIsExternal(pointer) {
return fast_c_api.assert_is_external(pointer);
}
function get_pointer_a() {
return fast_c_api.get_pointer();
}
function get_pointer_b() {
return fast_c_api.get_null_pointer();
}
function pass_pointer(pointer) {
return fast_c_api.pass_pointer(pointer);
}
function compare_pointers(pointer_a, pointer_b) {
return fast_c_api.compare_pointers(pointer_a, pointer_b);
}
%PrepareFunctionForOptimization(get_pointer_a);
reset_counts();
const external_a_slow = get_pointer_a();
const external_a_slow_clone = get_pointer_a();
assertEquals(slow_call_count(), 2);
assertEquals(fast_call_count(), 0);
assertIsExternal(external_a_slow);
assertIsExternal(external_a_slow_clone);
// Slow call that returns the same pointer from a new `External::New()`
// will still create a new / different object.
// Note that we cannot use `assertEquals(external_a_slow, external_a_slow_clone)`
// as it's a deep equality comparison and will return true for all empty object comparsions.
assertFalse(external_a_slow === external_a_slow_clone);
%PrepareFunctionForOptimization(pass_pointer);
reset_counts();
const external_a_slow_passed = pass_pointer(external_a_slow);
// If slow call returns the same External object, then object identity is
// preserved.
assertEquals(slow_call_count(), 1);
assertEquals(fast_call_count(), 0);
assertTrue(external_a_slow_passed === external_a_slow, true);
%OptimizeFunctionOnNextCall(pass_pointer);
const external_a_fast_passed = pass_pointer(external_a_slow);
assertEquals(slow_call_count(), 1);
assertEquals(fast_call_count(), 1);
assertIsExternal(external_a_slow);
assertIsExternal(external_a_fast_passed);
// Fast call always creates a new External object, as they cannot
// return the same External object given that they do not see it.
assertFalse(external_a_fast_passed === external_a_slow);
// An object that looks like an External is still not an External.
const emptyObject = Object.create(null);
// An object that internally carries a pointer is still not an External.
const alsoInternallyPointer = new Uint8Array();
assertThrows(() => pass_pointer(emptyObject));
assertThrows(() => pass_pointer(alsoInternallyPointer));
// Show off deep equality comparsions between various External objects and
// the empty object to show that all Externals work properly as objects.
assertEquals(external_a_slow, external_a_fast_passed);
assertEquals(external_a_fast_passed, emptyObject);
%OptimizeFunctionOnNextCall(get_pointer_a);
reset_counts();
const external_a_fast = get_pointer_a();
assertEquals(slow_call_count(), 0);
assertEquals(fast_call_count(), 1);
assertIsExternal(external_a_fast);
assertFalse(external_a_fast === external_a_slow);
%PrepareFunctionForOptimization(get_pointer_b);
fast_c_api.reset_counts();
const external_b_slow = get_pointer_b();
assertEquals(slow_call_count(), 1);
assertEquals(fast_call_count(), 0);
assertEquals(external_b_slow, null);
%OptimizeFunctionOnNextCall(get_pointer_b);
const external_b_fast = get_pointer_b();
assertEquals(slow_call_count(), 1);
assertEquals(fast_call_count(), 1);
assertEquals(external_b_fast, null);
const external_b_fast_passed = pass_pointer(external_b_slow);
assertEquals(external_b_fast_passed, null);
assertTrue(external_b_fast_passed === external_b_slow, true);
%PrepareFunctionForOptimization(compare_pointers);
assertUnoptimized(compare_pointers);
reset_counts();
assertFalse(compare_pointers(external_a_slow, external_b_slow));
assertEquals(slow_call_count(), 1);
assertEquals(fast_call_count(), 0);
%OptimizeFunctionOnNextCall(compare_pointers);
assertFalse(compare_pointers(external_a_slow, external_b_slow));
assertEquals(slow_call_count(), 1);
assertEquals(fast_call_count(), 1);
assertTrue(compare_pointers(external_a_slow, external_a_slow), true);
assertTrue(compare_pointers(external_a_slow, external_a_fast), true);
assertTrue(compare_pointers(external_b_slow, external_b_slow), true);
assertTrue(compare_pointers(external_b_slow, external_b_fast), true);
assertTrue(compare_pointers(external_b_slow, external_b_fast_passed), true);