Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
9c0c312
Reduce unofficial N-API surface
syrusakbary Aug 13, 2026
39ed7d1
Test structured clone transfer contract
syrusakbary Aug 13, 2026
853e2d7
Honor numeric property key conversion in QuickJS
syrusakbary Aug 13, 2026
c55fb1c
Consolidate heap space statistics snapshot
syrusakbary Aug 13, 2026
1fbc499
Return profiler results as environment values
syrusakbary Aug 13, 2026
539677f
Attach environment hooks atomically
syrusakbary Aug 13, 2026
15a7a88
Move runtime configuration into environment creation
syrusakbary Aug 13, 2026
7f4937b
Fold process memory into heap statistics
syrusakbary Aug 13, 2026
1216861
Configure source maps atomically
syrusakbary Aug 13, 2026
c3adc7b
Consolidate error metadata snapshots
syrusakbary Aug 13, 2026
81956dc
Make queued messages typed and consuming
syrusakbary Aug 13, 2026
0e09c5c
Open bytecode through one typed transaction
syrusakbary Aug 13, 2026
8bd57f8
Align QuickJS Error stack frames with V8
syrusakbary Aug 13, 2026
64f9389
Collapse module state into one snapshot
syrusakbary Aug 13, 2026
6ecf4ad
Type opaque module handles end to end
syrusakbary Aug 13, 2026
47cdea7
Configure module hooks atomically
syrusakbary Aug 13, 2026
7224fc8
Create modules through one tagged transaction
syrusakbary Aug 13, 2026
7ee2743
Return immutable metadata from module creation
syrusakbary Aug 13, 2026
fa693b4
Represent profiler runs as typed sessions
syrusakbary Aug 13, 2026
114e4b1
Unify near-heap-limit callback configuration
syrusakbary Aug 13, 2026
4fda2b3
Report host JavaScript heap statistics
syrusakbary Aug 13, 2026
6a6e2d0
Run GoogleTest discovery in the test phase
syrusakbary Aug 13, 2026
7ea3f68
Fix provider contracts after N-API surface reduction
syrusakbary Aug 13, 2026
9cd1ee0
Tighten reduced N-API provider contracts
syrusakbary Aug 13, 2026
d8bc023
Remove redundant provider state from unofficial ABI
syrusakbary Aug 13, 2026
14a6c01
Restore QuickJS context marker coverage
syrusakbary Aug 13, 2026
902011c
Fix provider boundary validation
syrusakbary Aug 14, 2026
ad55ac0
Query only requested module state
syrusakbary Aug 14, 2026
f09404e
Tighten environment and garbage collection APIs
syrusakbary Aug 14, 2026
05e2a35
Fix reduced N-API error and ownership edges
syrusakbary Aug 14, 2026
60c81ef
Limit context exception capture to sandbox inspection
syrusakbary Aug 14, 2026
fa1350f
Re-added process microtasks tick
syrusakbary Aug 14, 2026
a0aeeff
Harden the reduced N-API protocol boundary
syrusakbary Aug 15, 2026
4462d18
Tighten provider behavior contracts
syrusakbary Aug 15, 2026
d3f1581
Preserve provider callback semantics
syrusakbary Aug 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@ fn main() {
println!("cargo:rerun-if-changed=include");
println!("cargo:rerun-if-changed=lib/src");
println!("cargo:rerun-if-changed=v8/src");
println!("cargo:rerun-if-changed=src/edge_napi_embedder_hooks.cc");
println!("cargo:rerun-if-changed=src/edge_napi_embedder_hooks.h");
println!("cargo:rerun-if-env-changed=V8_INCLUDE_DIR");
println!("cargo:rerun-if-env-changed=V8_LIB_DIR");
println!("cargo:rerun-if-env-changed=V8_DEFINES");
Expand Down Expand Up @@ -100,7 +98,6 @@ fn main() {
.include(napi_lib_src.to_str().unwrap())
.include(napi_v8_src.to_str().unwrap())
.file("src/napi_bridge_init.cc")
.file("src/edge_napi_embedder_hooks.cc")
.file(napi_lib_src.join("napi_error_state.cc").to_str().unwrap())
.file(
napi_lib_src
Expand Down
801 changes: 470 additions & 331 deletions include/unofficial_napi.h

Large diffs are not rendered by default.

105 changes: 105 additions & 0 deletions include/unofficial_napi_wasm32_abi.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#ifndef UNOFFICIAL_NAPI_WASM32_ABI_H_
#define UNOFFICIAL_NAPI_WASM32_ABI_H_

#include <stddef.h>
#include <stdint.h>

// Fixed-width representations observed by Wasmer when Edge is compiled for
// wasm32. These are a wire contract, not native provider implementation types.
// Every pointer and size_t in the public C API is represented by one u32 here.
typedef struct {
uint32_t size;
uint32_t version;
uint32_t engine_flags;
uint32_t engine_flags_length;
} unofficial_napi_wasm32_runtime_options_v1;

typedef struct {
uint32_t size;
uint32_t version;
uint64_t total_memory;
uint64_t constrained_memory;
uint32_t max_young_generation_size_in_bytes;
uint32_t max_old_generation_size_in_bytes;
uint32_t code_range_size_in_bytes;
uint32_t stack_limit;
uint32_t guest_heap;
} unofficial_napi_wasm32_env_create_options_v1;

typedef struct {
uint32_t size;
uint32_t version;
uint32_t data;
uint32_t context_token_assign_callback;
uint32_t context_token_unassign_callback;
uint32_t enqueue_foreground_task_callback;
uint32_t fatal_error_callback;
uint32_t oom_error_callback;
} unofficial_napi_wasm32_env_hooks_v1;

typedef struct {
int32_t kind;
uint32_t text;
uint32_t bytecode;
} unofficial_napi_wasm32_js_source;

typedef struct {
uint32_t size;
uint32_t version;
uint32_t source_text;
uint32_t filename;
int32_t shape;
uint32_t params_or_undefined;
uint32_t host_defined_option_id;
int32_t line_offset;
int32_t column_offset;
uint32_t cache_bytes;
uint32_t cache_byte_length;
uint8_t has_cache;
uint8_t cache_policy;
uint8_t reserved[2];
} unofficial_napi_wasm32_bytecode_open_options_v1;

typedef struct {
uint32_t size;
uint32_t version;
int32_t kind;
uint32_t wrapper;
uint32_t url;
uint32_t context_or_undefined;
uint8_t payload[16];
} unofficial_napi_wasm32_module_create_options_v1;

typedef struct {
uint32_t size;
uint32_t version;
uint32_t import_module_dynamically;
uint32_t initialize_import_meta_object;
} unofficial_napi_wasm32_module_hooks_v1;

#if defined(__cplusplus)
static_assert(sizeof(unofficial_napi_wasm32_runtime_options_v1) == 16);
static_assert(sizeof(unofficial_napi_wasm32_env_create_options_v1) == 48);
static_assert(sizeof(unofficial_napi_wasm32_env_hooks_v1) == 32);
static_assert(sizeof(unofficial_napi_wasm32_js_source) == 12);
static_assert(sizeof(unofficial_napi_wasm32_bytecode_open_options_v1) == 48);
static_assert(sizeof(unofficial_napi_wasm32_module_create_options_v1) == 40);
static_assert(sizeof(unofficial_napi_wasm32_module_hooks_v1) == 16);
#else
_Static_assert(sizeof(unofficial_napi_wasm32_runtime_options_v1) == 16,
"wasm32 runtime-options wire layout changed");
_Static_assert(sizeof(unofficial_napi_wasm32_env_create_options_v1) == 48,
"wasm32 env-create wire layout changed");
_Static_assert(sizeof(unofficial_napi_wasm32_env_hooks_v1) == 32,
"wasm32 env-hooks wire layout changed");
_Static_assert(sizeof(unofficial_napi_wasm32_js_source) == 12,
"wasm32 JS-source wire layout changed");
_Static_assert(sizeof(unofficial_napi_wasm32_bytecode_open_options_v1) == 48,
"wasm32 bytecode-open wire layout changed");
_Static_assert(sizeof(unofficial_napi_wasm32_module_create_options_v1) == 40,
"wasm32 module-create wire layout changed");
_Static_assert(sizeof(unofficial_napi_wasm32_module_hooks_v1) == 16,
"wasm32 module-hooks wire layout changed");
#endif

#endif // UNOFFICIAL_NAPI_WASM32_ABI_H_
2 changes: 1 addition & 1 deletion quickjs/deps/quickjs
Submodule quickjs updated 2 files
+106 −19 quickjs.c
+44 −0 tests/test_builtin.js
9 changes: 9 additions & 0 deletions quickjs/src/internal/napi_bytecode.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,15 @@ namespace quickjs::detail
// cjs_function -> the compiled function; module -> the module value.
JSValue artifact = JS_UNDEFINED;
};

inline napi_bytecode_record__ *napi_bytecode_record_from_source(
const unofficial_napi_js_source *source)
{
return source != nullptr &&
source->kind == unofficial_napi_js_source_bytecode
? reinterpret_cast<napi_bytecode_record__ *>(source->bytecode)
: nullptr;
}
}

#endif // NAPI_QUICKJS_INTERNAL_NAPI_BYTECODE_H_
111 changes: 48 additions & 63 deletions quickjs/src/internal/napi_contextify.cc
Original file line number Diff line number Diff line change
Expand Up @@ -629,13 +629,27 @@ namespace quickjs::detail
napi_util__::set_string_property(ctx_, exception, "stack", summary + "\n" + stack_text);
}

napi_status napi_contextify__::get_error_source_positions(
napi_status napi_contextify__::get_error_metadata(
napi_value error,
unofficial_napi_error_source_positions *out)
unofficial_napi_error_metadata_mode mode,
unofficial_napi_error_metadata *out)
{
if (!napi_util__::check_env(env_) || error == nullptr || out == nullptr)
return napi_invalid_arg;
std::memset(out, 0, sizeof(*out));
if (mode == unofficial_napi_error_metadata_take_preserved)
return napi_ok;
if (mode != unofficial_napi_error_metadata_current &&
mode != unofficial_napi_error_metadata_positions_only &&
mode != unofficial_napi_error_metadata_thrown_at_only)
return napi_invalid_arg;

// QuickJS does not currently expose a provider stack snapshot for an
// arbitrary caught value. The cheap query is nevertheless a valid,
// side-effect-free empty result and must not inspect user properties.
if (mode == unofficial_napi_error_metadata_thrown_at_only)
return napi_ok;

out->line_number = -1;
out->start_column = -1;
out->end_column = -1;
Expand All @@ -645,7 +659,22 @@ namespace quickjs::detail
return status;
out->source_line = empty;
out->script_resource_name = empty;
return napi_ok;

if (mode == unofficial_napi_error_metadata_positions_only)
return napi_ok;

JSValueConst raw_error = napi_quickjs_value_inner(env_, error);
if (!JS_IsObject(raw_error))
return napi_util__::create_undefined(env_, &out->stderr_line);
JSValue value = JS_GetPropertyStr(ctx_, raw_error, "node:arrowMessage");
if (JS_IsException(value))
return napi_pending_exception;
if (JS_IsUndefined(value))
{
JS_FreeValue(ctx_, value);
return napi_util__::create_undefined(env_, &out->stderr_line);
}
return napi_util__::wrap_owned(env_, value, &out->stderr_line);
}

napi_status napi_contextify__::preserve_error_source_message(napi_value error)
Expand All @@ -656,15 +685,7 @@ namespace quickjs::detail
return napi_ok;
}

napi_status napi_contextify__::set_source_maps_enabled(bool enabled)
{
if (!napi_util__::check_env(env_))
return napi_invalid_arg;
source_maps_enabled_ = enabled;
return napi_ok;
}

napi_status napi_contextify__::set_get_source_map_error_source_callback(napi_value callback)
napi_status napi_contextify__::configure_source_maps(bool enabled, napi_value callback)
{
if (!napi_util__::check_env(env_))
return napi_invalid_arg;
Expand All @@ -674,49 +695,13 @@ namespace quickjs::detail
return napi_invalid_arg;
}

source_maps_enabled_ = enabled;
JS_FreeValue(ctx_, source_map_error_source_callback_);
source_map_error_source_callback_ =
callback == nullptr ? JS_UNDEFINED : JS_DupValue(ctx_, napi_quickjs_value_inner(env_, callback));
return napi_ok;
}

napi_status napi_contextify__::get_error_source_line_for_stderr(napi_value error,
napi_value *result_out)
{
if (!napi_util__::check_env(env_) || error == nullptr || result_out == nullptr)
return napi_invalid_arg;
JSValue value = JS_GetPropertyStr(ctx_, napi_quickjs_value_inner(env_, error), "node:arrowMessage");
if (JS_IsException(value))
return napi_pending_exception;
if (JS_IsUndefined(value))
{
JS_FreeValue(ctx_, value);
return napi_util__::create_undefined(env_, result_out);
}
return napi_util__::wrap_owned(env_, value, result_out);
}

napi_status napi_contextify__::get_error_thrown_at(napi_value error,
napi_value *result_out)
{
(void)error;
if (!napi_util__::check_env(env_) || result_out == nullptr)
return napi_invalid_arg;
return napi_util__::create_undefined(env_, result_out);
}

napi_status napi_contextify__::take_preserved_error_formatting(napi_value error,
napi_value *source_line_out,
napi_value *thrown_at_out)
{
if (!napi_util__::check_env(env_) || error == nullptr || source_line_out == nullptr || thrown_at_out == nullptr)
return napi_invalid_arg;
napi_status status = get_error_source_line_for_stderr(error, source_line_out);
if (status != napi_ok)
return status;
return napi_util__::create_undefined(env_, thrown_at_out);
}

napi_status napi_contextify__::make_context(napi_value sandbox_or_symbol,
napi_value name,
napi_value origin_or_undefined,
Expand Down Expand Up @@ -778,10 +763,10 @@ namespace quickjs::detail
(void)display_errors;
(void)break_on_sigint;
(void)break_on_first_line;
if (!napi_util__::check_env(env_) || source == nullptr ||
(source->text == nullptr && source->bytecode == nullptr) || result_out == nullptr)
if (!napi_util__::check_env(env_) ||
!unofficial_napi_js_source_is_valid(source) || result_out == nullptr)
return napi_invalid_arg;
auto *bytecode_record = static_cast<napi_bytecode_record__ *>(source->bytecode);
auto *bytecode_record = napi_bytecode_record_from_source(source);
if (bytecode_record != nullptr &&
bytecode_record->shape != unofficial_napi_bytecode_shape_script)
return napi_invalid_arg;
Expand Down Expand Up @@ -881,11 +866,11 @@ namespace quickjs::detail
{
(void)context_extensions_or_undefined;
(void)host_defined_option_id;
if (!napi_util__::check_env(env_) || source == nullptr ||
(source->text == nullptr && source->bytecode == nullptr) || result_out == nullptr)
if (!napi_util__::check_env(env_) ||
!unofficial_napi_js_source_is_valid(source) || result_out == nullptr)
return napi_invalid_arg;

auto *bytecode_record = static_cast<napi_bytecode_record__ *>(source->bytecode);
auto *bytecode_record = napi_bytecode_record_from_source(source);
if (bytecode_record != nullptr &&
bytecode_record->shape != unofficial_napi_bytecode_shape_cjs_function)
return napi_invalid_arg;
Expand Down Expand Up @@ -1077,7 +1062,7 @@ namespace quickjs::detail
napi_value host_defined_option_id,
int32_t line_offset,
int32_t column_offset,
void **bytecode_out,
unofficial_napi_bytecode *bytecode_out,
bool *can_parse_as_module_out)
{
(void)host_defined_option_id; // QuickJS handles HDO at record registration.
Expand Down Expand Up @@ -1155,7 +1140,7 @@ namespace quickjs::detail
return napi_invalid_arg;
}

*bytecode_out = record.release();
*bytecode_out = reinterpret_cast<unofficial_napi_bytecode>(record.release());
return napi_ok;
}

Expand All @@ -1166,7 +1151,7 @@ namespace quickjs::detail
int32_t shape,
napi_value params_or_undefined,
napi_value host_defined_option_id,
void **bytecode_out,
unofficial_napi_bytecode *bytecode_out,
bool *rejected_out)
{
(void)host_defined_option_id; // QuickJS handles HDO at record registration.
Expand Down Expand Up @@ -1244,15 +1229,15 @@ namespace quickjs::detail
record->artifact = restored;
}

*bytecode_out = record.release();
*bytecode_out = reinterpret_cast<unofficial_napi_bytecode>(record.release());
return napi_ok;
}

napi_status napi_contextify__::bytecode_serialize(void *bytecode, napi_value *buffer_out)
napi_status napi_contextify__::bytecode_serialize(unofficial_napi_bytecode bytecode, napi_value *buffer_out)
{
if (!napi_util__::check_env(env_) || bytecode == nullptr || buffer_out == nullptr)
return napi_invalid_arg;
auto *record = static_cast<napi_bytecode_record__ *>(bytecode);
auto *record = reinterpret_cast<napi_bytecode_record__ *>(bytecode);
std::vector<uint8_t> persisted;
if (!record->bytes.empty())
{
Expand All @@ -1273,11 +1258,11 @@ namespace quickjs::detail
return napi_create_typedarray(env_, napi_uint8_array, persisted.size(), arraybuffer, 0, buffer_out);
}

napi_status napi_contextify__::bytecode_release(void *bytecode)
napi_status napi_contextify__::bytecode_release(unofficial_napi_bytecode bytecode)
{
if (bytecode == nullptr)
return napi_invalid_arg;
auto *record = static_cast<napi_bytecode_record__ *>(bytecode);
auto *record = reinterpret_cast<napi_bytecode_record__ *>(bytecode);
if (record->ctx != nullptr && !JS_IsUndefined(record->artifact))
JS_FreeValue(record->ctx, record->artifact);
delete record;
Expand Down
23 changes: 8 additions & 15 deletions quickjs/src/internal/napi_contextify.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,11 @@ namespace quickjs::detail
napi_contextify__(const napi_contextify__ &) = delete;
napi_contextify__ &operator=(const napi_contextify__ &) = delete;

napi_status get_error_source_positions(napi_value error,
unofficial_napi_error_source_positions *out);
napi_status get_error_metadata(napi_value error,
unofficial_napi_error_metadata_mode mode,
unofficial_napi_error_metadata *out);
napi_status preserve_error_source_message(napi_value error);
napi_status set_source_maps_enabled(bool enabled);
napi_status set_get_source_map_error_source_callback(napi_value callback);
napi_status get_error_source_line_for_stderr(napi_value error,
napi_value *result_out);
napi_status get_error_thrown_at(napi_value error,
napi_value *result_out);
napi_status take_preserved_error_formatting(napi_value error,
napi_value *source_line_out,
napi_value *thrown_at_out);
napi_status configure_source_maps(bool enabled, napi_value callback);

napi_status make_context(napi_value sandbox_or_symbol,
napi_value name,
Expand Down Expand Up @@ -79,7 +72,7 @@ namespace quickjs::detail
napi_value host_defined_option_id,
int32_t line_offset,
int32_t column_offset,
void **bytecode_out,
unofficial_napi_bytecode *bytecode_out,
bool *can_parse_as_module_out);
napi_status bytecode_deserialize(const uint8_t *bytes,
size_t byte_length,
Expand All @@ -88,10 +81,10 @@ namespace quickjs::detail
int32_t shape,
napi_value params_or_undefined,
napi_value host_defined_option_id,
void **bytecode_out,
unofficial_napi_bytecode *bytecode_out,
bool *rejected_out);
napi_status bytecode_serialize(void *bytecode, napi_value *buffer_out);
napi_status bytecode_release(void *bytecode);
napi_status bytecode_serialize(unofficial_napi_bytecode bytecode, napi_value *buffer_out);
napi_status bytecode_release(unofficial_napi_bytecode bytecode);

private:
bool compile_trace_enabled() const;
Expand Down
Loading
Loading