diff --git a/CMakeLists.txt b/CMakeLists.txt index 495b8a286..bb47fbd7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,9 @@ set(ENABLE_LTO ON CACHE BOOL "Enable LTO build?") set(ENABLE_STATIC_LINK ON CACHE BOOL "Enable static linking?") set(ENABLE_STRIP ON CACHE BOOL "Enable stripping all symbols from release binary?") +# Optional features +set(FEATURE_INIT_FINI OFF CACHE BOOL "Enable init/fini arrays?") + # Option overrides if(JERRY_CMDLINE OR JERRY_CMDLINE_MINIMAL OR UNITTESTS OR DOCTESTS) set(JERRY_PORT_DEFAULT ON) @@ -106,6 +109,7 @@ message(STATUS "JERRY_LIBC " ${JERRY_LIBC} ${JERRY_LIBC_MESSAGE}) message(STATUS "JERRY_LIBM " ${JERRY_LIBM} ${JERRY_LIBM_MESSAGE}) message(STATUS "UNITTESTS " ${UNITTESTS}) message(STATUS "DOCTESTS " ${DOCTESTS}) +message(STATUS "FEATURE_INIT_FINI " ${FEATURE_INIT_FINI}) # Setup directories # Project binary dir diff --git a/docs/12.EXT-REFERENCE-MODULE.md b/docs/12.EXT-REFERENCE-MODULE.md index 5bc7e678b..96ed56982 100644 --- a/docs/12.EXT-REFERENCE-MODULE.md +++ b/docs/12.EXT-REFERENCE-MODULE.md @@ -13,14 +13,10 @@ type of module is requested. Additionally, this extension provides a means of easily defining so-called "native" JerryScript modules which can be resolved using the JerryScript native module resolver `jerryx_module_native_resolver()`, which can be passed to -`jerryx_module_resolve()`. Note, however, that native JerryScript modules are only supported and -`jerryx_module_native_resolver()` is only compiled in if compiler support for `__attribute__` extensions is present. In -effect this means that native JerryScript modules are available only when this extension is built with GCC or -LLVM/clang. In the absence of such support, you may construct alternative module systems and provide your own resolver -to `jerryx_module_resolve()`. - -`jerryscript-ext/module.h` defines the preprocessor directive `JERRYX_NATIVE_MODULES_SUPPORTED` only if support for -native JerryScript modules is available. +`jerryx_module_resolve()`. Native modules are registered during application startup and by calling `dlopen()` by means +of library constructors, support for which can be turned on using the `FEATURE_INIT_FINI` build flag. In the absence of +such a flag, the module registration and unregistration functions are exposed as global symbols which can be called +explicitly. ## jerryx_module_resolve @@ -52,9 +48,8 @@ jerryx_module_resolve (const jerry_char_t *name, **Summary** -The resolver for JerryScript modules. A pointer to this function can be passed in the second parameter to -`jerryx_module_resolve` to search for the module among the JerryScript modules built into the binary. This function is -available only if the preprocessor directive `JERRYX_NATIVE_MODULES_SUPPORTED` is defined. +The resolver for native JerryScript modules. A pointer to this function can be passed in the second parameter to +`jerryx_module_resolve` to search for the module among the native JerryScript modules loaded so far. **Prototype** @@ -74,8 +69,7 @@ jerryx_module_native_resolver (const jerry_char_t *name, **Summary** -Function pointer type for a function that will create an instance of a native module. This type is only defined if the -preprocessor directive `JERRYX_NATIVE_MODULES_SUPPORTED` is defined. +Function pointer type for a function that will create an instance of a native module. **Prototype** @@ -136,12 +130,15 @@ We can now load JavaScript files: ```c static const jerryx_module_resolver_t resolvers[] = { - /* Consult the JerryScript module resolver first, in case the requested module is a compiled-in JerryScript module. */ + /* + * Consult the JerryScript native module resolver first, in case the requested module is a native JerryScript + * module. + */ jerryx_module_native_resolver, /* - * If the requested module is not a JerryScript module, assume it is a JavaScript file on disk and use the above- - * defined JavaScript file loader to load it. + * If the requested module is not a native JerryScript module, assume it is a JavaScript file on disk and use the + * above-defined JavaScript file loader to load it. */ load_and_evaluate_js_file }; @@ -154,9 +151,11 @@ jerry_value_t js_module = jerryx_module_resolve (requested_module, resolvers, 2) **Summary** -Helper macro to define a JerryScript module. Currently stores the name of the module and its initializer in an -executable linker section. This macro is available only if the preprocessor directive `JERRYX_NATIVE_MODULES_SUPPORTED` -is defined. +Helper macro to define a native JerryScript module. Currently declares a global static structure of type +`jerryx_native_module_t` and a constructor/destructor pair that calls `jerryx_native_module_register()` resp. +`jerryx_native_module_unregister()`. If the extension is built without the FEATURE_INIT_FINI flag, indicating that +support for library constructors and destructors is absent, the constructor and destructor are declared as global +symbols so that they may be called explicitly from within the application. **Note**: The helper macro must appear at the bottom of a source file, and no semicolon must follow it. @@ -165,7 +164,8 @@ is defined. #define JERRYX_NATIVE_MODULE(module_name, on_resolve_cb) ``` -- `module_name` - the name of the module without quotes +- `module_name` - the name of the module without quotes. This value is used as the prefix for the registration and unregistration funtions. For example, when `module_name` is `example_module`, this results in the declaration of two functions `example_module_register()` and `example_module_unregister()`. These functions are declared global if support for library constructors/destructors is absent, allowing you to call them from other parts of the code by +first forward-declaring them. - `on_resolve_cb` - the function of type `jerryx_native_module_on_resolve_t` that will be called when the module needs to be loaded. @@ -184,3 +184,38 @@ my_module_on_resolve (void) /* Note that there is no semicolon at the end of the next line. This is how it must be. */ JERRYX_NATIVE_MODULE (my_module, my_module_on_resolve) ``` + +**Example Usage When Library Constructors Are Unavailable** + +```c +#include "jerryscript.h" +#include "jerryscript-ext/module.h" + +/** + * Forward-declare the module registration and unregistration function. + */ +extern void my_module_register (void); +extern void my_module_unregister (void); +int +main (int argc, char **argv) +{ + jerryx_module_resolver_t resolvers[] = + { + jerryx_native_module_resolver + }; + + /* This plays the role of the library constructor. */ + my_module_register (); + + jerry_init (JERRY_INIT_EMPTY); + ... + jerry_value_t my_module = jerryx_module_resolve ("my_module", resolvers, 1); + ... + jerry_cleanup (); + + /* This plays the role of the library destructor */ + my_module_unregister(); + + return 0; +} +``` diff --git a/jerry-ext/CMakeLists.txt b/jerry-ext/CMakeLists.txt index ade315349..c90c547a6 100644 --- a/jerry-ext/CMakeLists.txt +++ b/jerry-ext/CMakeLists.txt @@ -19,6 +19,10 @@ project (${JERRY_EXT_NAME} C) # Include directories set(INCLUDE_EXT "${CMAKE_CURRENT_SOURCE_DIR}/include") +if(FEATURE_INIT_FINI) + set(DEFINES_EXT ${DEFINES_EXT} ENABLE_INIT_FINI) +endif() + # Source directories file(GLOB SOURCE_EXT_ARG arg/*.c) file(GLOB SOURCE_EXT_MODULE module/*.c) @@ -32,7 +36,7 @@ set(SOURCE_EXT add_library(${JERRY_EXT_NAME} STATIC ${SOURCE_EXT}) target_include_directories(${JERRY_EXT_NAME} PUBLIC ${INCLUDE_EXT}) - +target_compile_definitions(${JERRY_EXT_NAME} PUBLIC ${DEFINES_EXT}) target_link_libraries(${JERRY_EXT_NAME} jerry-core) install(TARGETS ${JERRY_EXT_NAME} DESTINATION lib) diff --git a/jerry-ext/include/jerryscript-ext/module.h b/jerry-ext/include/jerryscript-ext/module.h index b123489f7..778a3bf94 100644 --- a/jerry-ext/include/jerryscript-ext/module.h +++ b/jerry-ext/include/jerryscript-ext/module.h @@ -18,13 +18,6 @@ #include "jerryscript.h" -#ifdef __GNUC__ -#define JERRYX_NATIVE_MODULES_SUPPORTED -#endif /* __GNUC__ */ - -#ifdef JERRYX_NATIVE_MODULES_SUPPORTED -#include "jerryscript-ext/section.impl.h" - /** * Declare the signature for the module initialization function. */ @@ -34,23 +27,69 @@ typedef jerry_value_t (*jerryx_native_module_on_resolve_t) (void); * Declare the structure used to define a module. One should only make use of this structure via the * JERRYX_NATIVE_MODULE macro declared below. */ -typedef struct +typedef struct jerryx_native_module_t { - jerry_char_t *name; /**< name of the module */ - jerryx_native_module_on_resolve_t on_resolve; /**< function that returns a new instance of the module */ + const jerry_char_t *name_p; /**< name of the module */ + const jerryx_native_module_on_resolve_t on_resolve; /**< function that returns a new instance of the module */ + struct jerryx_native_module_t *next_p; /**< pointer to next module in the list */ } jerryx_native_module_t; /** - * Declare a helper macro that expands to the declaration of a variable of type jerryx_native_module_t placed into the - * specially-named linker section "jerryx_modules" where the JerryScript module resolver - * jerryx_module_native_resolver () will look for it. + * Declare the constructor and destructor attributes. These evaluate to nothing if this extension is built without + * library constructor/destructor support. */ -#define JERRYX_NATIVE_MODULE(module_name, on_resolve_cb) \ - static const jerryx_native_module_t _module JERRYX_SECTION_ATTRIBUTE(jerryx_modules) = \ - { \ - .name = ((jerry_char_t *) #module_name), \ - .on_resolve = (on_resolve_cb) \ - }; +#ifdef ENABLE_INIT_FINI +#define JERRYX_MODULE_CONSTRUCTOR_ATTRIBUTE __attribute__((constructor)) +#define JERRYX_MODULE_DESTRUCTOR_ATTRIBUTE __attribute__((destructor)) +#define JERRYX_MODULE_REGISTRATION_QUALIFIER static +#else /* !ENABLE_INIT_FINI */ +#define JERRYX_MODULE_CONSTRUCTOR_ATTRIBUTE +#define JERRYX_MODULE_DESTRUCTOR_ATTRIBUTE +#define JERRYX_MODULE_REGISTRATION_QUALIFIER +#endif /* ENABLE_INIT_FINI */ + +/** + * Having two levels of macros allows strings to be used unquoted. + */ +#define JERRYX_NATIVE_MODULE(module_name, on_resolve_cb) \ + JERRYX_NATIVE_MODULE_IMPLEM(module_name, on_resolve_cb) + +#define JERRYX_NATIVE_MODULE_IMPLEM(module_name, on_resolve_cb) \ + static jerryx_native_module_t _ ## module_name ## _definition = \ + { \ + .name_p = (jerry_char_t *) #module_name, \ + .on_resolve = (on_resolve_cb), \ + .next_p = NULL \ + }; \ + \ + JERRYX_MODULE_REGISTRATION_QUALIFIER void \ + module_name ## _register (void) JERRYX_MODULE_CONSTRUCTOR_ATTRIBUTE; \ + JERRYX_MODULE_REGISTRATION_QUALIFIER void \ + module_name ## _register (void) \ + { \ + jerryx_native_module_register(&_##module_name##_definition); \ + } \ + \ + JERRYX_MODULE_REGISTRATION_QUALIFIER void \ + module_name ## _unregister (void) \ + JERRYX_MODULE_DESTRUCTOR_ATTRIBUTE; \ + JERRYX_MODULE_REGISTRATION_QUALIFIER void \ + module_name ## _unregister (void) \ + { \ + jerryx_native_module_unregister(&_##module_name##_definition); \ + } + +/** + * Register a native module. This makes it available for loading via jerryx_module_resolve, when + * jerryx_module_native_resolver is passed in as a possible resolver. + */ +void jerryx_native_module_register (jerryx_native_module_t *module_p); + +/** + * Unregister a native module. This removes the module from the list of available native modules, meaning that + * subsequent calls to jerryx_module_resolve with jerryx_module_native_resolver will not be able to find it. + */ +void jerryx_native_module_unregister (jerryx_native_module_t *module_p); /** * Declare the JerryScript module resolver so that it may be added to an array of jerryx_module_resolver_t items and @@ -58,8 +97,6 @@ typedef struct */ bool jerryx_module_native_resolver (const jerry_char_t *name, jerry_value_t *result); -#endif /* JERRYX_NATIVE_MODULES_SUPPORTED */ - /** * Declare the function pointer type for module resolvers. */ diff --git a/jerry-ext/include/jerryscript-ext/section.impl.h b/jerry-ext/include/jerryscript-ext/section.impl.h deleted file mode 100644 index 530d021c2..000000000 --- a/jerry-ext/include/jerryscript-ext/section.impl.h +++ /dev/null @@ -1,56 +0,0 @@ -/* Copyright JS Foundation and other contributors, http://js.foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef JERRYX_SECTION_IMPL_H -#define JERRYX_SECTION_IMPL_H - -/** - * Define the name of a section - * - * @param name the name of the section without quotes - */ -#ifdef __MACH__ -#define JERRYX_SECTION_NAME(name) "__DATA," #name -#else /* !__MACH__ */ -#define JERRYX_SECTION_NAME(name) #name -#endif /* __MACH__ */ - -/** - * Expands to the proper __attribute__(()) qualifier for appending a variable to a section. - */ -#define JERRYX_SECTION_ATTRIBUTE(name) \ - __attribute__ ((used, section (JERRYX_SECTION_NAME (name)), aligned (sizeof (void *)))) - -/** - * Declare references to a section that contains an array of items. - * - * @param name the name of the section (without quotes) - * @param type the type of the elements stored in the array - * - * This macro declares two extern const variables such that their type is an array of @p type and their names are - * constructed by prefixing @p name with "__start_" and "__stop_". They evaluate to the starting and ending address - * of the section @p name. - */ -#ifdef __MACH__ -#define JERRYX_SECTION_DECLARE(name, type) \ - extern const type __start_ ## name[] __asm("section$start$__DATA$" #name); \ - extern const type __stop_ ## name[] __asm("section$end$__DATA$" #name); -#else /* !__MACH__ */ -#define JERRYX_SECTION_DECLARE(name, type) \ - extern const type __start_ ## name[] __attribute__((weak)); \ - extern const type __stop_ ## name[] __attribute__((weak)); -#endif /* __MACH__ */ - -#endif /* !JERRYX_SECTION_IMPL_H */ diff --git a/jerry-ext/module/module.c b/jerry-ext/module/module.c index 6cf8873ce..406e9c7ca 100644 --- a/jerry-ext/module/module.c +++ b/jerry-ext/module/module.c @@ -72,9 +72,36 @@ static const jerry_context_data_manager_t jerryx_module_manager = }; /** - * Declare the linker section where module definitions are stored. + * Global static entry point to the linked list of available modules. */ -JERRYX_SECTION_DECLARE (jerryx_modules, jerryx_native_module_t) +static jerryx_native_module_t *first_module_p = NULL; + +void jerryx_native_module_register (jerryx_native_module_t *module_p) +{ + module_p->next_p = first_module_p; + first_module_p = module_p; +} /* jerryx_native_module_register */ + +void jerryx_native_module_unregister (jerryx_native_module_t *module_p) +{ + jerryx_native_module_t *parent_p = NULL, *iter_p = NULL; + + for (iter_p = first_module_p; iter_p != NULL; parent_p = iter_p, iter_p = iter_p->next_p) + { + if (iter_p == module_p) + { + if (parent_p) + { + parent_p->next_p = module_p->next_p; + } + else + { + first_module_p = module_p->next_p; + } + module_p->next_p = NULL; + } + } +} /* jerryx_native_module_unregister */ /** * Attempt to retrieve a module by name from a cache, and return false if not found. @@ -133,7 +160,6 @@ jerryx_module_add_to_cache (jerry_value_t cache, /**< cache to which to add the return ret; } /* jerryx_module_add_to_cache */ -#ifdef JERRYX_NATIVE_MODULES_SUPPORTED static const jerry_char_t *on_resolve_absent = (jerry_char_t *) "Module on_resolve () must not be NULL"; /** @@ -145,15 +171,12 @@ bool jerryx_module_native_resolver (const jerry_char_t *name, /**< name of the module */ jerry_value_t *result) /**< [out] where to put the resulting module instance */ { - int index; const jerryx_native_module_t *module_p = NULL; /* Look for the module by its name in the list of module definitions. */ - for (index = 0, module_p = &__start_jerryx_modules[0]; - &__start_jerryx_modules[index] < __stop_jerryx_modules; - index++, module_p = &__start_jerryx_modules[index]) + for (module_p = first_module_p; module_p != NULL; module_p = module_p->next_p) { - if (module_p->name != NULL && !strcmp ((char *) module_p->name, (char *) name)) + if (module_p->name_p != NULL && !strcmp ((char *) module_p->name_p, (char *) name)) { /* If we find the module by its name we load it and cache it if it has an on_resolve () and complain otherwise. */ (*result) = ((module_p->on_resolve) ? module_p->on_resolve () @@ -164,7 +187,6 @@ jerryx_module_native_resolver (const jerry_char_t *name, /**< name of the module return false; } /* jerryx_module_native_resolver */ -#endif /* JERRYX_NATIVE_MODULES_SUPPORTED */ /** * Resolve a single module using the module resolvers available in the section declared above and load it into the diff --git a/jerry-libc/CMakeLists.txt b/jerry-libc/CMakeLists.txt index b594e74ec..300a91cc3 100644 --- a/jerry-libc/CMakeLists.txt +++ b/jerry-libc/CMakeLists.txt @@ -16,12 +16,6 @@ cmake_minimum_required (VERSION 2.8.12) set(JERRY_LIBC_NAME jerry-libc) project (${JERRY_LIBC_NAME} C ASM) -# Optional features -set(FEATURE_INIT_FINI OFF CACHE BOOL "Enable init/fini arrays?") - -# Status messages -message(STATUS "FEATURE_INIT_FINI " ${FEATURE_INIT_FINI}) - # Checks the optional features # Enable init/fini arrays if(FEATURE_INIT_FINI) diff --git a/tests/unit-ext/module/CMakeLists.txt b/tests/unit-ext/module/CMakeLists.txt index 233b71274..7aa1bf3b7 100644 --- a/tests/unit-ext/module/CMakeLists.txt +++ b/tests/unit-ext/module/CMakeLists.txt @@ -18,6 +18,10 @@ project (${JERRYX_MODULE_UNITTEST_NAME} C) file(GLOB JERRYX_MODULE_UNIT_TEST_SOURCES *.c) +if (FEATURE_INIT_FINI) + set(DEFINES_JERRYX_MODULE_UNITTEST ${DEFINES_JERRYX_MODULE_UNITTEST} ENABLE_INIT_FINI) +endif() + add_executable(${JERRYX_MODULE_UNITTEST_NAME} ${JERRYX_MODULE_UNIT_TEST_SOURCES}) set_property(TARGET ${JERRYX_MODULE_UNITTEST_NAME} PROPERTY LINK_FLAGS "${LINKER_FLAGS_COMMON}") target_link_libraries(${JERRYX_MODULE_UNITTEST_NAME} jerry-ext jerry-core jerry-port-default-minimal) diff --git a/tests/unit-ext/module/jerry-module-test.c b/tests/unit-ext/module/jerry-module-test.c index e08a5ce81..492ad086c 100644 --- a/tests/unit-ext/module/jerry-module-test.c +++ b/tests/unit-ext/module/jerry-module-test.c @@ -19,8 +19,6 @@ #include "test-common.h" #include "jerryscript-ext/module.h" -#ifdef JERRYX_NATIVE_MODULES_SUPPORTED - /* Load a module. */ const char eval_string1[] = "require ('my_custom_module');"; @@ -138,6 +136,11 @@ eval_one (const char *the_string, double expected_result) jerry_release_value (js_eval_result); } /* eval_one */ +#ifndef ENABLE_INIT_FINI +extern void my_broken_module_register (void); +extern void my_custom_module_register (void); +#endif /* !ENABLE_INIT_FINI */ + int main (int argc, char **argv) { @@ -145,6 +148,11 @@ main (int argc, char **argv) (void) argv; jerry_value_t js_global = 0, js_function = 0, js_property_name = 0; +#ifndef ENABLE_INIT_FINI + my_broken_module_register (); + my_custom_module_register (); +#endif /* !ENABLE_INIT_FINI */ + jerry_init (JERRY_INIT_EMPTY); js_global = jerry_get_global_object (); @@ -164,5 +172,3 @@ main (int argc, char **argv) jerry_cleanup (); } /* main */ - -#endif /* JERRYX_NATIVE_MODULES_SUPPORTED */ diff --git a/tests/unit-ext/module/my-broken-module.c b/tests/unit-ext/module/my-broken-module.c index 59ff32377..39d4112a0 100644 --- a/tests/unit-ext/module/my-broken-module.c +++ b/tests/unit-ext/module/my-broken-module.c @@ -16,9 +16,9 @@ #include "jerryscript.h" #include "jerryscript-ext/module.h" -#ifdef JERRYX_NATIVE_MODULES_SUPPORTED +#define MODULE_NAME my_broken_module + /* * A broken module to test that the loader complains about the absence of on_resolve () */ -JERRYX_NATIVE_MODULE (my_broken_module, NULL) -#endif /* JERRYX_NATIVE_MODULES_SUPPORTED */ +JERRYX_NATIVE_MODULE (MODULE_NAME, NULL) diff --git a/tests/unit-ext/module/my-custom-module.c b/tests/unit-ext/module/my-custom-module.c index 178b7db2c..541e971d0 100644 --- a/tests/unit-ext/module/my-custom-module.c +++ b/tests/unit-ext/module/my-custom-module.c @@ -16,7 +16,7 @@ #include "jerryscript.h" #include "jerryscript-ext/module.h" -#ifdef JERRYX_NATIVE_MODULES_SUPPORTED +#define MODULE_NAME my_custom_module static jerry_value_t my_custom_module_on_resolve (void) @@ -24,6 +24,4 @@ my_custom_module_on_resolve (void) return jerry_create_number (42); } /* my_custom_module_on_resolve */ -JERRYX_NATIVE_MODULE (my_custom_module, my_custom_module_on_resolve) - -#endif /* JERRYX_NATIVE_MODULES_SUPPORTED */ +JERRYX_NATIVE_MODULE (MODULE_NAME, my_custom_module_on_resolve)