Skip to content

Commit

Permalink
feat: segregate nogc APIs from rest via type system (#110)
Browse files Browse the repository at this point in the history
  • Loading branch information
toyobayashi authored Mar 9, 2024
1 parent 0472fe4 commit 526f6c4
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 59 deletions.
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

0 comments on commit 526f6c4

Please sign in to comment.