module: Re-implement using library constructors/destructors (#2018)

By using constructors/destructors we unify the case of static linking with
that of dynamic linking, and we reuse the build flag FEATURE_INIT_FINI. Using
constructors/destructors also allows us to cover the case where library
constructor/destructor functionality is unavailable, because we can expose the
module registration/unregistration functions as global symbols, to be called
explicitly from within the application.

Fixes https://github.com/jerryscript-project/jerryscript/issues/1952

JerryScript-DCO-1.0-Signed-off-by: Gabriel Schulhof gabriel.schulhof@intel.com
This commit is contained in:
Gabriel "_|Nix|_" Schulhof 2017-09-22 13:35:38 +03:00 committed by Zoltan Herczeg
parent 8d916a44f1
commit 81952f3cd0
11 changed files with 172 additions and 124 deletions

View File

@ -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

View File

@ -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;
}
```

View File

@ -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)

View File

@ -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.
*/

View File

@ -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 */

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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 */

View File

@ -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)

View File

@ -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)