Created
March 27, 2025 00:47
-
-
Save ialex32x/a588b6800f158c95e301097d278a9c6a to your computer and use it in GitHub Desktop.
v8-test
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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