Skip to content

Instantly share code, notes, and snippets.

@ialex32x
Created March 27, 2025 00:47
Show Gist options
  • Save ialex32x/a588b6800f158c95e301097d278a9c6a to your computer and use it in GitHub Desktop.
Save ialex32x/a588b6800f158c95e301097d278a9c6a to your computer and use it in GitHub Desktop.
v8-test
#include <atomic>
#include <iostream>
#include <cassert>
#include <map>
#include <unordered_map>
#include <libplatform/libplatform.h>
#include <cppgc/allocation.h>
#include <cppgc/garbage-collected.h>
#include <cppgc/heap.h>
#include <cppgc/default-platform.h>
#include <v8-initialization.h>
#include <v8-cppgc.h>
#include <v8.h>
#define JSB_USE_CPP_HEAP 1
#define JSB_USE_CPPGC 1
#define JSB_USE_MIX 0
#define jsb_noinline __attribute__((noinline))
#define jsb_ensure(Condition) (!!(Condition))
#define jsb_ensuref(Condition, Format, ...) (!!(Condition))
template<typename T = v8::Object>
struct JSObject
{
int hash;
v8::Global<T> f;
JSObject() : hash(0), f() {}
JSObject(v8::Isolate* isolate, const v8::Local<T>& other)
{
hash = other->GetIdentityHash();
f.Reset(isolate, other);
f.SetWeak();
}
JSObject(int h, v8::Global<v8::Object>&& other): hash(h), f(std::move(other)) {}
JSObject(JSObject&& other) noexcept : hash(other.hash), f(std::move(other.f)) {}
JSObject& operator=(JSObject&& other) noexcept { hash = other.hash; f = std::move(other.f); return *this; }
~JSObject() { f.Reset(); }
struct hasher
{
size_t operator()(const JSObject& obj) const noexcept
{
//return obj->GetIdentityHash();
return obj.hash;
}
};
struct equaler
{
bool operator()(const JSObject& lhs, const JSObject& rhs) const
{
return lhs.f == rhs.f;
}
};
};
#define check(COND) if (!(COND)) { printf(#COND " assertion failed\n"); } else (void)0
static void check_type(const char* name, const v8::Local<v8::Value> value)
{
printf("[%s] IsUint32 %d\n", name, value->IsUint32());
printf("[%s] IsInt32 %d\n", name, value->IsInt32());
printf("[%s] IsNumber %d\n", name, value->IsNumber());
printf("[%s] IsBigInt %d\n", name, value->IsBigInt());
printf("[%s] IsObject %d\n", name, value->IsObject());
printf("[%s] IsArray %d\n", name, value->IsArray());
printf("\n");
}
static void _output(const v8::FunctionCallbackInfo<v8::Value>& info)
{
v8::Isolate* isolate = info.GetIsolate();
printf("[OUTPUT]");
for (int i = 0; i < info.Length(); ++i)
{
v8::String::Utf8Value value(isolate, info[i]);
printf(" %s", std::string(*value, value.length()).c_str());
}
printf("\n");
fflush(stdout);
}
static void _check(const v8::FunctionCallbackInfo<v8::Value>& info)
{
v8::String::Utf8Value value(info.GetIsolate(), info[0]);
char str[256];
sprintf_s(str, "JS (%s)", std::string(*value, value.length()).c_str());
check_type(str, info[0]);
}
static void eval(v8::Local<v8::Context> context, const char* code)
{
v8::TryCatch try_catch(context->GetIsolate());
{
v8::HandleScope handle_scope(context->GetIsolate());
v8::Local<v8::String> source = v8::String::NewFromUtf8(context->GetIsolate(), code).ToLocalChecked();
v8::Local<v8::Script> script = v8::Script::Compile(context, source).ToLocalChecked();
script->Run(context).ToLocalChecked();
if (try_catch.HasCaught())
{
v8::String::Utf8Value str(context->GetIsolate(), try_catch.Message().As<v8::Value>());
printf("eval failed: %s\n", *str);
}
}
}
enum { kWrappableTypeIndex, kWrappableInstanceIndex, kWrappableFieldCount };
static const uint16_t kEmbedderID = 0x1;
class FNativeObject final
: public cppgc::GarbageCollected<FNativeObject>
, public cppgc::NameProvider
#if JSB_USE_MIX
, public cppgc::GarbageCollectedMixin
#endif
{
public:
enum InternalFields { kEmbedderType = 0, kSlot, kInternalFieldCount };
float xx = 0;
static void New(const v8::FunctionCallbackInfo<v8::Value>& args)
{
v8::Isolate* isolate = args.GetIsolate();
v8::Local<v8::Object> js_object = args.This();
FNativeObject* gc_object = cppgc::MakeGarbageCollected<FNativeObject>(isolate->GetCppHeap()->GetAllocationHandle());
js_object->SetAlignedPointerInInternalField(kWrappableTypeIndex, (void*) &kEmbedderID);
js_object->SetAlignedPointerInInternalField(kWrappableInstanceIndex, gc_object);
args.GetReturnValue().Set(js_object);
}
static v8::Local<v8::Function> GetConstructor(v8::Local<v8::Context> context)
{
const auto ft = v8::FunctionTemplate::New(context->GetIsolate(), New);
const auto ot = ft->InstanceTemplate();
ot->SetInternalFieldCount(kWrappableFieldCount);
return ft->GetFunction(context).ToLocalChecked();
}
static void nop() {}
FNativeObject()
{
printf("[C++] FNativeObject constructor +++ %p\n", this);
}
~FNativeObject()
{
printf("[C++] FNativeObject destructor ---%p\n", this);
}
#if JSB_USE_MIX
jsb_noinline void FinalizeGarbageCollectedObject()
{
printf("[C++] FNativeObject FinalizeGarbageCollectedObject%p\n", this);
delete this;
}
#endif
#if JSB_USE_MIX
virtual void Trace(cppgc::Visitor* visitor) const override
#else
void Trace(cppgc::Visitor* visitor) const
#endif
{
//printf("[C++] FNativeObject trace ***%p\n", this);
}
virtual const char* GetHumanReadableName() const override { return "NativeObject"; }
};
static_assert(cppgc::IsGarbageCollectedTypeV<FNativeObject>);
static_assert(cppgc::IsCompleteV<FNativeObject>);
//static std::vector<v8::Global<v8::Object>> persistent_objects;
static std::atomic_int64_t allocated_num1 = 0;
static void finalizer1(const v8::WeakCallbackInfo<void>& p)
{
//size_t index = (size_t) p.GetParameter();
//persistent_objects[index].Reset();
printf("~C1\n");
}
static void deleter1(void* data, size_t len, void* payload)
{
//size_t index = (size_t) p.GetParameter();
//persistent_objects[index].Reset();
printf("~C1\n");
--allocated_num1;
}
static void constructor1(const v8::FunctionCallbackInfo<v8::Value>& info)
{
static struct { int nop; } dummy;
++allocated_num1;
v8::Isolate* isolate = info.GetIsolate();
printf("C1\n");
info.This()->Set(isolate->GetCurrentContext(), 0,
v8::ArrayBuffer::New(isolate,
v8::ArrayBuffer::NewBackingStore(&dummy, sizeof(dummy), deleter1, nullptr)));
//v8::Global<v8::Object> handle(info.GetIsolate(), info.This());
//const size_t size = persistent_objects.size();
//handle.SetWeak((void*) size, finalizer1, v8::WeakCallbackType::kParameter);
//persistent_objects.emplace_back(std::move(handle));
//printf("c1 check1: %d\n", info.NewTarget() == GetGlobals().SavedTemplateOfC1.Get(info.GetIsolate())->GetFunction(info.GetIsolate()->GetCurrentContext()).ToLocalChecked());
//printf("c1 check2: %d\n", info.NewTarget() == GetGlobals().SavedConstructorOfC1.Get(info.GetIsolate()));
}
static void constructor2(const v8::FunctionCallbackInfo<v8::Value>& info)
{
printf("c2\n");
}
static void constructor3(const v8::FunctionCallbackInfo<v8::Value>& info)
{
printf("c3\n");
}
int main()
{
if (jsb_ensure(1 != 1))
{
std::cout << "jsb_ensure" << std::endl;
}
// global init
#if JSB_USE_CPPGC
auto cppgc_platform = std::make_shared<cppgc::DefaultPlatform>();
#else
std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
if (!platform.get())
{
return -1;
}
#endif
std::string args = "--expose-gc";
v8::V8::SetFlagsFromString(args.c_str(), args.size());
#if JSB_USE_CPPGC
v8::V8::InitializePlatform(cppgc_platform->GetV8Platform());
cppgc::InitializeProcess(cppgc_platform->GetPageAllocator());
#else
v8::V8::InitializePlatform(platform.get());
#endif
//std::unique_ptr<cppgc::Heap> heap = cppgc::Heap::Create(cppgc_platform);
v8::V8::Initialize();
#ifdef V8_COMPRESS_POINTERS
std::cout << "V8_COMPRESS_POINTERS" << std::endl;
#endif
// runtime init
{
v8::Isolate::CreateParams create_params;
#if JSB_USE_CPP_HEAP
v8::CppHeapCreateParams heap_create_params = v8::CppHeapCreateParams({},
v8::WrapperDescriptor(kWrappableTypeIndex, kWrappableInstanceIndex, kEmbedderID));
std::unique_ptr<v8::CppHeap> cpp_heap = v8::CppHeap::Create(cppgc_platform->GetV8Platform(), heap_create_params);
create_params.cpp_heap = cpp_heap.get();
#endif
v8::ArrayBuffer::Allocator* array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
create_params.array_buffer_allocator = array_buffer_allocator;
v8::Isolate* isolate = v8::Isolate::New(create_params);
//isolate->AttachCppHeap(cpp_heap.get());
{
v8::Isolate::Scope isolate_scope(isolate);
v8::HandleScope handle_scope1(isolate);
v8::Local<v8::Context> context = v8::Context::New(isolate);
// register essential functions
{
v8::HandleScope handle_scope2(isolate);
v8::Context::Scope context_scope(context);
context->Global()->Set(context, v8::String::NewFromUtf8Literal(isolate, "print"), v8::Function::New(context, _output).ToLocalChecked()).Check();
context->Global()->Set(context, v8::String::NewFromUtf8Literal(isolate, "check"), v8::Function::New(context, _check).ToLocalChecked()).Check();
}
{
// register native classes for testing
{
v8::HandleScope handle_scope3(isolate);
v8::Context::Scope context_scope(context);
context->Global()
->Set(context,
v8::String::NewFromUtf8Literal(isolate, "C1"),
v8::FunctionTemplate::New(isolate, constructor1)->GetFunction(context).ToLocalChecked())
.Check();
#if JSB_USE_CPP_HEAP
context->Global()
->Set(context,
v8::String::NewFromUtf8(isolate, "FNativeObject").ToLocalChecked(),
FNativeObject::GetConstructor(context))
.Check();
#else
FNativeObject::nop();
#endif
}
}
// check types
{
v8::HandleScope handle_scope8(isolate);
v8::Context::Scope context_scope(context);
check_type("Array", v8::Array::New(isolate));
check_type("BigInt", v8::BigInt::New(isolate, 1));
check_type("Uint32", v8::Uint32::NewFromUnsigned(isolate, -1));
check_type("Int32", v8::Int32::New(isolate, -1));
check_type("Number(9007199254740991)", v8::Number::New(isolate, 9007199254740991));
check_type("Number(123.456)", v8::Number::New(isolate, 123.456));
printf("%lld\n", (int64_t)v8::Number::New(isolate, 9007199254740991)->NumberValue(context).ToChecked());
printf("%d\n", v8::Number::New(isolate, 123.456).As<v8::Int32>()->Value());
printf("> before eval\n");
eval(context, R"""(
check((-1) >>> 0);
check((-1));
check(1);
let buf = [];
globalThis.buf = buf;
for (let i=0;i<5;i++) { buf.push(new C1()); }
//cc = undefined;
globalThis.nat_objs = [];
globalThis.nat_objs.push(new FNativeObject());
globalThis.nat_objs.push(new FNativeObject());
globalThis.nat_objs.push(new FNativeObject());
)""");
printf("> after eval\n");
printf("> low mem notification\n");
isolate->LowMemoryNotification();
}
// wrapper test
{
v8::HandleScope handle_scope8(isolate);
v8::Context::Scope context_scope(context);
printf("> before eval\n");
eval(context, R"""(
globalThis.nat_objs = undefined;
globalThis.nat_objs = new FNativeObject();
)""");
printf("> after eval\n");
printf("> low mem notification\n");
isolate->LowMemoryNotification();
}
}
printf("> low mem notification\n");
isolate->LowMemoryNotification();
printf("> get to dispose\n");
isolate->Dispose();
#if JSB_USE_CPP_HEAP
//isolate->DetachCppHeap();
//cpp_heap->Terminate();
#endif
printf("> delete array buffer allocator\n");
delete array_buffer_allocator;
#if JSB_USE_CPPGC
printf("> shutdown cppgc\n");
cppgc::ShutdownProcess();
#endif
}
std::cout << "> exit\n";
check(allocated_num1 == 0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment