aboutsummaryrefslogtreecommitdiffstats
path: root/src/emacs-module.c
diff options
context:
space:
mode:
authorPhilipp Stephani2019-11-02 10:54:57 +0100
committerPhilipp Stephani2019-12-04 21:17:10 +0100
commit096be9c4541329af259273fe604dc4f8669fbd8a (patch)
tree9a93e99ec78c598bfa42b73c30e7ff349a3bb489 /src/emacs-module.c
parent0ca32d1270bd5d494e365f3525fa65ac423f6658 (diff)
downloademacs-096be9c4541329af259273fe604dc4f8669fbd8a.tar.gz
emacs-096be9c4541329af259273fe604dc4f8669fbd8a.zip
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 'src/emacs-module.c')
-rw-r--r--src/emacs-module.c144
1 files changed, 130 insertions, 14 deletions
diff --git a/src/emacs-module.c b/src/emacs-module.c
index 4b991a1c744..e5c88fd814a 100644
--- a/src/emacs-module.c
+++ b/src/emacs-module.c
@@ -70,12 +70,6 @@ To add a new module function, proceed as follows:
70 70
71#include <config.h> 71#include <config.h>
72 72
73#ifndef HAVE_GMP
74#include "mini-gmp.h"
75#define EMACS_MODULE_HAVE_MPZ_T
76#endif
77
78#define EMACS_MODULE_GMP
79#include "emacs-module.h" 73#include "emacs-module.h"
80 74
81#include <stdarg.h> 75#include <stdarg.h>
@@ -772,21 +766,143 @@ module_make_time (emacs_env *env, struct timespec time)
772 return lisp_to_value (env, timespec_to_lisp (time)); 766 return lisp_to_value (env, timespec_to_lisp (time));
773} 767}
774 768
775static void 769/*
776module_extract_big_integer (emacs_env *env, emacs_value value, 770Big integer support.
777 struct emacs_mpz *result) 771
772There are two possible ways to support big integers in the module API
773that have been discussed:
774
7751. Exposing GMP numbers (mpz_t) directly in the API.
776
7772. Isolating the API from GMP by converting to/from a custom
778 sign-magnitude representation.
779
780Approach (1) has the advantage of being faster (no import/export
781required) and requiring less code in Emacs and in modules that would
782use GMP anyway. However, (1) also couples big integer support
783directly to the current implementation in Emacs (GMP). Also (1)
784requires each module author to ensure that their module is linked to
785the same GMP library as Emacs itself; in particular, module authors
786can't link GMP statically. (1) also requires conditional compilation
787and workarounds to ensure the module interface still works if GMP
788isn't available while including emacs-module.h. It also means that
789modules written in languages such as Go and Java that support big
790integers without GMP now have to carry an otherwise unnecessary GMP
791dependency. Approach (2), on the other hand, neatly decouples the
792module interface from the GMP-based implementation. It's not
793significantly more complex than (1) either: the additional code is
794mostly straightforward. Over all, the benefits of (2) over (1) are
795large enough to prefer it here.
796
797We use a simple sign-magnitude representation for the big integers.
798For the magnitude we pick an array of an unsigned integer type similar
799to mp_limb_t instead of e.g. unsigned char. This matches in most
800cases the representation of a GMP limb. In such cases GMP picks an
801optimized algorithm for mpz_import and mpz_export that boils down to a
802single memcpy to convert the magnitude. This way we largely avoid the
803import/export overhead on most platforms.
804*/
805
806enum
778{ 807{
779 MODULE_FUNCTION_BEGIN (); 808 /* Documented maximum count of magnitude elements. */
780 Lisp_Object o = value_to_lisp (value); 809 module_bignum_count_max = min (SIZE_MAX, PTRDIFF_MAX) / sizeof (emacs_limb_t)
810};
811
812static bool
813module_extract_big_integer (emacs_env *env, emacs_value arg, int *sign,
814 ptrdiff_t *count, emacs_limb_t *magnitude)
815{
816 MODULE_FUNCTION_BEGIN (false);
817 Lisp_Object o = value_to_lisp (arg);
781 CHECK_INTEGER (o); 818 CHECK_INTEGER (o);
782 mpz_set_integer (result->value, o); 819 int dummy;
820 if (sign == NULL)
821 sign = &dummy;
822 /* See
823 https://gmplib.org/manual/Integer-Import-and-Export.html#index-Export. */
824 enum
825 {
826 order = -1,
827 size = sizeof *magnitude,
828 bits = size * CHAR_BIT,
829 endian = 0,
830 nails = 0,
831 numb = 8 * size - nails
832 };
833 if (FIXNUMP (o))
834 {
835 EMACS_INT x = XFIXNUM (o);
836 *sign = (0 < x) - (x < 0);
837 if (x == 0 || count == NULL)
838 return true;
839 /* As a simplification we don't check how many array elements
840 are exactly required, but use a reasonable static upper
841 bound. For most architectures exactly one element should
842 suffice. */
843 EMACS_UINT u;
844 enum { required = (sizeof u + size - 1) / size };
845 verify (0 < required && required <= module_bignum_count_max);
846 if (magnitude == NULL)
847 {
848 *count = required;
849 return true;
850 }
851 if (*count < required)
852 {
853 ptrdiff_t actual = *count;
854 *count = required;
855 args_out_of_range_3 (INT_TO_INTEGER (actual),
856 INT_TO_INTEGER (required),
857 INT_TO_INTEGER (module_bignum_count_max));
858 }
859 /* Set u = abs(x). See https://stackoverflow.com/a/17313717. */
860 if (0 < x)
861 u = (EMACS_UINT) x;
862 else
863 u = -(EMACS_UINT) x;
864 verify (required * bits < PTRDIFF_MAX);
865 for (ptrdiff_t i = 0; i < required; ++i)
866 magnitude[i] = (emacs_limb_t) (u >> (i * bits));
867 return true;
868 }
869 const mpz_t *x = xbignum_val (o);
870 *sign = mpz_sgn (*x);
871 if (count == NULL)
872 return true;
873 size_t required_size = (mpz_sizeinbase (*x, 2) + numb - 1) / numb;
874 eassert (required_size <= PTRDIFF_MAX);
875 ptrdiff_t required = (ptrdiff_t) required_size;
876 eassert (required <= module_bignum_count_max);
877 if (magnitude == NULL)
878 {
879 *count = required;
880 return true;
881 }
882 if (*count < required)
883 {
884 ptrdiff_t actual = *count;
885 *count = required;
886 args_out_of_range_3 (INT_TO_INTEGER (actual), INT_TO_INTEGER (required),
887 INT_TO_INTEGER (module_bignum_count_max));
888 }
889 size_t written;
890 mpz_export (magnitude, &written, order, size, endian, nails, *x);
891 eassert (written == required_size);
892 return true;
783} 893}
784 894
785static emacs_value 895static emacs_value
786module_make_big_integer (emacs_env *env, const struct emacs_mpz *value) 896module_make_big_integer (emacs_env *env, int sign,
897 ptrdiff_t count, const unsigned long *magnitude)
787{ 898{
788 MODULE_FUNCTION_BEGIN (NULL); 899 MODULE_FUNCTION_BEGIN (NULL);
789 mpz_set (mpz[0], value->value); 900 if (sign == 0)
901 return lisp_to_value (env, make_fixed_natnum (0));
902 enum { order = -1, size = sizeof *magnitude, endian = 0, nails = 0 };
903 mpz_import (mpz[0], count, order, size, endian, nails, magnitude);
904 if (sign < 0)
905 mpz_neg (mpz[0], mpz[0]);
790 return lisp_to_value (env, make_integer_mpz ()); 906 return lisp_to_value (env, make_integer_mpz ());
791} 907}
792 908