Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: segregate nogc APIs from rest via type system #110

Merged
merged 2 commits into from
Mar 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
44 changes: 24 additions & 20 deletions packages/emnapi/include/node/js_native_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@

EXTERN_C_START

NAPI_EXTERN napi_status NAPI_CDECL
napi_get_last_error_info(napi_env env, const napi_extended_error_info** result);
NAPI_EXTERN napi_status NAPI_CDECL napi_get_last_error_info(
node_api_nogc_env env, const napi_extended_error_info** result);

// Getters for defined singletons
NAPI_EXTERN napi_status NAPI_CDECL napi_get_undefined(napi_env env,
Expand Down Expand Up @@ -104,15 +104,15 @@ NAPI_EXTERN napi_status NAPI_CDECL
node_api_create_external_string_latin1(napi_env env,
char* str,
size_t length,
napi_finalize finalize_callback,
node_api_nogc_finalize finalize_callback,
void* finalize_hint,
napi_value* result,
bool* copied);
NAPI_EXTERN napi_status NAPI_CDECL
node_api_create_external_string_utf16(napi_env env,
char16_t* str,
size_t length,
napi_finalize finalize_callback,
node_api_nogc_finalize finalize_callback,
void* finalize_hint,
napi_value* result,
bool* copied);
Expand Down Expand Up @@ -303,7 +303,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_instanceof(napi_env env,

// Gets all callback info in a single call. (Ugly, but faster.)
NAPI_EXTERN napi_status NAPI_CDECL napi_get_cb_info(
napi_env env, // [in] NAPI environment handle
napi_env env, // [in] Node-API environment handle
napi_callback_info cbinfo, // [in] Opaque callback-info handle
size_t* argc, // [in-out] Specifies the size of the provided argv array
// and receives the actual count of args.
Expand All @@ -327,7 +327,7 @@ napi_define_class(napi_env env,
NAPI_EXTERN napi_status NAPI_CDECL napi_wrap(napi_env env,
napi_value js_object,
void* native_object,
napi_finalize finalize_cb,
node_api_nogc_finalize finalize_cb,
void* finalize_hint,
napi_ref* result);
NAPI_EXTERN napi_status NAPI_CDECL napi_unwrap(napi_env env,
Expand All @@ -339,7 +339,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_remove_wrap(napi_env env,
NAPI_EXTERN napi_status NAPI_CDECL
napi_create_external(napi_env env,
void* data,
napi_finalize finalize_cb,
node_api_nogc_finalize finalize_cb,
void* finalize_hint,
napi_value* result);
NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_external(napi_env env,
Expand Down Expand Up @@ -438,7 +438,7 @@ NAPI_EXTERN napi_status NAPI_CDECL
napi_create_external_arraybuffer(napi_env env,
void* external_data,
size_t byte_length,
napi_finalize finalize_cb,
node_api_nogc_finalize finalize_cb,
void* finalize_hint,
napi_value* result);
#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
Expand Down Expand Up @@ -480,7 +480,7 @@ napi_get_dataview_info(napi_env env,
size_t* byte_offset);

// version management
NAPI_EXTERN napi_status NAPI_CDECL napi_get_version(napi_env env,
NAPI_EXTERN napi_status NAPI_CDECL napi_get_version(node_api_nogc_env env,
uint32_t* result);

// Promises
Expand All @@ -504,7 +504,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_run_script(napi_env env,

// Memory management
NAPI_EXTERN napi_status NAPI_CDECL napi_adjust_external_memory(
napi_env env, int64_t change_in_bytes, int64_t* adjusted_value);
node_api_nogc_env env, int64_t change_in_bytes, int64_t* adjusted_value);

#if NAPI_VERSION >= 5

Expand All @@ -522,20 +522,21 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_get_date_value(napi_env env,
double* result);

// Add finalizer for pointer
NAPI_EXTERN napi_status NAPI_CDECL napi_add_finalizer(napi_env env,
napi_value js_object,
void* finalize_data,
napi_finalize finalize_cb,
void* finalize_hint,
napi_ref* result);
NAPI_EXTERN napi_status NAPI_CDECL
napi_add_finalizer(napi_env env,
napi_value js_object,
void* finalize_data,
node_api_nogc_finalize finalize_cb,
void* finalize_hint,
napi_ref* result);

#endif // NAPI_VERSION >= 5

#ifdef NAPI_EXPERIMENTAL
#define NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER

NAPI_EXTERN napi_status NAPI_CDECL
node_api_post_finalizer(napi_env env,
node_api_post_finalizer(node_api_nogc_env env,
napi_finalize finalize_cb,
void* finalize_data,
void* finalize_hint);
Expand Down Expand Up @@ -579,10 +580,13 @@ napi_get_all_property_names(napi_env env,
napi_value* result);

// Instance data
NAPI_EXTERN napi_status NAPI_CDECL napi_set_instance_data(
napi_env env, void* data, napi_finalize finalize_cb, void* finalize_hint);
NAPI_EXTERN napi_status NAPI_CDECL
napi_set_instance_data(node_api_nogc_env env,
void* data,
napi_finalize finalize_cb,
void* finalize_hint);

NAPI_EXTERN napi_status NAPI_CDECL napi_get_instance_data(napi_env env,
NAPI_EXTERN napi_status NAPI_CDECL napi_get_instance_data(node_api_nogc_env env,
void** data);
#endif // NAPI_VERSION >= 6

Expand Down
39 changes: 39 additions & 0 deletions packages/emnapi/include/node/js_native_api_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,35 @@ typedef uint16_t char16_t;
// JSVM API types are all opaque pointers for ABI stability
// typedef undefined structs instead of void* for compile time type safety
typedef struct napi_env__* napi_env;

// We need to mark APIs which can be called during garbage collection (GC),
// meaning that they do not affect the state of the JS engine, and can
// therefore be called synchronously from a finalizer that itself runs
// synchronously during GC. Such APIs can receive either a `napi_env` or a
// `node_api_nogc_env` as their first parameter, because we should be able to
// also call them during normal, non-garbage-collecting operations, whereas
// APIs that affect the state of the JS engine can only receive a `napi_env` as
// their first parameter, because we must not call them during GC. In lieu of
// inheritance, we use the properties of the const qualifier to accomplish
// this, because both a const and a non-const value can be passed to an API
// expecting a const value, but only a non-const value can be passed to an API
// expecting a non-const value.
//
// In conjunction with appropriate CFLAGS to warn us if we're passing a const
// (nogc) environment into an API that expects a non-const environment, and the
// definition of nogc finalizer function pointer types below, which receive a
// nogc environment as their first parameter, and can thus only call nogc APIs
// (unless the user explicitly casts the environment), we achieve the ability
// to ensure at compile time that we do not call APIs that affect the state of
// the JS engine from a synchronous (nogc) finalizer.
#if !defined(NAPI_EXPERIMENTAL) || \
(defined(NAPI_EXPERIMENTAL) && \
defined(NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT))
typedef struct napi_env__* node_api_nogc_env;
#else
typedef const struct napi_env__* node_api_nogc_env;
#endif

typedef struct napi_value__* napi_value;
typedef struct napi_ref__* napi_ref;
typedef struct napi_handle_scope__* napi_handle_scope;
Expand Down Expand Up @@ -116,6 +145,16 @@ typedef void(NAPI_CDECL* napi_finalize)(napi_env env,
void* finalize_data,
void* finalize_hint);

#if !defined(NAPI_EXPERIMENTAL) || \
(defined(NAPI_EXPERIMENTAL) && \
defined(NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT))
typedef napi_finalize node_api_nogc_finalize;
#else
typedef void(NAPI_CDECL* node_api_nogc_finalize)(node_api_nogc_env env,
void* finalize_data,
void* finalize_hint);
#endif

typedef struct {
// One of utf8name or name should be NULL.
const char* utf8name;
Expand Down
30 changes: 15 additions & 15 deletions packages/emnapi/include/node/node_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ NAPI_EXTERN napi_status NAPI_CDECL
napi_create_external_buffer(napi_env env,
size_t length,
void* data,
napi_finalize finalize_cb,
node_api_nogc_finalize finalize_cb,
void* finalize_hint,
napi_value* result);
#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
Expand Down Expand Up @@ -169,20 +169,20 @@ napi_create_async_work(napi_env env,
napi_async_work* result);
NAPI_EXTERN napi_status NAPI_CDECL napi_delete_async_work(napi_env env,
napi_async_work work);
NAPI_EXTERN napi_status NAPI_CDECL napi_queue_async_work(napi_env env,
NAPI_EXTERN napi_status NAPI_CDECL napi_queue_async_work(node_api_nogc_env env,
napi_async_work work);
NAPI_EXTERN napi_status NAPI_CDECL napi_cancel_async_work(napi_env env,
NAPI_EXTERN napi_status NAPI_CDECL napi_cancel_async_work(node_api_nogc_env env,
napi_async_work work);

// version management
NAPI_EXTERN napi_status NAPI_CDECL
napi_get_node_version(napi_env env, const napi_node_version** version);
napi_get_node_version(node_api_nogc_env env, const napi_node_version** version);

#if NAPI_VERSION >= 2

// Return the current libuv event loop for a given environment
NAPI_EXTERN napi_status NAPI_CDECL
napi_get_uv_event_loop(napi_env env, struct uv_loop_s** loop);
napi_get_uv_event_loop(node_api_nogc_env env, struct uv_loop_s** loop);

#endif // NAPI_VERSION >= 2

Expand All @@ -191,11 +191,11 @@ napi_get_uv_event_loop(napi_env env, struct uv_loop_s** loop);
NAPI_EXTERN napi_status NAPI_CDECL napi_fatal_exception(napi_env env,
napi_value err);

NAPI_EXTERN napi_status NAPI_CDECL
napi_add_env_cleanup_hook(napi_env env, napi_cleanup_hook fun, void* arg);
NAPI_EXTERN napi_status NAPI_CDECL napi_add_env_cleanup_hook(
node_api_nogc_env env, napi_cleanup_hook fun, void* arg);

NAPI_EXTERN napi_status NAPI_CDECL
napi_remove_env_cleanup_hook(napi_env env, napi_cleanup_hook fun, void* arg);
NAPI_EXTERN napi_status NAPI_CDECL napi_remove_env_cleanup_hook(
node_api_nogc_env env, napi_cleanup_hook fun, void* arg);

NAPI_EXTERN napi_status NAPI_CDECL
napi_open_callback_scope(napi_env env,
Expand Down Expand Up @@ -238,18 +238,18 @@ napi_acquire_threadsafe_function(napi_threadsafe_function func);
NAPI_EXTERN napi_status NAPI_CDECL napi_release_threadsafe_function(
napi_threadsafe_function func, napi_threadsafe_function_release_mode mode);

NAPI_EXTERN napi_status NAPI_CDECL
napi_unref_threadsafe_function(napi_env env, napi_threadsafe_function func);
NAPI_EXTERN napi_status NAPI_CDECL napi_unref_threadsafe_function(
node_api_nogc_env env, napi_threadsafe_function func);

NAPI_EXTERN napi_status NAPI_CDECL
napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func);
NAPI_EXTERN napi_status NAPI_CDECL napi_ref_threadsafe_function(
node_api_nogc_env env, napi_threadsafe_function func);

#endif // NAPI_VERSION >= 4

#if NAPI_VERSION >= 8

NAPI_EXTERN napi_status NAPI_CDECL
napi_add_async_cleanup_hook(napi_env env,
napi_add_async_cleanup_hook(node_api_nogc_env env,
napi_async_cleanup_hook hook,
void* arg,
napi_async_cleanup_hook_handle* remove_handle);
Expand All @@ -262,7 +262,7 @@ napi_remove_async_cleanup_hook(napi_async_cleanup_hook_handle remove_handle);
#if NAPI_VERSION >= 9

NAPI_EXTERN napi_status NAPI_CDECL
node_api_get_module_file_name(napi_env env, const char** result);
node_api_get_module_file_name(node_api_nogc_env env, const char** result);

#endif // NAPI_VERSION >= 9

Expand Down
4 changes: 2 additions & 2 deletions packages/emnapi/src/emnapi_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ EXTERN_C_END

EXTERN_C_START

EMNAPI_INTERNAL_EXTERN napi_status napi_set_last_error(napi_env env,
EMNAPI_INTERNAL_EXTERN napi_status napi_set_last_error(node_api_nogc_env env,
napi_status error_code,
uint32_t engine_error_code,
void* engine_reserved);
EMNAPI_INTERNAL_EXTERN napi_status napi_clear_last_error(napi_env env);
EMNAPI_INTERNAL_EXTERN napi_status napi_clear_last_error(node_api_nogc_env env);

#ifdef __EMSCRIPTEN__
#if __EMSCRIPTEN_major__ * 10000 + __EMSCRIPTEN_minor__ * 100 + __EMSCRIPTEN_tiny__ >= 30114 // NOLINT
Expand Down
49 changes: 49 additions & 0 deletions packages/test/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@

#include <js_native_api.h>

#if !defined(__wasm__) || (defined(__EMSCRIPTEN__) || defined(__wasi__))
#include <stdio.h>
#include <stdlib.h> // abort()

#define EPRINT(str) fprintf(stderr, "%s\n", (str))

#else
#include <stddef.h>
void console_error(const char* fmt, const char* str);
#define abort() __builtin_trap()
#define EPRINT(str) console_error("%s", (str))
#endif

// Empty value so that macros here are able to return NULL or void
#define NODE_API_RETVAL_NOTHING // Intentionally blank #define

Expand All @@ -22,6 +35,19 @@
} \
} while (0)

// The nogc version of GET_AND_THROW_LAST_ERROR. We cannot access any
// exceptions and we cannot fail by way of JS exception, so we abort.
#define FATALLY_FAIL_WITH_LAST_ERROR(env) \
do { \
const napi_extended_error_info* error_info; \
napi_get_last_error_info((env), &error_info); \
const char* err_message = error_info->error_message; \
const char* error_message = \
err_message != NULL ? err_message : "empty error message"; \
EPRINT(error_message); \
abort(); \
} while (0)

#define NODE_API_ASSERT_BASE(env, assertion, message, ret_val) \
do { \
if (!(assertion)) { \
Expand All @@ -33,6 +59,15 @@
} \
} while (0)

#define NODE_API_NOGC_ASSERT_BASE(assertion, message, ret_val) \
do { \
if (!(assertion)) { \
EPRINT("assertion (" #assertion ") failed: " message); \
abort(); \
return ret_val; \
} \
} while (0)

// Returns NULL on failed assertion.
// This is meant to be used inside napi_callback methods.
#define NODE_API_ASSERT(env, assertion, message) \
Expand All @@ -43,6 +78,9 @@
#define NODE_API_ASSERT_RETURN_VOID(env, assertion, message) \
NODE_API_ASSERT_BASE(env, assertion, message, NODE_API_RETVAL_NOTHING)

#define NODE_API_NOGC_ASSERT_RETURN_VOID(assertion, message) \
NODE_API_NOGC_ASSERT_BASE(assertion, message, NODE_API_RETVAL_NOTHING)

#define NODE_API_CALL_BASE(env, the_call, ret_val) \
do { \
if ((the_call) != napi_ok) { \
Expand All @@ -51,6 +89,14 @@
} \
} while (0)

#define NODE_API_NOGC_CALL_BASE(env, the_call, ret_val) \
do { \
if ((the_call) != napi_ok) { \
FATALLY_FAIL_WITH_LAST_ERROR((env)); \
return ret_val; \
} \
} while (0)

// Returns NULL if the_call doesn't return napi_ok.
#define NODE_API_CALL(env, the_call) \
NODE_API_CALL_BASE(env, the_call, NULL)
Expand All @@ -59,6 +105,9 @@
#define NODE_API_CALL_RETURN_VOID(env, the_call) \
NODE_API_CALL_BASE(env, the_call, NODE_API_RETVAL_NOTHING)

#define NODE_API_NOGC_CALL_RETURN_VOID(env, the_call) \
NODE_API_NOGC_CALL_BASE(env, the_call, NODE_API_RETVAL_NOTHING)

#define NODE_API_CHECK_STATUS(the_call) \
do { \
napi_status status = (the_call); \
Expand Down
Loading
Loading