summaryrefslogtreecommitdiff
path: root/test/data
diff options
context:
space:
mode:
authorPhilipp Stephani <phst@google.com>2019-11-02 10:54:57 +0100
committerPhilipp Stephani <phst@google.com>2019-12-04 21:17:10 +0100
commit096be9c4541329af259273fe604dc4f8669fbd8a (patch)
tree9a93e99ec78c598bfa42b73c30e7ff349a3bb489 /test/data
parent0ca32d1270bd5d494e365f3525fa65ac423f6658 (diff)
downloademacs-096be9c4541329af259273fe604dc4f8669fbd8a.tar.gz
Change module interface to no longer use GMP objects directly.
As described in the new comment added to emacs-module.c, using GMP directly in the module interface has significant downsides: it couples the module interface directly to the implementation and requires module authors to link their module against the same GMP library as Emacs itself, which is often difficult and an unnecessary burden. By picking a representation for the magnitude that often matches the one used by GMP, we can avoid overhead when converting from and to GMP in most cases. Loading the test module in test/data/emacs-module and evaluating (dotimes (_ 10000) (mod-test-double (* 2 most-negative-fixnum))) under Callgrind shows that on my (GNU/Linux) machine Emacs only spends 10% of the CPU time of mod-test-double in mpz_import and mpz_export combined, even though that function does little else. (By contrast, 30% is spent in allocate_pseudovector.) * src/emacs-module.h.in: Don't check EMACS_MODULE_GMP. Don't include gmp.h. Remove emacs_mpz structure. Instead, define type alias emacs_limb_t and macro EMACS_LIMB_MAX. * src/module-env-27.h: Change interface of extract_big_integer and make_big_integer to take a sign-magnitude representation instead of mpz_t. * src/emacs-module.c: Don't check EMACS_MODULE_GMP or EMACS_MODULE_HAVE_MPZ_T. Add a comment about the chosen implementation. (module_extract_big_integer, module_make_big_integer): Reimplement without using mpz_t in the interface. * doc/lispref/internals.texi (Module Values): Adapt function documentation and example. Stop mentioning GMP and EMACS_MODULE_GMP. * test/data/emacs-module/mod-test.c: Don't define EMACS_MODULE_GMP or EMACS_MODULE_HAVE_MPZ_T. (memory_full, extract_big_integer, make_big_integer): New helper functions, identical to example in the Info documentation. (Fmod_test_nanoseconds, Fmod_test_double): Adapt to new interface.
Diffstat (limited to 'test/data')
-rw-r--r--test/data/emacs-module/mod-test.c111
1 files changed, 97 insertions, 14 deletions
diff --git a/test/data/emacs-module/mod-test.c b/test/data/emacs-module/mod-test.c
index 2891b73c1a0..b579c8a6278 100644
--- a/test/data/emacs-module/mod-test.c
+++ b/test/data/emacs-module/mod-test.c
@@ -33,10 +33,8 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#include <gmp.h>
#else
#include "mini-gmp.h"
-#define EMACS_MODULE_HAVE_MPZ_T
#endif
-#define EMACS_MODULE_GMP
#include <emacs-module.h>
#include "timespec.h"
@@ -66,6 +64,8 @@ int plugin_is_GPL_compatible;
# error "INTPTR_MAX too large"
#endif
+/* Smoke test to verify that EMACS_LIMB_MAX is defined. */
+_Static_assert (0 < EMACS_LIMB_MAX, "EMACS_LIMB_MAX missing or incorrect");
/* Always return symbol 't'. */
static emacs_value
@@ -372,23 +372,106 @@ Fmod_test_add_nanosecond (emacs_env *env, ptrdiff_t nargs, emacs_value *args,
return env->make_time (env, time);
}
+static void
+memory_full (emacs_env *env)
+{
+ const char *message = "Memory exhausted";
+ emacs_value data = env->make_string (env, message, strlen (message));
+ env->non_local_exit_signal (env, env->intern (env, "error"),
+ env->funcall (env, env->intern (env, "list"), 1,
+ &data));
+}
+
+enum
+{
+ max_count = ((SIZE_MAX < PTRDIFF_MAX ? SIZE_MAX : PTRDIFF_MAX)
+ / sizeof (emacs_limb_t))
+};
+
+static bool
+extract_big_integer (emacs_env *env, emacs_value arg, mpz_t result)
+{
+ int sign;
+ ptrdiff_t count;
+ bool success = env->extract_big_integer (env, arg, &sign, &count, NULL);
+ if (!success)
+ return false;
+ if (sign == 0)
+ {
+ mpz_set_ui (result, 0);
+ return true;
+ }
+ enum { order = -1, size = sizeof (unsigned long), endian = 0, nails = 0 };
+ assert (0 < count && count <= max_count);
+ emacs_limb_t *magnitude = malloc (count * size);
+ if (magnitude == NULL)
+ {
+ memory_full (env);
+ return false;
+ }
+ success = env->extract_big_integer (env, arg, NULL, &count, magnitude);
+ assert (success);
+ mpz_import (result, count, order, size, endian, nails, magnitude);
+ free (magnitude);
+ if (sign < 0)
+ mpz_neg (result, result);
+ return true;
+}
+
+static emacs_value
+make_big_integer (emacs_env *env, const mpz_t value)
+{
+ if (mpz_sgn (value) == 0)
+ return env->make_integer (env, 0);
+ /* See
+ https://gmplib.org/manual/Integer-Import-and-Export.html#index-Export. */
+ enum
+ {
+ order = -1,
+ size = sizeof (emacs_limb_t),
+ endian = 0,
+ nails = 0,
+ numb = 8 * size - nails
+ };
+ size_t count = (mpz_sizeinbase (value, 2) + numb - 1) / numb;
+ if (max_count < count)
+ {
+ memory_full (env);
+ return NULL;
+ }
+ emacs_limb_t *magnitude = malloc (count * size);
+ if (magnitude == NULL)
+ {
+ memory_full (env);
+ return NULL;
+ }
+ size_t written;
+ mpz_export (magnitude, &written, order, size, endian, nails, value);
+ assert (written == count);
+ assert (count <= PTRDIFF_MAX);
+ emacs_value result = env->make_big_integer (env, mpz_sgn (value),
+ (ptrdiff_t) count, magnitude);
+ free (magnitude);
+ return result;
+}
+
static emacs_value
Fmod_test_nanoseconds (emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) {
assert (nargs == 1);
struct timespec time = env->extract_time (env, args[0]);
- struct emacs_mpz nanoseconds;
+ mpz_t nanoseconds;
assert (LONG_MIN <= time.tv_sec && time.tv_sec <= LONG_MAX);
- mpz_init_set_si (nanoseconds.value, time.tv_sec);
+ mpz_init_set_si (nanoseconds, time.tv_sec);
#ifdef __MINGW32__
_Static_assert (1000000000 <= ULONG_MAX, "unsupported architecture");
#else
static_assert (1000000000 <= ULONG_MAX, "unsupported architecture");
#endif
- mpz_mul_ui (nanoseconds.value, nanoseconds.value, 1000000000);
+ mpz_mul_ui (nanoseconds, nanoseconds, 1000000000);
assert (0 <= time.tv_nsec && time.tv_nsec <= ULONG_MAX);
- mpz_add_ui (nanoseconds.value, nanoseconds.value, time.tv_nsec);
- emacs_value result = env->make_big_integer (env, &nanoseconds);
- mpz_clear (nanoseconds.value);
+ mpz_add_ui (nanoseconds, nanoseconds, time.tv_nsec);
+ emacs_value result = make_big_integer (env, nanoseconds);
+ mpz_clear (nanoseconds);
return result;
}
@@ -398,12 +481,12 @@ Fmod_test_double (emacs_env *env, ptrdiff_t nargs, emacs_value *args,
{
assert (nargs == 1);
emacs_value arg = args[0];
- struct emacs_mpz value;
- mpz_init (value.value);
- env->extract_big_integer (env, arg, &value);
- mpz_mul_ui (value.value, value.value, 2);
- emacs_value result = env->make_big_integer (env, &value);
- mpz_clear (value.value);
+ mpz_t value;
+ mpz_init (value);
+ extract_big_integer (env, arg, value);
+ mpz_mul_ui (value, value, 2);
+ emacs_value result = make_big_integer (env, value);
+ mpz_clear (value);
return result;
}