Skip to content
File

Blob: src/rust/jsg/ffi.c++

43.9 KB
1// Copyright (c) 2026 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5#include "ffi.h"
6 
7#include <workerd/jsg/jsg.h>
8#include <workerd/jsg/setup.h>
9#include <workerd/jsg/util.h>
10#include <workerd/jsg/wrappable.h>
11#include <workerd/rust/jsg/ffi-inl.h>
12#include <workerd/rust/jsg/lib.rs.h>
13#include <workerd/rust/jsg/v8.rs.h>
14 
15#include <kj-rs/convert.h>
16 
17#include <kj/common.h>
18#include <kj/string.h>
19 
20#include <memory>
21 
22using namespace kj_rs;
23 
24namespace workerd::rust::jsg {
25 
26#define DEFINE_TYPED_ARRAY_NEW(name, v8_type, elem_type) \
27 Local local_new_##name(Isolate* isolate, const elem_type* data, size_t length) { \
28 auto backingStore = v8::ArrayBuffer::NewBackingStore(isolate, length * sizeof(elem_type)); \
29 memcpy(backingStore->Data(), data, length * sizeof(elem_type)); \
30 auto arrayBuffer = v8::ArrayBuffer::New(isolate, std::move(backingStore)); \
31 return to_ffi(v8::v8_type::New(arrayBuffer, 0, length)); \
32 }
33 
34#define DEFINE_TYPED_ARRAY_UNWRAP(name, v8_type, elem_type) \
35 ::rust::Vec<elem_type> unwrap_##name(Isolate* isolate, Local value) { \
36 auto v8Val = local_from_ffi<v8::Value>(kj::mv(value)); \
37 KJ_REQUIRE(v8Val->Is##v8_type()); \
38 auto typed = v8Val.As<v8::v8_type>(); \
39 ::rust::Vec<elem_type> result; \
40 result.reserve(typed->Length()); \
41 auto data = reinterpret_cast<elem_type*>( \
42 static_cast<uint8_t*>(typed->Buffer()->Data()) + typed->ByteOffset()); \
43 for (size_t i = 0; i < typed->Length(); i++) { \
44 result.push_back(data[i]); \
45 } \
46 return result; \
47 }
48 
49#define DEFINE_TYPED_ARRAY_GET(name, v8_type, elem_type) \
50 elem_type local_##name##_get(Isolate* isolate, const Local& array, size_t index) { \
51 auto typed = local_as_ref_from_ffi<v8::v8_type>(array); \
52 KJ_REQUIRE(index < typed->Length(), "index out of bounds"); \
53 auto data = reinterpret_cast<elem_type*>( \
54 static_cast<uint8_t*>(typed->Buffer()->Data()) + typed->ByteOffset()); \
55 return data[index]; \
56 }
57 
58// BackingStore — the size_t handle is the address of a heap-allocated
59// std::shared_ptr<v8::BackingStore> allocated with `new`.
60 
61// Create an interned V8 string from a Rust identifier name (rust::String or rust::Str).
62// NewFromUtf8 returns an empty MaybeLocal (without scheduling a JS exception) when the
63// string exceeds v8::String::kMaxLength; the KJ_REQUIRE prevents a confusing ICE inside
64// jsg::check() by catching the overlong name early with a clear error message.
65template <typename Name>
66static v8::Local<v8::String> makeInternedStr(v8::Isolate* isolate, const Name& name) {
67 KJ_REQUIRE(name.size() <= static_cast<size_t>(v8::String::kMaxLength),
68 "Rust identifier name exceeds V8 string length limit", name);
69 return ::workerd::jsg::check(
70 v8::String::NewFromUtf8(isolate, name.data(), v8::NewStringType::kInternalized, name.size()));
71}
72 
73// Wrappable implementation - calls into Rust via CXX bridge
74Wrappable::~Wrappable() {
75 wrappable_invoke_drop(*this);
76}
77 
78void Wrappable::jsgVisitForGc(::workerd::jsg::GcVisitor& visitor) {
79 auto ffi_visitor = to_ffi(&visitor);
80 wrappable_invoke_trace(*this, &ffi_visitor);
81}
82 
83kj::StringPtr Wrappable::jsgGetMemoryName() const {
84 // memory_name() on the Rust side returns a &'static str backed by a
85 // compile-time c"..." literal, so the pointer is valid for the process
86 // lifetime. Construct kj::StringPtr directly from data+size — no copy,
87 // no allocation, no caching needed.
88 auto name = wrappable_invoke_get_name(*this);
89 return kj::StringPtr(name.data(), name.size());
90}
91 
92size_t Wrappable::jsgGetMemorySelfSize() const {
93 return sizeof(Wrappable);
94}
95 
96// Local<T>
97void local_drop(Local value) {
98 // Convert from FFI representation and let v8::Local destructor handle cleanup
99 local_from_ffi<v8::Value>(kj::mv(value));
100}
101 
102Local local_clone(const Local& value) {
103 return Local{.ptr = value.ptr};
104}
105 
106Global local_to_global(Isolate* isolate, Local value) {
107 v8::Global<v8::Value> global(isolate, local_from_ffi<v8::Value>(kj::mv(value)));
108 return to_ffi(kj::mv(global));
109}
110 
111Local local_new_number(Isolate* isolate, double value) {
112 v8::Local<v8::Number> val = v8::Number::New(isolate, value);
113 return to_ffi(kj::mv(val));
114}
115 
116Local local_new_string(Isolate* isolate, ::rust::Str value) {
117 auto val = ::workerd::jsg::check(
118 v8::String::NewFromUtf8(isolate, value.cbegin(), v8::NewStringType::kNormal, value.size()));
119 return to_ffi(kj::mv(val));
120}
121 
122Local local_new_boolean(Isolate* isolate, bool value) {
123 v8::Local<v8::Boolean> val = v8::Boolean::New(isolate, value);
124 return to_ffi(kj::mv(val));
125}
126 
127Local local_new_object(Isolate* isolate) {
128 v8::Local<v8::Object> object = v8::Object::New(isolate);
129 return to_ffi(kj::mv(object));
130}
131 
132Local local_new_null(Isolate* isolate) {
133 v8::Local<v8::Primitive> null = v8::Null(isolate);
134 return to_ffi(kj::mv(null));
135}
136 
137Local local_new_undefined(Isolate* isolate) {
138 v8::Local<v8::Primitive> undefined = v8::Undefined(isolate);
139 return to_ffi(kj::mv(undefined));
140}
141 
142bool local_eq(const Local& lhs, const Local& rhs) {
143 return local_as_ref_from_ffi<v8::Value>(lhs) == local_as_ref_from_ffi<v8::Value>(rhs);
144}
145 
146bool local_has_value(const Local& val) {
147 return *local_as_ref_from_ffi<v8::Value>(val) != nullptr;
148}
149 
150bool local_is_string(const Local& val) {
151 return local_as_ref_from_ffi<v8::Value>(val)->IsString();
152}
153 
154bool local_is_boolean(const Local& val) {
155 return local_as_ref_from_ffi<v8::Value>(val)->IsBoolean();
156}
157 
158bool local_is_number(const Local& val) {
159 return local_as_ref_from_ffi<v8::Value>(val)->IsNumber();
160}
161 
162bool local_is_null(const Local& val) {
163 return local_as_ref_from_ffi<v8::Value>(val)->IsNull();
164}
165 
166bool local_is_undefined(const Local& val) {
167 return local_as_ref_from_ffi<v8::Value>(val)->IsUndefined();
168}
169 
170bool local_is_null_or_undefined(const Local& val) {
171 return local_as_ref_from_ffi<v8::Value>(val)->IsNullOrUndefined();
172}
173 
174bool local_is_object(const Local& val) {
175 return local_as_ref_from_ffi<v8::Value>(val)->IsObject();
176}
177 
178bool local_is_native_error(const Local& val) {
179 return local_as_ref_from_ffi<v8::Value>(val)->IsNativeError();
180}
181 
182bool local_is_array(const Local& val) {
183 return local_as_ref_from_ffi<v8::Value>(val)->IsArray();
184}
185 
186bool local_is_uint8_array(const Local& val) {
187 return local_as_ref_from_ffi<v8::Value>(val)->IsUint8Array();
188}
189 
190bool local_is_uint16_array(const Local& val) {
191 return local_as_ref_from_ffi<v8::Value>(val)->IsUint16Array();
192}
193 
194bool local_is_uint32_array(const Local& val) {
195 return local_as_ref_from_ffi<v8::Value>(val)->IsUint32Array();
196}
197 
198bool local_is_int8_array(const Local& val) {
199 return local_as_ref_from_ffi<v8::Value>(val)->IsInt8Array();
200}
201 
202bool local_is_int16_array(const Local& val) {
203 return local_as_ref_from_ffi<v8::Value>(val)->IsInt16Array();
204}
205 
206bool local_is_int32_array(const Local& val) {
207 return local_as_ref_from_ffi<v8::Value>(val)->IsInt32Array();
208}
209 
210bool local_is_float32_array(const Local& val) {
211 return local_as_ref_from_ffi<v8::Value>(val)->IsFloat32Array();
212}
213 
214bool local_is_float64_array(const Local& val) {
215 return local_as_ref_from_ffi<v8::Value>(val)->IsFloat64Array();
216}
217 
218bool local_is_bigint64_array(const Local& val) {
219 return local_as_ref_from_ffi<v8::Value>(val)->IsBigInt64Array();
220}
221 
222bool local_is_biguint64_array(const Local& val) {
223 return local_as_ref_from_ffi<v8::Value>(val)->IsBigUint64Array();
224}
225 
226bool local_is_float16_array(const Local& val) {
227 return local_as_ref_from_ffi<v8::Value>(val)->IsFloat16Array();
228}
229 
230bool local_is_uint8clamped_array(const Local& val) {
231 return local_as_ref_from_ffi<v8::Value>(val)->IsUint8ClampedArray();
232}
233 
234bool local_is_array_buffer(const Local& val) {
235 return local_as_ref_from_ffi<v8::Value>(val)->IsArrayBuffer();
236}
237 
238bool local_is_array_buffer_view(const Local& val) {
239 return local_as_ref_from_ffi<v8::Value>(val)->IsArrayBufferView();
240}
241 
242bool local_is_function(const Local& val) {
243 return local_as_ref_from_ffi<v8::Value>(val)->IsFunction();
244}
245 
246bool local_is_symbol(const Local& val) {
247 return local_as_ref_from_ffi<v8::Value>(val)->IsSymbol();
248}
249 
250bool local_is_name(const Local& val) {
251 return local_as_ref_from_ffi<v8::Value>(val)->IsName();
252}
253 
254bool local_is_shared_array_buffer(const Local& val) {
255 return local_as_ref_from_ffi<v8::Value>(val)->IsSharedArrayBuffer();
256}
257 
258::rust::String local_type_of(Isolate* isolate, const Local& val) {
259 auto v8Val = local_as_ref_from_ffi<v8::Value>(val);
260 v8::Local<v8::String> typeStr = v8Val->TypeOf(isolate);
261 v8::String::Utf8Value utf8(isolate, typeStr);
262 return ::rust::String(*utf8, utf8.length());
263}
264 
265// Utf8Value
266Utf8Value utf8_value_new(Isolate* isolate, Local value) {
267 auto* v = new v8::String::Utf8Value(isolate, local_from_ffi<v8::Value>(kj::mv(value)));
268 return Utf8Value{reinterpret_cast<size_t>(v)};
269}
270 
271void utf8_value_drop(Utf8Value value) {
272 delete reinterpret_cast<v8::String::Utf8Value*>(value.ptr);
273}
274 
275size_t utf8_value_length(const Utf8Value& value) {
276 return reinterpret_cast<const v8::String::Utf8Value*>(value.ptr)->length();
277}
278 
279const uint8_t* utf8_value_data(const Utf8Value& value) {
280 return reinterpret_cast<const uint8_t*>(
281 **reinterpret_cast<const v8::String::Utf8Value*>(value.ptr));
282}
283 
284// Local<String>
285Local local_string_empty(Isolate* isolate) {
286 return to_ffi(v8::String::Empty(isolate));
287}
288 
289int32_t local_string_length(const Local& value) {
290 return local_as_ref_from_ffi<v8::String>(value)->Length();
291}
292 
293bool local_string_is_one_byte(const Local& value) {
294 // Note: IsOneByte() reflects V8's internal string representation, not the logical
295 // content. A string containing only Latin-1 characters may still return false if V8
296 // stores it as two-byte (e.g. after concatenation), i.e. false negatives are possible.
297 // Use ContainsOnlyOneByte() for a content-based check, keeping in mind that it scans
298 // the entire string.
299 return local_as_ref_from_ffi<v8::String>(value)->IsOneByte();
300}
301 
302bool local_string_contains_only_one_byte(const Local& value) {
303 return local_as_ref_from_ffi<v8::String>(value)->ContainsOnlyOneByte();
304}
305 
306size_t local_string_utf8_length(Isolate* isolate, const Local& value) {
307 return local_as_ref_from_ffi<v8::String>(value)->Utf8LengthV2(isolate);
308}
309 
310void local_string_write_v2(Isolate* isolate,
311 const Local& value,
312 uint32_t offset,
313 uint32_t length,
314 uint16_t* buffer,
315 int32_t flags) {
316 local_as_ref_from_ffi<v8::String>(value)->WriteV2(isolate, offset, length, buffer, flags);
317}
318 
319void local_string_write_one_byte_v2(Isolate* isolate,
320 const Local& value,
321 uint32_t offset,
322 uint32_t length,
323 uint8_t* buffer,
324 int32_t flags) {
325 local_as_ref_from_ffi<v8::String>(value)->WriteOneByteV2(isolate, offset, length, buffer, flags);
326}
327 
328size_t local_string_write_utf8_v2(
329 Isolate* isolate, const Local& value, uint8_t* buffer, size_t capacity, int32_t flags) {
330 return local_as_ref_from_ffi<v8::String>(value)->WriteUtf8V2(
331 isolate, reinterpret_cast<char*>(buffer), capacity, flags);
332}
333 
334bool local_string_equals(const Local& value, const Local& other) {
335 return local_as_ref_from_ffi<v8::String>(value)->StringEquals(
336 local_as_ref_from_ffi<v8::String>(other));
337}
338 
339bool local_string_is_flat(const Local& value) {
340 return local_as_ref_from_ffi<v8::String>(value)->IsFlat();
341}
342 
343Local local_string_concat(Isolate* isolate, Local left, Local right) {
344 return to_ffi(v8::String::Concat(isolate, local_from_ffi<v8::String>(kj::mv(left)),
345 local_from_ffi<v8::String>(kj::mv(right))));
346}
347 
348Local local_string_internalize(Isolate* isolate, const Local& value) {
349 return to_ffi(local_as_ref_from_ffi<v8::String>(value)->InternalizeString(isolate));
350}
351 
352MaybeLocal local_string_new_from_utf8(
353 Isolate* isolate, const uint8_t* data, int32_t length, bool internalized) {
354 auto type = internalized ? v8::NewStringType::kInternalized : v8::NewStringType::kNormal;
355 return maybe_local_to_ffi(
356 v8::String::NewFromUtf8(isolate, reinterpret_cast<const char*>(data), type, length));
357}
358 
359MaybeLocal local_string_new_from_one_byte(
360 Isolate* isolate, const uint8_t* data, int32_t length, bool internalized) {
361 auto type = internalized ? v8::NewStringType::kInternalized : v8::NewStringType::kNormal;
362 return maybe_local_to_ffi(v8::String::NewFromOneByte(isolate, data, type, length));
363}
364 
365MaybeLocal local_string_new_from_two_byte(
366 Isolate* isolate, const uint16_t* data, int32_t length, bool internalized) {
367 auto type = internalized ? v8::NewStringType::kInternalized : v8::NewStringType::kNormal;
368 return maybe_local_to_ffi(v8::String::NewFromTwoByte(isolate, data, type, length));
369}
370 
371bool maybe_local_is_empty(const MaybeLocal& value) {
372 auto ptr_void = reinterpret_cast<const void*>(&value.ptr);
373 return reinterpret_cast<const v8::MaybeLocal<v8::Value>*>(ptr_void)->IsEmpty();
374}
375 
376// Local<Name>
377int32_t local_name_get_identity_hash(const Local& value) {
378 return local_as_ref_from_ffi<v8::Name>(value)->GetIdentityHash();
379}
380 
381// Local<Symbol>
382Local local_symbol_new(Isolate* isolate) {
383 return to_ffi(v8::Symbol::New(isolate));
384}
385 
386Local local_symbol_new_with_description(Isolate* isolate, Local description) {
387 return to_ffi(v8::Symbol::New(isolate, local_from_ffi<v8::String>(kj::mv(description))));
388}
389 
390MaybeLocal local_symbol_description(Isolate* isolate, const Local& value) {
391 auto sym = local_as_ref_from_ffi<v8::Symbol>(value);
392 v8::Local<v8::Value> desc = sym->Description(isolate);
393 if (desc->IsUndefined()) {
394 return maybe_local_to_ffi(v8::MaybeLocal<v8::String>());
395 }
396 // Description is always a String when present.
397 return maybe_local_to_ffi(v8::MaybeLocal<v8::String>(desc.As<v8::String>()));
398}
399 
400// Local<Function>
401Local local_function_call(
402 Isolate* isolate, const Local& function, const Local& recv, ::rust::Slice<const Local> args) {
403 auto context = isolate->GetCurrentContext();
404 auto fn = local_as_ref_from_ffi<v8::Function>(function);
405 auto receiver = local_as_ref_from_ffi<v8::Value>(recv);
406 
407 v8::LocalVector<v8::Value> v8Args(isolate, args.size());
408 for (size_t i = 0; i < args.size(); i++) {
409 v8Args[i] = local_as_ref_from_ffi<v8::Value>(args[i]);
410 }
411 
412 return to_ffi(::workerd::jsg::check(fn->Call(context, receiver, v8Args.size(), v8Args.data())));
413}
414 
415// Local<Object>
416void local_object_set_property(Isolate* isolate, Local& object, ::rust::Str key, Local value) {
417 auto v8_obj = local_as_ref_from_ffi<v8::Object>(object);
418 auto context = isolate->GetCurrentContext();
419 auto v8_key = makeInternedStr(isolate, key);
420 ::workerd::jsg::check(v8_obj->Set(context, v8_key, local_from_ffi<v8::Value>(kj::mv(value))));
421}
422 
423bool local_object_has_property(Isolate* isolate, const Local& object, ::rust::Str key) {
424 auto v8_obj = local_as_ref_from_ffi<v8::Object>(object);
425 auto context = isolate->GetCurrentContext();
426 auto v8_key = makeInternedStr(isolate, key);
427 return v8_obj->Has(context, v8_key).FromJust();
428}
429 
430kj::Maybe<Local> local_object_get_property(Isolate* isolate, const Local& object, ::rust::Str key) {
431 auto v8_obj = local_as_ref_from_ffi<v8::Object>(object);
432 auto context = isolate->GetCurrentContext();
433 auto v8_key = makeInternedStr(isolate, key);
434 v8::Local<v8::Value> result;
435 if (!v8_obj->Get(context, v8_key).ToLocal(&result)) {
436 return kj::none;
437 }
438 return to_ffi(kj::mv(result));
439}
440 
441// Local<Array>
442Local local_new_array(Isolate* isolate, size_t length) {
443 return to_ffi(v8::Array::New(isolate, length));
444}
445 
446uint32_t local_array_length(Isolate* isolate, const Local& array) {
447 return local_as_ref_from_ffi<v8::Array>(array)->Length();
448}
449 
450Local local_array_get(Isolate* isolate, const Local& array, uint32_t index) {
451 auto context = isolate->GetCurrentContext();
452 auto v8Array = local_as_ref_from_ffi<v8::Array>(array);
453 return to_ffi(::workerd::jsg::check(v8Array->Get(context, index)));
454}
455 
456void local_array_set(Isolate* isolate, Local& array, uint32_t index, Local value) {
457 auto context = isolate->GetCurrentContext();
458 auto v8Array = local_as_ref_from_ffi<v8::Array>(array);
459 ::workerd::jsg::check(v8Array->Set(context, index, local_from_ffi<v8::Value>(kj::mv(value))));
460}
461 
462// Local<ArrayBuffer>
463Local local_new_array_buffer(Isolate* isolate, const uint8_t* data, size_t length) {
464 auto backingStore = v8::ArrayBuffer::NewBackingStore(isolate, length);
465 if (length > 0) {
466 memcpy(backingStore->Data(), data, length);
467 }
468 return to_ffi(v8::ArrayBuffer::New(isolate, std::move(backingStore)));
469}
470 
471// "empty" means zero-initialized with no source data to copy from, as opposed to
472// local_new_array_buffer which copies caller-supplied bytes into the buffer.
473Local local_new_array_buffer_empty(Isolate* isolate, size_t byte_length) {
474 return to_ffi(v8::ArrayBuffer::New(isolate, byte_length));
475}
476 
477kj::Maybe<Local> array_buffer_new_with_mode(
478 Isolate* isolate, size_t byte_length, BackingStoreInitializationMode mode) {
479 auto maybe = v8::ArrayBuffer::MaybeNew(
480 isolate, byte_length, static_cast<v8::BackingStoreInitializationMode>(mode));
481 if (maybe.IsEmpty()) return kj::none;
482 return to_ffi(maybe.ToLocalChecked());
483}
484 
485Local array_buffer_from_backing_store(Isolate* isolate, size_t ptr) {
486 return to_ffi(
487 v8::ArrayBuffer::New(isolate, *reinterpret_cast<std::shared_ptr<v8::BackingStore>*>(ptr)));
488}
489 
490size_t local_array_buffer_byte_length(Isolate* isolate, const Local& buffer) {
491 return local_as_ref_from_ffi<v8::ArrayBuffer>(buffer)->ByteLength();
492}
493 
494uint8_t* local_array_buffer_data(Isolate* isolate, const Local& buffer) {
495 return static_cast<uint8_t*>(local_as_ref_from_ffi<v8::ArrayBuffer>(buffer)->Data());
496}
497 
498size_t local_array_buffer_get_backing_store(Isolate* isolate, const Local& buffer) {
499 return reinterpret_cast<size_t>(new std::shared_ptr<v8::BackingStore>(
500 local_as_ref_from_ffi<v8::ArrayBuffer>(buffer)->GetBackingStore()));
501}
502 
503// Local<ArrayBufferView>
504size_t local_array_buffer_view_byte_offset(Isolate* isolate, const Local& view) {
505 return local_as_ref_from_ffi<v8::ArrayBufferView>(view)->ByteOffset();
506}
507 
508size_t local_array_buffer_view_byte_length(Isolate* isolate, const Local& view) {
509 return local_as_ref_from_ffi<v8::ArrayBufferView>(view)->ByteLength();
510}
511 
512uint8_t* local_array_buffer_view_buffer_data(Isolate* isolate, const Local& view) {
513 return static_cast<uint8_t*>(local_as_ref_from_ffi<v8::ArrayBufferView>(view)->Buffer()->Data());
514}
515 
516Local local_array_buffer_view_get_buffer(Isolate* isolate, const Local& view) {
517 return to_ffi(local_as_ref_from_ffi<v8::ArrayBufferView>(view)->Buffer());
518}
519 
520size_t local_array_buffer_view_element_size(Isolate* isolate, const Local& view) {
521 auto& v8Val = local_as_ref_from_ffi<v8::Value>(view);
522 if (v8Val->IsUint8Array() || v8Val->IsInt8Array() || v8Val->IsUint8ClampedArray()) return 1;
523 if (v8Val->IsUint16Array() || v8Val->IsInt16Array()) return 2;
524 if (v8Val->IsUint32Array() || v8Val->IsInt32Array() || v8Val->IsFloat32Array()) return 4;
525 if (v8Val->IsFloat64Array() || v8Val->IsBigInt64Array() || v8Val->IsBigUint64Array()) return 8;
526 return 0; // DataView — no fixed element size
527}
528 
529bool local_array_buffer_view_is_integer_type(Isolate* isolate, const Local& view) {
530 auto& v8Val = local_as_ref_from_ffi<v8::Value>(view);
531 // Float32Array, Float64Array, and DataView are not integer types.
532 return v8Val->IsTypedArray() && !v8Val->IsFloat32Array() && !v8Val->IsFloat64Array();
533}
534 
535// BackingStore
536size_t backing_store_new_resizable(size_t byte_length, size_t max_byte_length) {
537 return reinterpret_cast<size_t>(new std::shared_ptr<v8::BackingStore>(
538 v8::ArrayBuffer::NewResizableBackingStore(byte_length, max_byte_length)));
539}
540 
541void backing_store_drop(size_t ptr) {
542 delete reinterpret_cast<std::shared_ptr<v8::BackingStore>*>(ptr);
543}
544 
545uint8_t* backing_store_data(size_t ptr) {
546 return static_cast<uint8_t*>(
547 reinterpret_cast<std::shared_ptr<v8::BackingStore>*>(ptr)->get()->Data());
548}
549 
550size_t backing_store_byte_length(size_t ptr) {
551 return reinterpret_cast<std::shared_ptr<v8::BackingStore>*>(ptr)->get()->ByteLength();
552}
553 
554size_t backing_store_max_byte_length(size_t ptr) {
555 return reinterpret_cast<std::shared_ptr<v8::BackingStore>*>(ptr)->get()->MaxByteLength();
556}
557 
558bool backing_store_is_shared(size_t ptr) {
559 return reinterpret_cast<std::shared_ptr<v8::BackingStore>*>(ptr)->get()->IsShared();
560}
561 
562bool backing_store_is_resizable_by_user_javascript(size_t ptr) {
563 return reinterpret_cast<std::shared_ptr<v8::BackingStore>*>(ptr)
564 ->get()
565 ->IsResizableByUserJavaScript();
566}
567 
568// ArrayBuffer detach/detachable/was-detached
569void local_array_buffer_detach(Isolate* isolate, Local& buffer) {
570 local_as_ref_from_ffi<v8::ArrayBuffer>(buffer)->Detach(v8::Local<v8::Value>()).Check();
571}
572 
573bool local_array_buffer_was_detached(Isolate* isolate, const Local& buffer) {
574 return local_as_ref_from_ffi<v8::ArrayBuffer>(buffer)->WasDetached();
575}
576 
577bool local_array_buffer_is_detachable(Isolate* isolate, const Local& buffer) {
578 return local_as_ref_from_ffi<v8::ArrayBuffer>(buffer)->IsDetachable();
579}
580 
581// ArrayBuffer is_shared (value-level check)
582bool local_array_buffer_is_shared(const Local& value) {
583 return local_as_ref_from_ffi<v8::Value>(value)->IsSharedArrayBuffer();
584}
585 
586// TypedArray creation functions
587// TODO(perf): These macros duplicate patterns in buffersource.h — unify when the
588// Rust FFI stabilises.
589DEFINE_TYPED_ARRAY_NEW(uint8_array, Uint8Array, uint8_t)
590DEFINE_TYPED_ARRAY_NEW(uint16_array, Uint16Array, uint16_t)
591DEFINE_TYPED_ARRAY_NEW(uint32_array, Uint32Array, uint32_t)
592DEFINE_TYPED_ARRAY_NEW(int8_array, Int8Array, int8_t)
593DEFINE_TYPED_ARRAY_NEW(int16_array, Int16Array, int16_t)
594DEFINE_TYPED_ARRAY_NEW(int32_array, Int32Array, int32_t)
595DEFINE_TYPED_ARRAY_NEW(float32_array, Float32Array, float)
596DEFINE_TYPED_ARRAY_NEW(float64_array, Float64Array, double)
597DEFINE_TYPED_ARRAY_NEW(bigint64_array, BigInt64Array, int64_t)
598DEFINE_TYPED_ARRAY_NEW(biguint64_array, BigUint64Array, uint64_t)
599 
600// Wrappers
601Local wrap_resource(Isolate* isolate, kj::Rc<Wrappable> wrappable, const Global& tmpl) {
602 // Check if already wrapped
603 KJ_IF_SOME(handle, wrappable->tryGetHandle(isolate)) {
604 return to_ffi(v8::Local<v8::Value>::Cast(handle));
605 }
606 
607 auto& global_tmpl = global_as_ref_from_ffi<v8::FunctionTemplate>(tmpl);
608 auto local_tmpl = v8::Local<v8::FunctionTemplate>::New(isolate, global_tmpl);
609 v8::Local<v8::Object> object = ::workerd::jsg::check(
610 local_tmpl->InstanceTemplate()->NewInstance(isolate->GetCurrentContext()));
611 
612 // attachWrapper sets up CppgcShim, TracedReference, internal fields, etc.
613 wrappable->attachWrapper(isolate, object, true);
614 
615 // Override tag to identify as Rust object for unwrapping
616 auto tagAddress = const_cast<uint16_t*>(&::workerd::jsg::Wrappable::WORKERD_RUST_WRAPPABLE_TAG);
617 object->SetAlignedPointerInInternalField(::workerd::jsg::Wrappable::WRAPPABLE_TAG_FIELD_INDEX,
618 tagAddress,
619 static_cast<v8::EmbedderDataTypeTag>(::workerd::jsg::Wrappable::WRAPPABLE_TAG_FIELD_INDEX));
620 
621 return to_ffi(v8::Local<v8::Value>::Cast(object));
622}
623 
624void wrappable_attach_wrapper(kj::Rc<Wrappable> wrappable, FunctionCallbackInfo& args) {
625 auto* isolate = args.GetIsolate();
626 auto object = args.This();
627 
628 // attachWrapper sets up CppgcShim, TracedReference, internal fields, etc.
629 wrappable->attachWrapper(isolate, object, true);
630 
631 // Override tag to identify as Rust object for unwrapping
632 auto tagAddress = const_cast<uint16_t*>(&::workerd::jsg::Wrappable::WORKERD_RUST_WRAPPABLE_TAG);
633 object->SetAlignedPointerInInternalField(::workerd::jsg::Wrappable::WRAPPABLE_TAG_FIELD_INDEX,
634 tagAddress,
635 static_cast<v8::EmbedderDataTypeTag>(::workerd::jsg::Wrappable::WRAPPABLE_TAG_FIELD_INDEX));
636}
637 
638// Unwrappers
639::rust::String unwrap_string(Isolate* isolate, Local value) {
640 v8::Local<v8::String> v8Str = ::workerd::jsg::check(
641 local_from_ffi<v8::Value>(kj::mv(value))->ToString(isolate->GetCurrentContext()));
642 v8::String::ValueView view(isolate, v8Str);
643 if (!view.is_one_byte()) {
644 return ::rust::String(reinterpret_cast<const char16_t*>(view.data16()), view.length());
645 }
646 return ::rust::String::latin1(reinterpret_cast<const char*>(view.data8()), view.length());
647}
648 
649bool unwrap_boolean(Isolate* isolate, Local value) {
650 return local_from_ffi<v8::Value>(kj::mv(value))->ToBoolean(isolate)->Value();
651}
652 
653double unwrap_number(Isolate* isolate, Local value) {
654 return ::workerd::jsg::check(
655 local_from_ffi<v8::Value>(kj::mv(value))->ToNumber(isolate->GetCurrentContext()))
656 ->Value();
657}
658 
659kj::Rc<Wrappable> unwrap_resource(Isolate* isolate, Local value) {
660 auto v8_val = local_from_ffi<v8::Value>(kj::mv(value));
661 // Non-object values (numbers, strings, booleans, etc.) are never wrapped resources.
662 if (!v8_val->IsObject()) return nullptr;
663 auto v8_obj = v8_val.As<v8::Object>();
664 // Plain JS objects have no internal fields; check before reading to avoid V8 fatal error.
665 if (v8_obj->InternalFieldCount() < ::workerd::jsg::Wrappable::INTERNAL_FIELD_COUNT ||
666 v8_obj->GetAlignedPointerFromInternalField(
667 ::workerd::jsg::Wrappable::WRAPPABLE_TAG_FIELD_INDEX,
668 static_cast<v8::EmbedderDataTypeTag>(
669 ::workerd::jsg::Wrappable::WRAPPABLE_TAG_FIELD_INDEX)) !=
670 const_cast<uint16_t*>(&::workerd::jsg::Wrappable::WORKERD_RUST_WRAPPABLE_TAG)) {
671 return nullptr;
672 }
673 auto* ptr = static_cast<Wrappable*>(
674 reinterpret_cast<::workerd::jsg::Wrappable*>(v8_obj->GetAlignedPointerFromInternalField(
675 ::workerd::jsg::Wrappable::WRAPPED_OBJECT_FIELD_INDEX,
676 static_cast<v8::EmbedderDataTypeTag>(
677 ::workerd::jsg::Wrappable::WRAPPED_OBJECT_FIELD_INDEX))));
678 return ptr->toRc();
679}
680 
681// TypedArray unwrap functions
682DEFINE_TYPED_ARRAY_UNWRAP(uint8_array, Uint8Array, uint8_t)
683DEFINE_TYPED_ARRAY_UNWRAP(uint16_array, Uint16Array, uint16_t)
684DEFINE_TYPED_ARRAY_UNWRAP(uint32_array, Uint32Array, uint32_t)
685DEFINE_TYPED_ARRAY_UNWRAP(int8_array, Int8Array, int8_t)
686DEFINE_TYPED_ARRAY_UNWRAP(int16_array, Int16Array, int16_t)
687DEFINE_TYPED_ARRAY_UNWRAP(int32_array, Int32Array, int32_t)
688DEFINE_TYPED_ARRAY_UNWRAP(float32_array, Float32Array, float)
689DEFINE_TYPED_ARRAY_UNWRAP(float64_array, Float64Array, double)
690DEFINE_TYPED_ARRAY_UNWRAP(bigint64_array, BigInt64Array, int64_t)
691DEFINE_TYPED_ARRAY_UNWRAP(biguint64_array, BigUint64Array, uint64_t)
692 
693// Uses V8's Array::Iterate() which is faster than indexed access.
694// Returns Global handles because Local handles get reused during iteration.
695::rust::Vec<Global> local_array_iterate(Isolate* isolate, Local value) {
696 auto context = isolate->GetCurrentContext();
697 auto v8Val = local_from_ffi<v8::Value>(kj::mv(value));
698 
699 KJ_REQUIRE(v8Val->IsArray(), "Value must be an array");
700 auto arr = v8Val.As<v8::Array>();
701 
702 struct Data {
703 Isolate* isolate;
704 ::rust::Vec<Global>* result;
705 };
706 
707 ::rust::Vec<Global> result;
708 Data data{isolate, &result};
709 
710 auto iterateResult = arr->Iterate(context,
711 [](uint32_t index, v8::Local<v8::Value> element,
712 void* userData) -> v8::Array::CallbackResult {
713 auto* d = static_cast<Data*>(userData);
714 d->result->push_back(to_ffi(v8::Global<v8::Value>(d->isolate, element)));
715 return v8::Array::CallbackResult::kContinue;
716 },
717 &data);
718 
719 KJ_REQUIRE(iterateResult.IsJust(), "Iteration failed");
720 return result;
721}
722 
723// Local<TypedArray>
724size_t local_typed_array_length(Isolate* isolate, const Local& array) {
725 return local_as_ref_from_ffi<v8::TypedArray>(array)->Length();
726}
727 
728uintptr_t local_typed_array_buffer_data(Isolate* isolate, const Local& array) {
729 return reinterpret_cast<uintptr_t>(
730 local_as_ref_from_ffi<v8::TypedArray>(array)->Buffer()->Data());
731}
732 
733size_t local_typed_array_byte_offset(Isolate* isolate, const Local& array) {
734 return local_as_ref_from_ffi<v8::TypedArray>(array)->ByteOffset();
735}
736 
737size_t local_typed_array_byte_length(Isolate* isolate, const Local& array) {
738 return local_as_ref_from_ffi<v8::TypedArray>(array)->ByteLength();
739}
740 
741// TypedArray element getter functions
742DEFINE_TYPED_ARRAY_GET(uint8_array, Uint8Array, uint8_t)
743DEFINE_TYPED_ARRAY_GET(uint16_array, Uint16Array, uint16_t)
744DEFINE_TYPED_ARRAY_GET(uint32_array, Uint32Array, uint32_t)
745DEFINE_TYPED_ARRAY_GET(int8_array, Int8Array, int8_t)
746DEFINE_TYPED_ARRAY_GET(int16_array, Int16Array, int16_t)
747DEFINE_TYPED_ARRAY_GET(int32_array, Int32Array, int32_t)
748DEFINE_TYPED_ARRAY_GET(float32_array, Float32Array, float)
749DEFINE_TYPED_ARRAY_GET(float64_array, Float64Array, double)
750DEFINE_TYPED_ARRAY_GET(bigint64_array, BigInt64Array, int64_t)
751DEFINE_TYPED_ARRAY_GET(biguint64_array, BigUint64Array, uint64_t)
752DEFINE_TYPED_ARRAY_GET(uint8clamped_array, Uint8ClampedArray, uint8_t)
753 
754// Global<T>
755void global_reset(Global& value) {
756 global_as_ref_from_ffi<v8::Value>(value)->Reset();
757}
758 
759Global global_clone(Isolate* isolate, const Global& value) {
760 auto& original = global_as_ref_from_ffi<v8::Value>(value);
761 return to_ffi(v8::Global<v8::Value>(isolate, original));
762}
763 
764Local global_to_local(Isolate* isolate, const Global& value) {
765 auto& glbl = global_as_ref_from_ffi<v8::Value>(value);
766 v8::Local<v8::Value> local = v8::Local<v8::Value>::New(isolate, glbl);
767 return to_ffi(kj::mv(local));
768}
769 
770// Wrappable - data access
771const TraitObjectPtr& wrappable_get_trait_object(const Wrappable& wrappable) {
772 return wrappable.trait_object;
773}
774 
775void wrappable_clear_trait_object(Wrappable& wrappable) {
776 wrappable.trait_object = {0, 0, 0, 0};
777}
778 
779kj::uint wrappable_strong_refcount(const Wrappable& wrappable) {
780 return wrappable.getStrongRefcount();
781}
782 
783// Wrappable lifecycle
784kj::Rc<Wrappable> wrappable_new(TraitObjectPtr ptr) {
785 auto rc = kj::rc<Wrappable>();
786 rc->trait_object = kj::mv(ptr);
787 rc->addStrongRef();
788 return kj::mv(rc);
789}
790 
791kj::Rc<Wrappable> wrappable_to_rc(Wrappable& wrappable) {
792 return wrappable.toRc();
793}
794 
795void wrappable_add_strong_ref(Wrappable& wrappable) {
796 wrappable.addStrongRef();
797}
798 
799void wrappable_remove_strong_ref(Wrappable& wrappable, bool is_strong) {
800 // maybeDeferDestruction() requires a kj::Own<Wrappable> to take ownership of.
801 // We must temporarily increment the refcount via addRef() to create that handle.
802 //
803 // Refcount accounting:
804 // addRef(wrappable) → +1 (creates `own`)
805 // maybeDeferDestruction → internally stores `own` in RefToDelete
806 // ~RefToDelete → if is_strong: calls removeStrongRef(), then drops `own` → -1
807 // Net effect: 0 (the actual kj::Rc decrement happens later when Rust drops the KjRc).
808 //
809 // is_strong must match the Ref's current strong flag. If GC tracing already transitioned
810 // the ref to weak (strong=false), passing true here would double-decrement strongRefcount.
811 auto own = kj::addRef(wrappable);
812 wrappable.maybeDeferDestruction(is_strong, kj::mv(own), &wrappable);
813}
814 
815void wrappable_visit_global(GcVisitor* visitor, uintptr_t* global, TracedReference& traced) {
816 auto* gcVisitor = gc_visitor_from_ffi(visitor);
817 auto& strongHandle = *reinterpret_cast<v8::Global<v8::Value>*>(global);
818 auto& tracedHandle = traced_ref_from_ffi(traced);
819 gcVisitor->visit(strongHandle, tracedHandle);
820}
821 
822void traced_reference_reset(TracedReference& traced) {
823 traced_ref_from_ffi(traced).Reset();
824}
825 
826void wrappable_visit_ref(
827 Wrappable& wrappable, uintptr_t* ref_parent, bool* ref_strong, GcVisitor* visitor) {
828 auto* gcVisitor = gc_visitor_from_ffi(visitor);
829 
830 // Convert opaque uintptr_t to kj::Maybe<Wrappable&>
831 kj::Maybe<::workerd::jsg::Wrappable&> parentMaybe;
832 if (*ref_parent != 0) {
833 parentMaybe = *reinterpret_cast<::workerd::jsg::Wrappable*>(*ref_parent);
834 }
835 
836 wrappable.visitRef(*gcVisitor, parentMaybe, *ref_strong);
837 
838 // Write back
839 KJ_IF_SOME(p, parentMaybe) {
840 *ref_parent = reinterpret_cast<uintptr_t>(&p);
841 } else {
842 *ref_parent = 0;
843 }
844}
845 
846// FunctionCallbackInfo
847Isolate* fci_get_isolate(FunctionCallbackInfo* args) {
848 return args->GetIsolate();
849}
850 
851Local fci_get_this(FunctionCallbackInfo* args) {
852 return to_ffi(args->This());
853}
854 
855size_t fci_get_length(FunctionCallbackInfo* args) {
856 return args->Length();
857}
858 
859Local fci_get_arg(FunctionCallbackInfo* args, size_t index) {
860 return to_ffi((*args)[index]);
861}
862 
863void fci_set_return_value(FunctionCallbackInfo* args, Local value) {
864 args->GetReturnValue().Set(local_from_ffi<v8::Value>(kj::mv(value)));
865}
866 
867Global create_resource_template(Isolate* isolate, const ResourceDescriptor& descriptor) {
868 // Construct lazily.
869 v8::EscapableHandleScope scope(isolate);
870 
871 v8::Local<v8::FunctionTemplate> constructor;
872 KJ_IF_SOME(descriptor, descriptor.constructor) {
873 constructor = v8::FunctionTemplate::New(isolate,
874 reinterpret_cast<v8::FunctionCallback>(reinterpret_cast<void*>(descriptor.callback)));
875 } else {
876 constructor = v8::FunctionTemplate::New(isolate, &workerd::jsg::throwIllegalConstructor);
877 }
878 
879 auto prototype = constructor->PrototypeTemplate();
880 
881 // Signatures protect our methods from being invoked with the wrong `this`.
882 auto signature = v8::Signature::New(isolate, constructor);
883 
884 auto instance = constructor->InstanceTemplate();
885 
886 instance->SetInternalFieldCount(workerd::jsg::Wrappable::INTERNAL_FIELD_COUNT);
887 
888 auto classname = ::workerd::jsg::check(v8::String::NewFromUtf8(
889 isolate, descriptor.name.data(), v8::NewStringType::kNormal, descriptor.name.size()));
890 
891 if (workerd::jsg::getShouldSetToStringTag(isolate)) {
892 prototype->Set(v8::Symbol::GetToStringTag(isolate), classname, v8::PropertyAttribute::DontEnum);
893 }
894 
895 auto internalMarker =
896 v8::Symbol::For(isolate, ::workerd::jsg::v8StrIntern(isolate, "cloudflare:internal-class"));
897 prototype->Set(internalMarker, internalMarker,
898 static_cast<v8::PropertyAttribute>(v8::PropertyAttribute::DontEnum |
899 v8::PropertyAttribute::DontDelete | v8::PropertyAttribute::ReadOnly));
900 
901 constructor->SetClassName(classname);
902 
903 for (const auto& method: descriptor.static_methods) {
904 auto functionTemplate = v8::FunctionTemplate::New(isolate,
905 reinterpret_cast<v8::FunctionCallback>(reinterpret_cast<void*>(method.callback)),
906 v8::Local<v8::Value>(), v8::Local<v8::Signature>(), 0, v8::ConstructorBehavior::kThrow);
907 functionTemplate->RemovePrototype();
908 constructor->Set(makeInternedStr(isolate, method.name), functionTemplate);
909 }
910 
911 for (const auto& method: descriptor.methods) {
912 auto functionTemplate = v8::FunctionTemplate::New(isolate,
913 reinterpret_cast<v8::FunctionCallback>(reinterpret_cast<void*>(method.callback)),
914 v8::Local<v8::Value>(), signature, 0, v8::ConstructorBehavior::kThrow);
915 auto name = makeInternedStr(isolate, method.name);
916 prototype->Set(name, functionTemplate);
917 }
918 
919 const bool specCompliant = workerd::jsg::getSpecCompliantPropertyAttributes(isolate);
920 
921 // Mirrors ResourceTypeBuilder constructor (resource.h:1263-1273): always create and install
922 // the inspectProperties ObjectTemplate under the kResourceTypeInspect API symbol. node:util's
923 // inspect() checks for this symbol on the prototype to identify JSG resource types and uses
924 // the dictionary to enumerate inspect-only properties by name. This must be present even on
925 // resource types with no #[jsg_inspect_property] fields, because inspect() also uses the
926 // symbol's presence to decide how to walk the prototype chain.
927 auto kResourceTypeInspectStr = ::workerd::jsg::v8StrIntern(isolate, "kResourceTypeInspect");
928 auto kResourceTypeInspectSymbol = v8::Symbol::ForApi(isolate, kResourceTypeInspectStr);
929 auto inspectProperties = v8::ObjectTemplate::New(isolate);
930 prototype->Set(kResourceTypeInspectSymbol, inspectProperties,
931 static_cast<v8::PropertyAttribute>(
932 v8::PropertyAttribute::ReadOnly | v8::PropertyAttribute::DontEnum));
933 
934 for (const auto& prop: descriptor.properties) {
935 auto v8Name = makeInternedStr(isolate, prop.name);
936 
937 // Helper: build a FunctionTemplate for a getter or setter callback, applying
938 // spec_compliant_property_attributes name/length rules when enabled.
939 // `isGetter` true → length=0, name="get <prop>"; false → length=1, name="set <prop>".
940 auto makePropFn = [&](size_t callback, bool isGetter) {
941 v8::Local<v8::FunctionTemplate> fn;
942 if (specCompliant) {
943 int len = isGetter ? 0 : 1;
944 // Per Web IDL, spec-compliant getters/setters use empty signature (matching C++
945 // registerPrototypeProperty/registerReadonlyPrototypeProperty in resource.h:1438-1441).
946 fn = v8::FunctionTemplate::New(isolate,
947 reinterpret_cast<v8::FunctionCallback>(reinterpret_cast<void*>(callback)),
948 v8::Local<v8::Value>(), v8::Local<v8::Signature>(), len,
949 v8::ConstructorBehavior::kThrow);
950 auto prefix = isGetter ? "get " : "set ";
951 fn->SetClassName(::workerd::jsg::v8Str(isolate, kj::str(prefix, prop.name)));
952 } else {
953 fn = v8::FunctionTemplate::New(
954 isolate, reinterpret_cast<v8::FunctionCallback>(reinterpret_cast<void*>(callback)));
955 }
956 return fn;
957 };
958 
959 switch (prop.kind) {
960 case PropertyKind::Prototype: {
961 // Mirrors registerPrototypeProperty / registerReadonlyPrototypeProperty in resource.h.
962 auto getterFn = makePropFn(prop.getter_callback, true /* isGetter */);
963 KJ_IF_SOME(setterCb, prop.setter_callback) {
964 auto setterFn = makePropFn(setterCb, false /* isGetter */);
965 // Normal (non-Unimplemented) prototype properties are enumerable — use None, matching
966 // C++ registerPrototypeProperty (resource.h:1454-1455) with Gcb::enumerable = true.
967 prototype->SetAccessorProperty(v8Name, getterFn, setterFn, v8::PropertyAttribute::None);
968 } else {
969 // Read-only prototype properties are also enumerable — use ReadOnly only, matching
970 // C++ registerReadonlyPrototypeProperty (resource.h:1498-1501) with Gcb::enumerable = true.
971 prototype->SetAccessorProperty(
972 v8Name, getterFn, v8::Local<v8::FunctionTemplate>(), v8::PropertyAttribute::ReadOnly);
973 }
974 break;
975 }
976 case PropertyKind::Instance: {
977 // Mirrors registerInstanceProperty / registerReadonlyInstanceProperty in resource.h.
978 //
979 // We use ObjectTemplate::SetAccessorProperty with FunctionTemplates rather than
980 // SetNativeDataProperty because our Rust callbacks are FunctionCallbackInfo-style
981 // (matching #[jsg_method]), not PropertyCallbackInfo-style.
982 // SetAccessorProperty on the InstanceTemplate installs the accessor as an own
983 // property on every instance, matching JSG_INSTANCE_PROPERTY semantics.
984 auto getterFn = makePropFn(prop.getter_callback, true /* isGetter */);
985 KJ_IF_SOME(setterCb, prop.setter_callback) {
986 auto setterFn = makePropFn(setterCb, false /* isGetter */);
987 instance->SetAccessorProperty(v8Name, getterFn, setterFn, v8::PropertyAttribute::None);
988 } else {
989 instance->SetAccessorProperty(
990 v8Name, getterFn, v8::Local<v8::FunctionTemplate>(), v8::PropertyAttribute::ReadOnly);
991 }
992 break;
993 }
994 case PropertyKind::Inspect: {
995 // Mirrors registerInspectProperty in resource.h (lines 1521-1535).
996 //
997 // 1. Create a unique per-property symbol (so the getter is inaccessible via string lookup).
998 // 2. Register name → symbol in inspectProperties so node:util can enumerate it by name.
999 // 3. Install the getter under the unique symbol on the prototype (ReadOnly | DontEnum).
1000 //
1001 // spec_compliant_property_attributes has no effect on inspect properties.
1002 auto symbol = v8::Symbol::New(isolate, v8Name);
1003 inspectProperties->Set(v8Name, symbol, v8::PropertyAttribute::ReadOnly);
1004 auto getterFn = v8::FunctionTemplate::New(isolate,
1005 reinterpret_cast<v8::FunctionCallback>(reinterpret_cast<void*>(prop.getter_callback)));
1006 prototype->SetAccessorProperty(symbol, getterFn, v8::Local<v8::FunctionTemplate>(),
1007 static_cast<v8::PropertyAttribute>(
1008 v8::PropertyAttribute::ReadOnly | v8::PropertyAttribute::DontEnum));
1009 break;
1010 }
1011 }
1012 }
1013 
1014 for (const auto& constant: descriptor.static_constants) {
1015 auto name = makeInternedStr(isolate, constant.name);
1016 auto value = v8::Number::New(isolate, constant.value);
1017 
1018 // Per Web IDL, constants are {writable: false, enumerable: true, configurable: false}.
1019 auto attrs = ::workerd::jsg::getSpecCompliantPropertyAttributes(isolate)
1020 ? static_cast<v8::PropertyAttribute>(
1021 v8::PropertyAttribute::ReadOnly | v8::PropertyAttribute::DontDelete)
1022 : v8::PropertyAttribute::ReadOnly;
1023 constructor->Set(name, value, attrs);
1024 prototype->Set(name, value, attrs);
1025 }
1026 
1027 auto result = scope.Escape(constructor);
1028 return to_ffi(v8::Global<v8::FunctionTemplate>(isolate, result));
1029}
1030 
1031// FunctionTemplate
1032Local function_template_get_function(Isolate* isolate, const Global& tmpl) {
1033 auto& global_tmpl = global_as_ref_from_ffi<v8::FunctionTemplate>(tmpl);
1034 auto local_tmpl = v8::Local<v8::FunctionTemplate>::New(isolate, global_tmpl);
1035 auto function = ::workerd::jsg::check(local_tmpl->GetFunction(isolate->GetCurrentContext()));
1036 return to_ffi(kj::mv(function));
1037}
1038 
1039// Realm
1040Realm* realm_from_isolate(Isolate* isolate) {
1041 auto* realm =
1042 static_cast<Realm*>(isolate->GetData(::workerd::jsg::SetDataIndex::SET_DATA_RUST_REALM));
1043 KJ_ASSERT(realm != nullptr, "Rust Realm not set on isolate");
1044 return realm;
1045}
1046 
1047// Errors
1048Local exception_create(Isolate* isolate, ExceptionType exception_type, ::rust::Str description) {
1049 auto message = ::workerd::jsg::check(v8::String::NewFromUtf8(
1050 isolate, description.data(), v8::NewStringType::kInternalized, description.size()));
1051 switch (exception_type) {
1052 case ExceptionType::RangeError:
1053 return to_ffi(v8::Exception::RangeError(message));
1054 case ExceptionType::ReferenceError:
1055 return to_ffi(v8::Exception::ReferenceError(message));
1056 case ExceptionType::SyntaxError:
1057 return to_ffi(v8::Exception::SyntaxError(message));
1058 case ExceptionType::TypeError:
1059 return to_ffi(v8::Exception::TypeError(message));
1060 default:
1061 // DOM-style exceptions (OperationError, DataError, etc.) and Error fall back to Error.
1062 // TODO(soon): Use js.domException() to create proper DOMException objects.
1063 return to_ffi(v8::Exception::Error(message));
1064 }
1065}
1066 
1067// Isolate
1068void isolate_throw_exception(Isolate* isolate, Local exception) {
1069 isolate->ThrowException(local_from_ffi<v8::Value>(kj::mv(exception)));
1070}
1071 
1072void isolate_throw_error(Isolate* isolate, ::rust::Str description) {
1073 auto message = ::workerd::jsg::check(v8::String::NewFromUtf8(
1074 isolate, description.data(), v8::NewStringType::kInternalized, description.size()));
1075 isolate->ThrowError(message);
1076}
1077 
1078void isolate_throw_internal_error(Isolate* isolate, ::rust::Str internalMessage) {
1079 // Mirrors makeInternalError() from util.c++: generates a unique error ID,
1080 // logs the internal message (with ID) to KJ_LOG(ERROR) for Sentry, and
1081 // throws a generic "internal error; reference = <id>" JS Error to the caller.
1082 // kj::heapString(data, size) copies exactly `size` bytes and appends a NUL,
1083 // avoiding the out-of-bounds read that kj::StringPtr(data, size) would cause
1084 // on non-NUL-terminated Rust &str data.
1085 auto message = kj::heapString(internalMessage.data(), internalMessage.size());
1086 isolate->ThrowException(::workerd::jsg::makeInternalError(isolate, message));
1087}
1088 
1089void isolate_terminate_execution(Isolate* isolate) {
1090 ::workerd::jsg::IsolateBase::from(isolate).terminateExecution();
1091}
1092 
1093bool isolate_is_locked(Isolate* isolate) {
1094 return v8::Locker::IsLocked(isolate);
1095}
1096 
1097} // namespace workerd::rust::jsg