* inline function expansion @ 2023-05-07 14:32 Lynn Winebarger 2023-05-07 17:51 ` Basile Starynkevitch 2023-05-07 19:48 ` Philip Kaludercic 0 siblings, 2 replies; 25+ messages in thread From: Lynn Winebarger @ 2023-05-07 14:32 UTC (permalink / raw) To: help-gnu-emacs Hi, Is there a function that will let us see the result of inline function expansion at the source level? I see "byte-compile-preprocess" and "byte-compile-inline-expand" as possibilities, but neither has a docstring. If I use define-inline, I would like to be able to verify that the result is what I expect, or vice versa, that I understand what the result will be well enough to have the correct expectation. Thanks, Lynn ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: inline function expansion 2023-05-07 14:32 inline function expansion Lynn Winebarger @ 2023-05-07 17:51 ` Basile Starynkevitch 2023-05-07 19:48 ` Philip Kaludercic 1 sibling, 0 replies; 25+ messages in thread From: Basile Starynkevitch @ 2023-05-07 17:51 UTC (permalink / raw) To: help-gnu-emacs On 5/7/23 16:32, Lynn Winebarger wrote: > Hi, > > Is there a function that will let us see the result of inline function > expansion at the source level? I see "byte-compile-preprocess" and > "byte-compile-inline-expand" as possibilities, but neither has a > docstring. > > If I use define-inline, I would like to be able to verify that the > result is what I expect, or vice versa, that I understand what the > result will be well enough to have the correct expectation. > > Thanks, > Lynn My understand of the current trunk, updated and compiled daily from git, on Linux x86-64/64 Debian or Linux Ubuntu 12.2.0)GNU Emacs 30.0.50 Development version d5ab8b6f2459 on master branch; build date 2023-05-07 is that such a function cannot exist. On recent GNU emacs, the internal fonctions are JIT compiled (using gcclibjit from GCC 13) by the gccasmjit library, and we get: > rimski.x86_64 ~ 19:38 .0 % ldd /usr/local/bin/emacs-30.0.50-trunk > linux-vdso.so.1 (0x00007ffea99f2000) > libtiff.so.6 => /lib/x86_64-linux-gnu/libtiff.so.6 > (0x00007f84ce7d6000) > libjpeg.so.8 => /lib/x86_64-linux-gnu/libjpeg.so.8 > (0x00007f84ce753000) > libpng16.so.16 => /lib/x86_64-linux-gnu/libpng16.so.16 > (0x00007f84ce71b000) > libgif.so.7 => /lib/x86_64-linux-gnu/libgif.so.7 (0x00007f84ce710000) > libwebpdemux.so.2 => /lib/x86_64-linux-gnu/libwebpdemux.so.2 > (0x00007f84ce70a000) > libwebp.so.7 => /lib/x86_64-linux-gnu/libwebp.so.7 > (0x00007f84cdd91000) > libgtk-3.so.0 => /lib/x86_64-linux-gnu/libgtk-3.so.0 > (0x00007f84cd400000) > libgdk-3.so.0 => /lib/x86_64-linux-gnu/libgdk-3.so.0 > (0x00007f84cdc95000) > libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f84ce6ea000) > libpango-1.0.so.0 => /lib/x86_64-linux-gnu/libpango-1.0.so.0 > (0x00007f84cdc2b000) > libharfbuzz.so.0 => /lib/x86_64-linux-gnu/libharfbuzz.so.0 > (0x00007f84cd318000) > libcairo.so.2 => /lib/x86_64-linux-gnu/libcairo.so.2 > (0x00007f84cd1f1000) > libgdk_pixbuf-2.0.so.0 => > /lib/x86_64-linux-gnu/libgdk_pixbuf-2.0.so.0 (0x00007f84ce6b9000) > libgio-2.0.so.0 => /lib/x86_64-linux-gnu/libgio-2.0.so.0 > (0x00007f84cd01f000) > libgobject-2.0.so.0 => /lib/x86_64-linux-gnu/libgobject-2.0.so.0 > (0x00007f84ccfbe000) > libglib-2.0.so.0 => /lib/x86_64-linux-gnu/libglib-2.0.so.0 > (0x00007f84cce77000) > libSM.so.6 => /lib/x86_64-linux-gnu/libSM.so.6 (0x00007f84ce6ae000) > libICE.so.6 => /lib/x86_64-linux-gnu/libICE.so.6 (0x00007f84cdc0c000) > libX11.so.6 => /lib/x86_64-linux-gnu/libX11.so.6 (0x00007f84ccd39000) > libdbus-1.so.3 => /lib/x86_64-linux-gnu/libdbus-1.so.3 > (0x00007f84ccceb000) > libXrandr.so.2 => /lib/x86_64-linux-gnu/libXrandr.so.2 > (0x00007f84cdbff000) > libXinerama.so.1 => /lib/x86_64-linux-gnu/libXinerama.so.1 > (0x00007f84cdbfa000) > libXfixes.so.3 => /lib/x86_64-linux-gnu/libXfixes.so.3 > (0x00007f84cdbf2000) > libXext.so.6 => /lib/x86_64-linux-gnu/libXext.so.6 > (0x00007f84cdbdb000) > libxml2.so.2 => /lib/x86_64-linux-gnu/libxml2.so.2 > (0x00007f84ccb01000) > libtinfo.so.6 => /lib/x86_64-linux-gnu/libtinfo.so.6 > (0x00007f84ccacf000) > libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 > (0x00007f84ccaa2000) > libfreetype.so.6 => /lib/x86_64-linux-gnu/libfreetype.so.6 > (0x00007f84cc9d8000) > libfontconfig.so.1 => /lib/x86_64-linux-gnu/libfontconfig.so.1 > (0x00007f84cc98a000) > libgnutls.so.30 => /lib/x86_64-linux-gnu/libgnutls.so.30 > (0x00007f84cc78f000) > libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f84cc6a6000) > libjansson.so.4 => /lib/x86_64-linux-gnu/libjansson.so.4 > (0x00007f84cc696000) > libgmp.so.10 => /lib/x86_64-linux-gnu/libgmp.so.10 > (0x00007f84cc613000) > libgccjit.so.0 => /lib/x86_64-linux-gnu/libgccjit.so.0 > (0x00007f84ca400000) > libXi.so.6 => /lib/x86_64-linux-gnu/libXi.so.6 (0x00007f84cc5ff000) > libXcomposite.so.1 => /lib/x86_64-linux-gnu/libXcomposite.so.1 > (0x00007f84cdbd2000) > libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f84ca000000) > libzstd.so.1 => /lib/x86_64-linux-gnu/libzstd.so.1 > (0x00007f84ca34c000) > liblzma.so.5 => /lib/x86_64-linux-gnu/liblzma.so.5 > (0x00007f84ca31a000) > libLerc.so.4 => /lib/x86_64-linux-gnu/libLerc.so.4 > (0x00007f84ca295000) > libjbig.so.0 => /lib/x86_64-linux-gnu/libjbig.so.0 > (0x00007f84ca284000) > libdeflate.so.0 => /lib/x86_64-linux-gnu/libdeflate.so.0 > (0x00007f84ca271000) > /lib64/ld-linux-x86-64.so.2 (0x00007f84ce87c000) > libgmodule-2.0.so.0 => /lib/x86_64-linux-gnu/libgmodule-2.0.so.0 > (0x00007f84ca26a000) > libpangocairo-1.0.so.0 => > /lib/x86_64-linux-gnu/libpangocairo-1.0.so.0 (0x00007f84ca259000) > libpangoft2-1.0.so.0 => /lib/x86_64-linux-gnu/libpangoft2-1.0.so.0 > (0x00007f84ca23f000) > libfribidi.so.0 => /lib/x86_64-linux-gnu/libfribidi.so.0 > (0x00007f84ca222000) > libcairo-gobject.so.2 => > /lib/x86_64-linux-gnu/libcairo-gobject.so.2 (0x00007f84ca217000) > libatk-1.0.so.0 => /lib/x86_64-linux-gnu/libatk-1.0.so.0 > (0x00007f84c9fd8000) > libepoxy.so.0 => /lib/x86_64-linux-gnu/libepoxy.so.0 > (0x00007f84c9ea3000) > libatk-bridge-2.0.so.0 => > /lib/x86_64-linux-gnu/libatk-bridge-2.0.so.0 (0x00007f84c9e67000) > libxkbcommon.so.0 => /lib/x86_64-linux-gnu/libxkbcommon.so.0 > (0x00007f84c9e21000) > libwayland-client.so.0 => > /lib/x86_64-linux-gnu/libwayland-client.so.0 (0x00007f84c9e10000) > libwayland-cursor.so.0 => > /lib/x86_64-linux-gnu/libwayland-cursor.so.0 (0x00007f84ca20b000) > libwayland-egl.so.1 => /lib/x86_64-linux-gnu/libwayland-egl.so.1 > (0x00007f84ca206000) > libXcursor.so.1 => /lib/x86_64-linux-gnu/libXcursor.so.1 > (0x00007f84c9e04000) > libXdamage.so.1 => /lib/x86_64-linux-gnu/libXdamage.so.1 > (0x00007f84c9dff000) > libthai.so.0 => /lib/x86_64-linux-gnu/libthai.so.0 > (0x00007f84c9df2000) > libgraphite2.so.3 => /lib/x86_64-linux-gnu/libgraphite2.so.3 > (0x00007f84c9dcb000) > libpixman-1.so.0 => /lib/x86_64-linux-gnu/libpixman-1.so.0 > (0x00007f84c9d1f000) > libxcb-shm.so.0 => /lib/x86_64-linux-gnu/libxcb-shm.so.0 > (0x00007f84c9d1a000) > libxcb.so.1 => /lib/x86_64-linux-gnu/libxcb.so.1 (0x00007f84c9cf0000) > libxcb-render.so.0 => /lib/x86_64-linux-gnu/libxcb-render.so.0 > (0x00007f84c9ce0000) > libXrender.so.1 => /lib/x86_64-linux-gnu/libXrender.so.1 > (0x00007f84c9cd3000) > libmount.so.1 => /lib/x86_64-linux-gnu/libmount.so.1 > (0x00007f84c9c8f000) > libffi.so.8 => /lib/x86_64-linux-gnu/libffi.so.8 (0x00007f84c9c84000) > libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0 > (0x00007f84c9bea000) > libuuid.so.1 => /lib/x86_64-linux-gnu/libuuid.so.1 > (0x00007f84c9be1000) > libbsd.so.0 => /lib/x86_64-linux-gnu/libbsd.so.0 (0x00007f84c9bca000) > libsystemd.so.0 => /lib/x86_64-linux-gnu/libsystemd.so.0 > (0x00007f84c9afa000) > libicuuc.so.72 => /lib/x86_64-linux-gnu/libicuuc.so.72 > (0x00007f84c98fb000) > libbrotlidec.so.1 => /lib/x86_64-linux-gnu/libbrotlidec.so.1 > (0x00007f84c98ee000) > libexpat.so.1 => /lib/x86_64-linux-gnu/libexpat.so.1 > (0x00007f84c98c3000) > libp11-kit.so.0 => /lib/x86_64-linux-gnu/libp11-kit.so.0 > (0x00007f84c9784000) > libidn2.so.0 => /lib/x86_64-linux-gnu/libidn2.so.0 > (0x00007f84c9763000) > libunistring.so.2 => /lib/x86_64-linux-gnu/libunistring.so.2 > (0x00007f84c95af000) > libtasn1.so.6 => /lib/x86_64-linux-gnu/libtasn1.so.6 > (0x00007f84c9598000) > libnettle.so.8 => /lib/x86_64-linux-gnu/libnettle.so.8 > (0x00007f84c9548000) > libhogweed.so.6 => /lib/x86_64-linux-gnu/libhogweed.so.6 > (0x00007f84c9500000) > libisl.so.23 => /lib/x86_64-linux-gnu/libisl.so.23 > (0x00007f84c9200000) > libmpc.so.3 => /lib/x86_64-linux-gnu/libmpc.so.3 (0x00007f84c94dc000) > libmpfr.so.6 => /lib/x86_64-linux-gnu/libmpfr.so.6 > (0x00007f84c9423000) > libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 > (0x00007f84c8e00000) > libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 > (0x00007f84c91dc000) > libatspi.so.0 => /lib/x86_64-linux-gnu/libatspi.so.0 > (0x00007f84c91a3000) > libdatrie.so.1 => /lib/x86_64-linux-gnu/libdatrie.so.1 > (0x00007f84c9418000) > libXau.so.6 => /lib/x86_64-linux-gnu/libXau.so.6 (0x00007f84c9412000) > libXdmcp.so.6 => /lib/x86_64-linux-gnu/libXdmcp.so.6 > (0x00007f84c940a000) > libblkid.so.1 => /lib/x86_64-linux-gnu/libblkid.so.1 > (0x00007f84c916c000) > libmd.so.0 => /lib/x86_64-linux-gnu/libmd.so.0 (0x00007f84c915f000) > libcap.so.2 => /lib/x86_64-linux-gnu/libcap.so.2 (0x00007f84c9153000) > libgcrypt.so.20 => /lib/x86_64-linux-gnu/libgcrypt.so.20 > (0x00007f84c8cb8000) > liblz4.so.1 => /lib/x86_64-linux-gnu/liblz4.so.1 (0x00007f84c9130000) > libicudata.so.72 => /lib/x86_64-linux-gnu/libicudata.so.72 > (0x00007f84c6e00000) > libbrotlicommon.so.1 => /lib/x86_64-linux-gnu/libbrotlicommon.so.1 > (0x00007f84c910d000) > libgpg-error.so.0 => /lib/x86_64-linux-gnu/libgpg-error.so.0 > (0x00007f84c90e7000) Notice the dependency on libgccjit.so.0, which is provided by GCC 12 or GCC 13 suitably configured That thing is used in GNU emacs source code. Look into its ffile emacs/src/comp.c (line 553, etc... 4689) On the same system the Ubuntu emacs don't use libgccjit. But perhaps I misunderstood your question? Regards. NB. My pet open source project is http://refpersys.org/ (an inference engine project, GPLv3+) and source code on https://github.com/RefPerSys/RefPerSys .... feel free to contact me (Basile Starynkevitch) by email to basile@starynkevitch.net or basile.starynkevitch@cea.fr (both near Paris in France) about it. -- Basile Starynkevitch <basile@starynkevitch.net> (only mine opinions / les opinions sont miennes uniquement) 92340 Bourg-la-Reine, France web page: starynkevitch.net/Basile/ ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: inline function expansion 2023-05-07 14:32 inline function expansion Lynn Winebarger 2023-05-07 17:51 ` Basile Starynkevitch @ 2023-05-07 19:48 ` Philip Kaludercic 2023-05-07 20:16 ` Lynn Winebarger ` (2 more replies) 1 sibling, 3 replies; 25+ messages in thread From: Philip Kaludercic @ 2023-05-07 19:48 UTC (permalink / raw) To: Lynn Winebarger; +Cc: help-gnu-emacs Lynn Winebarger <owinebar@gmail.com> writes: > Hi, > > Is there a function that will let us see the result of inline function > expansion at the source level? I see "byte-compile-preprocess" and > "byte-compile-inline-expand" as possibilities, but neither has a > docstring. What I usually do is just to invoke M-x disassemble, but you won't be viewing the code transformation on source-level. I am not sure if inlineing happens on a s-expression level like with macros, or if the byte-code optimiser just inlines the definition instead of generating code funcall. > If I use define-inline, I would like to be able to verify that the > result is what I expect, or vice versa, that I understand what the > result will be well enough to have the correct expectation. Isn't the idea of inlining that the behaviour/effect of invoking a function shouldn't change, just that the resulting code might be more efficient? > Thanks, > Lynn ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: inline function expansion 2023-05-07 19:48 ` Philip Kaludercic @ 2023-05-07 20:16 ` Lynn Winebarger 2023-05-08 0:21 ` Emanuel Berg 2023-05-08 2:03 ` Lynn Winebarger 2023-05-11 7:11 ` Lynn Winebarger 2 siblings, 1 reply; 25+ messages in thread From: Lynn Winebarger @ 2023-05-07 20:16 UTC (permalink / raw) To: Philip Kaludercic; +Cc: help-gnu-emacs On Sun, May 7, 2023 at 3:48 PM Philip Kaludercic <philipk@posteo.net> wrote: > Lynn Winebarger <owinebar@gmail.com> writes: > > > Hi, > > > > Is there a function that will let us see the result of inline function > > expansion at the source level? I see "byte-compile-preprocess" and > > "byte-compile-inline-expand" as possibilities, but neither has a > > docstring. > > What I usually do is just to invoke M-x disassemble, but you won't be > viewing the code transformation on source-level. I am not sure if > inlineing happens on a s-expression level like with macros, or if the > byte-code optimiser just inlines the definition instead of generating > code funcall. I know that define-inline uses compiler macros so that the function symbol is bound to an ordinary function while the compiler interprets the symbol as a macro. So, what I'd like is something like compiler-macroexpand-all. > > > If I use define-inline, I would like to be able to verify that the > > result is what I expect, or vice versa, that I understand what the > > result will be well enough to have the correct expectation. > > Isn't the idea of inlining that the behaviour/effect of invoking a > function shouldn't change, just that the resulting code might be more > efficient? The main thing I am interested in is the ability to do compile-time evaluation on constant expressions. I'm trying to design/implement a way of defining generic methods so that the specialization may be determined (or explicitly constructed) at compile time and compiled to a non-generic function call, and left to dynamic dispatch otherwise. I think inline functions, or at least the technique used in implementing them, will have to be a component in that. I really need to be able to see the expanded code to know if I'm using it correctly, though. It's something along the lines of the interface-based generics found in C#, so you can write generic code based on a set of defined interfaces, and ways to implement those interfaces, then instantiate those generic methods with particular realizations of the interfaces. Then those instantiations would be simple function calls. On the other hand, the generic function could be called at run time with objects that are wrapped by a realization of the interface. They should both give the same semantics (unless late-binding would cause different dispatching), but in the former case the composition of the functions implementing the interfaces would happen by inlining and not require intermediate allocation of wrapper objects.... Lynn Lynn ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: inline function expansion 2023-05-07 20:16 ` Lynn Winebarger @ 2023-05-08 0:21 ` Emanuel Berg 2023-05-08 11:12 ` Lynn Winebarger 0 siblings, 1 reply; 25+ messages in thread From: Emanuel Berg @ 2023-05-08 0:21 UTC (permalink / raw) To: help-gnu-emacs Lynn Winebarger wrote: >> Isn't the idea of inlining that the behaviour/effect of >> invoking a function shouldn't change, just that the >> resulting code might be more efficient? > > The main thing I am interested in is the ability to do > compile-time evaluation on constant expressions. What exactly are "constant expressions" in the context of this discussion? The same as this? https://www.stroustrup.com/sac10-constexpr.pdf > I'm trying to design/implement a way of defining generic > methods so that the specialization may be determined (or > explicitly constructed) at compile time and compiled to > a non-generic function call, and left to dynamic > dispatch otherwise. Okay, so what would be the gain(s) of having such a capability? -- underground experts united https://dataswamp.org/~incal ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: inline function expansion 2023-05-08 0:21 ` Emanuel Berg @ 2023-05-08 11:12 ` Lynn Winebarger 0 siblings, 0 replies; 25+ messages in thread From: Lynn Winebarger @ 2023-05-08 11:12 UTC (permalink / raw) To: help-gnu-emacs On Mon, May 8, 2023 at 12:15 AM Emanuel Berg <incal@dataswamp.org> wrote: > >> Isn't the idea of inlining that the behaviour/effect of > >> invoking a function shouldn't change, just that the > >> resulting code might be more efficient? > > > > The main thing I am interested in is the ability to do > > compile-time evaluation on constant expressions. > > What exactly are "constant expressions" in the context of > this discussion? > > The same as this? > > https://www.stroustrup.com/sac10-constexpr.pdf > See https://www.gnu.org/software/emacs/manual/html_node/elisp/Inline-Functions.html#index-inline_002dconst_002dp > > I'm trying to design/implement a way of defining generic > > methods so that the specialization may be determined (or > > explicitly constructed) at compile time and compiled to > > a non-generic function call, and left to dynamic > > dispatch otherwise. > > Okay, so what would be the gain(s) of having such > a capability? A structured way to write generic code without paying a substantial run-time cost. Pretty much the same case as for define-inline, except applied to generic methods. Lynn ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: inline function expansion 2023-05-07 19:48 ` Philip Kaludercic 2023-05-07 20:16 ` Lynn Winebarger @ 2023-05-08 2:03 ` Lynn Winebarger 2023-05-11 7:11 ` Lynn Winebarger 2 siblings, 0 replies; 25+ messages in thread From: Lynn Winebarger @ 2023-05-08 2:03 UTC (permalink / raw) To: Philip Kaludercic; +Cc: help-gnu-emacs On Sun, May 7, 2023 at 3:48 PM Philip Kaludercic <philipk@posteo.net> wrote: > What I usually do is just to invoke M-x disassemble, but you won't be > viewing the code transformation on source-level. I am not sure if > inlineing happens on a s-expression level like with macros, or if the > byte-code optimiser just inlines the definition instead of generating > code funcall. > So, https://debbugs.gnu.org/cgi/bugreport.cgi?bug=60450 says macroexpand-all does in fact expand compiler macros, although macroexpand does not. If that's the intended behavior then I can just use that function. It may still be a bug, though. Lynn ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: inline function expansion 2023-05-07 19:48 ` Philip Kaludercic 2023-05-07 20:16 ` Lynn Winebarger 2023-05-08 2:03 ` Lynn Winebarger @ 2023-05-11 7:11 ` Lynn Winebarger 2023-05-12 6:25 ` Emanuel Berg ` (2 more replies) 2 siblings, 3 replies; 25+ messages in thread From: Lynn Winebarger @ 2023-05-11 7:11 UTC (permalink / raw) To: Philip Kaludercic, Stefan Monnier; +Cc: help-gnu-emacs On Sun, May 7, 2023 at 3:48 PM Philip Kaludercic <philipk@posteo.net> wrote: > Lynn Winebarger <owinebar@gmail.com> writes: > > If I use define-inline, I would like to be able to verify that the > > result is what I expect, or vice versa, that I understand what the > > result will be well enough to have the correct expectation. > > Isn't the idea of inlining that the behaviour/effect of invoking a > function shouldn't change, just that the resulting code might be more > efficient? I was working off of the description in "Evolution of Emacs Lisp", page 45, which gives the example of cl-type-p for evaluation of constant expressions at compile-time via inline functions (figure 2): (define-inline cl-typep (val type) (inline-letevals (val) (pcase (inline-const-val type) (`(not ,ty) (inline-quote (not (cl-typep ,val ',ty)))) (`(eql ,v) (inline-quote (eql ,val ',v))) (`(satisfies ,pred) (inline-quote (funcall #',pred ,val))) ((and (pred symbolp) ty (guard (get ty 'cl-deftype-satisfies))) (inline-quote (funcall #',(get ty 'cl-deftype-satisfies) ,val))) ... (ty (error "Bad type spec: %s" ty))))) The info documentation does not include any examples involving inline-const-p, and in fact, I cannot find any code in the emacs lisp directory, or in the source of a couple of thousand packages, that makes use of inline-const-p or inline-const-val *other* than this exact function. I'd like to define inline- variants of pure subrs, e.g. arithmetic operators, type-predicates, that evaluate during macroexpansion rather than involving the compiler's optimization phase. I think it will be easier to ensure compile-time generic methods (e.g. a macro that simply calls a generic method) will (or at least can) dispatch during macro-expansion, without involving the byte-compiler's optimization phase. Unfortunately, I don't see how I can write something like (define-inline inline-+ (&rest args) (if (seq-every-p #'inline-const-p args) (apply (eval-when-compile (symbol-function '+)) args) (inline-quote (,(eval-when-compile (symbol-function '+)) . ,args)))) Admittedly, I haven't actually tried the code above, but if I understand the define-inline macro, the defun would include the "if" statement, and that is not the intension. I can still define the kind of inlined primitives described above using a macro based on define-inline's approach of defining a compiler macro and a defun, but I'm not seeing how to use the existing inline-* machinery to do it. Am I missing something? Lynn ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: inline function expansion 2023-05-11 7:11 ` Lynn Winebarger @ 2023-05-12 6:25 ` Emanuel Berg 2023-05-18 14:56 ` Lynn Winebarger 2023-05-18 18:29 ` Stefan Monnier 2 siblings, 0 replies; 25+ messages in thread From: Emanuel Berg @ 2023-05-12 6:25 UTC (permalink / raw) To: help-gnu-emacs Lynn Winebarger wrote: > I was working off of the description in "Evolution of Emacs > Lisp", page 45, which gives the example of cl-type-p for > evaluation of constant expressions at compile-time via > inline functions (figure 2) I still don't know what constant expressions are, is it Elisp pieces of source that all evaluate to something based only on entities that themselves don't change, so the whole thing can be optimized, i.e. replaced by this value so computation won't be done over and over? How do you check is something is a constant expression or not? > The info documentation does not include any examples > involving inline-const-p, and in fact, I cannot find any > code in the emacs lisp directory, or in the source of > a couple of thousand packages, that makes use of > inline-const-p or inline-const-val *other* than this > exact function. Sounds like you found something interesting ... > I'd like to define inline- variants of pure subrs, e.g. > arithmetic operators, type-predicates, that evaluate during > macroexpansion rather than involving the compiler's > optimization phase. But what does it matter when it happens, as long as it is done when it is supposed to be used? Compiling is a modular process with different stages. (It would be interesting BTW to see a list of these stages, along with the technical terms what is happening at each stage. For example, you have used the word "dispatch" several times, which I also haven't come across in the Elisp world until now.) > I think it will be easier to ensure compile-time generic > methods (e.g. a macro that simply calls a generic method) > will (or at least can) dispatch during macro-expansion, > without involving the byte-compiler's optimization phase. Okay now I see, you want to switch order, or duplicate the optimization so it happens before as well, in your case so you can have inline functions even at macro-expansion time? But what will be the advantage of that compared to doing it after? Unless you want to optimize and inline the macro-expansion process as well ... -- underground experts united https://dataswamp.org/~incal ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: inline function expansion 2023-05-11 7:11 ` Lynn Winebarger 2023-05-12 6:25 ` Emanuel Berg @ 2023-05-18 14:56 ` Lynn Winebarger 2023-05-19 13:31 ` Stefan Monnier 2023-05-18 18:29 ` Stefan Monnier 2 siblings, 1 reply; 25+ messages in thread From: Lynn Winebarger @ 2023-05-18 14:56 UTC (permalink / raw) To: Philip Kaludercic, Stefan Monnier; +Cc: help-gnu-emacs On Thu, May 11, 2023 at 3:11 AM Lynn Winebarger <owinebar@gmail.com> wrote: > On Sun, May 7, 2023 at 3:48 PM Philip Kaludercic <philipk@posteo.net> wrote: > > Lynn Winebarger <owinebar@gmail.com> writes: > > > If I use define-inline, I would like to be able to verify that the > > > result is what I expect, or vice versa, that I understand what the > > > result will be well enough to have the correct expectation. > > > > Isn't the idea of inlining that the behaviour/effect of invoking a > > function shouldn't change, just that the resulting code might be more > > efficient? In some languages, that is the definition. But it can also be interleaved with compile-time evaluation of constant-expressions to provide a more structured alternative to defmacro. At least, that's how I read the motivation for define-inline versus defsubst. The current implementation appears to be difficult to use in a meaningful way - as per the below, I've only identified two cases that make actual use of the facilities provided by define-inline beyond those afforded by defsubst, and defsubst is much easier to use. I've been trying to work out how to provide the functionality define-inline seeks to provide in a more convenient form without writing a full-blown partial-evaluator. > I was working off of the description in "Evolution of Emacs Lisp", > page 45, which gives the example of cl-type-p for evaluation of > constant expressions at compile-time via inline functions (figure 2): > (define-inline cl-typep (val type) > (inline-letevals (val) > (pcase (inline-const-val type) > (`(not ,ty) > (inline-quote (not (cl-typep ,val ',ty)))) > (`(eql ,v) > (inline-quote (eql ,val ',v))) > (`(satisfies ,pred) (inline-quote (funcall #',pred ,val))) > ((and (pred symbolp) ty (guard (get ty 'cl-deftype-satisfies))) > (inline-quote (funcall #',(get ty 'cl-deftype-satisfies) ,val))) > ... > (ty (error "Bad type spec: %s" ty))))) > > The info documentation does not include any examples involving > inline-const-p, and in fact, I cannot find any code in the emacs lisp > directory, or in the source of a couple of thousand packages, that > makes use of inline-const-p or inline-const-val *other* than this > exact function. After some more investigation, the only other code I've seen that uses define-inline to do more than defsubst would (as I understand it) is in gnus-sum.el: (define-inline gnus-summary-article-header (&optional number) "Return the header of article NUMBER." (inline-quote (gnus-data-header (gnus-data-find ,(or number (inline-quote (gnus-summary-article-number))))))) And I believe that occurence of "number" should be "(inline-constant-val number)". One example where there could be a use of define-inline's additional functionality is in: (define-inline cconv--var-classification (binder form) (inline-quote (cdr (assoc (cons ,binder ,form) cconv-var-classification)))) That could be changed to (define-inline cconv--var-classification (binder form) (inline-quote (cdr (assoc ,(inline-quote ,(cons (inline-const-val binder) (inline-const-val form))_ cconv-var-classification)))) It's a slight difference, but it is an example where computation could be moved to compile-time. From these example, it seems there are three capabilities afforded by the underlying implementation technique of define-inline: 1) Computing subexpressions that are compile-time constants (that's the cconv example) 2) Eliminating one or more parameters known at compile-time (that's what cl-typep does) 3) A "constexpr" (in C++ parlance) constructor, "inline-quote" Automating the first one involves identifying the maximal constant expression containing each potentially constant parameter, which is hard in general. But if we restrict the language handled by the inliner, it might be doable. For example, if we could assume no macros implicitly bind any identifiers already in use, and parameters marked with &const as a guarantee that the result of the function does not vary based on state associated with them, maybe that would be enough to determine non-trivial subexpressions pure subexpressions above rather than forcing the user to identify them explicitly by unquoting. There's also a need to identify variables that may be side-effected but which do not escape the inlined context, so the inlined function is "const" with respect to them. For example, in (lambda (n) (let ((acc 1)) (while (> n 0) (setq acc (* n acc) n (1- n))) acc)), the "while" form is "pure" because the value of the function is constant with respect to it. Alternatively, the while form could be written in CPS style that eliminates the side-effects, but the point of the restrictions is to avoid actually performing that analysis. For the second, I'm thinking that what the programmer wants to express is that if the "type" parameter is constant, then reducing all forms with pure operators with respect to type is a "pure" macro in the sense that it will always produce the same expression, and that expression has no occurrences of "type". For example, (cl-typep x 'integer) => (integerp x). ,This is "pure" as a function transforming source code forms. I'm thinking the user could mark the "val" parameter with "&opaque" to indicate that the cl-typep inliner should curry cl-typep to evaluate type at compile-type (when constant) and produce an expression that has no references to "type" and does not reference "val" while computing it. The third item is interesting in the case of the cconv example, where "cons" can be called at compile time if it occurs in a pure expression that is evaluated at compile time, but otherwise should be deferred to run-time, whether the arguments are constant or not. This is possible because most values in lisp can be stored in source expressions - C++'s constexpr constructors explicitly require a return value of a literal type. Just for the terms of the debate, I think the exclusion of "assoc" from being a "pure" function is incorrect, *if* we extend the notion of constants to include pure functions (or restrict the notion of constants to exclude non-pure functions). Then assoc is pure because when all three arguments are constant (i.e. the test function is pure), then the value is constant. Lynn ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: inline function expansion 2023-05-18 14:56 ` Lynn Winebarger @ 2023-05-19 13:31 ` Stefan Monnier 2023-05-20 14:18 ` Lynn Winebarger 0 siblings, 1 reply; 25+ messages in thread From: Stefan Monnier @ 2023-05-19 13:31 UTC (permalink / raw) To: Lynn Winebarger; +Cc: Philip Kaludercic, help-gnu-emacs > After some more investigation, the only other code I've seen that uses > define-inline to do more than defsubst would (as I understand it) is > in gnus-sum.el: > (define-inline gnus-summary-article-header (&optional number) > "Return the header of article NUMBER." > (inline-quote > (gnus-data-header (gnus-data-find > ,(or number > (inline-quote (gnus-summary-article-number))))))) > And I believe that occurence of "number" should be > "(inline-constant-val number)". You're right, tho in practice number is either nil or non-constant, so it doesn't make much difference. > One example where there could be a use of define-inline's additional > functionality is in: > (define-inline cconv--var-classification (binder form) > (inline-quote > (cdr (assoc (cons ,binder ,form) cconv-var-classification)))) > > That could be changed to > (define-inline cconv--var-classification (binder form) > (inline-quote > (cdr (assoc ,(inline-quote ,(cons (inline-const-val binder) > (inline-const-val form))_ cconv-var-classification)))) I think you can simplify that to: (define-inline cconv--var-classification (binder form) (inline-quote (cdr (assoc ,(cons (inline-const-val binder) (inline-const-val form)) cconv-var-classification)))) but here as well, this optimization would never apply because those args are never literal constants. Worse: the failure of `inline-const-val` would cause the whole inlining to fail :-( To support this kind of optimization we'd need to add specific support for it to `define-inline`. > Automating the first one involves identifying the maximal constant > expression containing each potentially constant parameter, which is > hard in general. But if we restrict the language handled by the > inliner, it might be doable. For example, if we could assume no > macros implicitly bind any identifiers already in use, and parameters > marked with &const as a guarantee that the result of the function > does not vary based on state associated with them, maybe that would be > enough to determine non-trivial subexpressions pure subexpressions > above rather than forcing the user to identify them explicitly by > unquoting. The driving principle behind `define-inline` is to not do any analysis and leave that responsability in the hands of the users of `define-inline` :-) So we could/should add some special annotation like (inline-precompute <FOO>) which treats <FOO> as an expression that builds a value and precomputes it if all the `,<EXP>` that appear in it are `inline-const-p`. > For the second, I'm thinking that what the programmer wants to > express is that if the "type" parameter is constant, then reducing all > forms with pure operators with respect to type is a "pure" macro in > the sense that it will always produce the same expression, and that > expression has no occurrences of "type". Note that in the case of `cl-typep` the expansion may fail to fully eliminate the "type" parameter, IIRC (i.e. it manages to inline/unroll part of the recursion but not all of it because some part of the type is not statically known or too complex). > Then assoc is pure because when all three arguments are constant > (i.e. the test function is pure), then the value is constant. IIRC the reason it's not "pure" (for some definition of "pure") is because it can signal an error. Stefan ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: inline function expansion 2023-05-19 13:31 ` Stefan Monnier @ 2023-05-20 14:18 ` Lynn Winebarger 2023-05-20 15:32 ` Stefan Monnier 0 siblings, 1 reply; 25+ messages in thread From: Lynn Winebarger @ 2023-05-20 14:18 UTC (permalink / raw) To: Stefan Monnier; +Cc: Philip Kaludercic, help-gnu-emacs On Fri, May 19, 2023 at 9:31 AM Stefan Monnier <monnier@iro.umontreal.ca> wrote: > > After some more investigation, the only other code I've seen that uses > > define-inline to do more than defsubst would (as I understand it) is > > in gnus-sum.el: > > (define-inline gnus-summary-article-header (&optional number) > > "Return the header of article NUMBER." > > (inline-quote > > (gnus-data-header (gnus-data-find > > ,(or number > > (inline-quote (gnus-summary-article-number))))))) > > And I believe that occurence of "number" should be > > "(inline-constant-val number)". > > You're right, tho in practice number is either nil or non-constant, so > it doesn't make much difference. I'm just pointing out it is difficult to tell how to use the facilities for compile-time evaluation provided by define-inline. > > > One example where there could be a use of define-inline's additional > > functionality is in: > > (define-inline cconv--var-classification (binder form) > > (inline-quote > > (cdr (assoc (cons ,binder ,form) cconv-var-classification)))) > > > > That could be changed to > > (define-inline cconv--var-classification (binder form) > > (inline-quote > > (cdr (assoc ,(inline-quote ,(cons (inline-const-val binder) > > (inline-const-val form))_ cconv-var-classification)))) > > I think you can simplify that to: > > (define-inline cconv--var-classification (binder form) > (inline-quote > (cdr (assoc ,(cons (inline-const-val binder) > (inline-const-val form)) > cconv-var-classification)))) Don't you need something to add a quote to the cons cell when "binder" or "form" are not constant? > > but here as well, this optimization would never apply because those args > are never literal constants. Worse: the failure of `inline-const-val` > would cause the whole inlining to fail :-( Could inline--do-quote catch the throw? > To support this kind of optimization we'd need to add specific support > for it to `define-inline`. > > > Automating the first one involves identifying the maximal constant > > expression containing each potentially constant parameter, which is > > hard in general. But if we restrict the language handled by the > > inliner, it might be doable. For example, if we could assume no > > macros implicitly bind any identifiers already in use, and parameters > > marked with &const as a guarantee that the result of the function > > does not vary based on state associated with them, maybe that would be > > enough to determine non-trivial subexpressions pure subexpressions > > above rather than forcing the user to identify them explicitly by > > unquoting. > > The driving principle behind `define-inline` is to not do any analysis > and leave that responsability in the hands of the users of > `define-inline` :-) > > So we could/should add some special annotation like (inline-precompute > <FOO>) which treats <FOO> as an expression that builds a value and > precomputes it if all the `,<EXP>` that appear in it are > `inline-const-p`. > > > For the second, I'm thinking that what the programmer wants to > > express is that if the "type" parameter is constant, then reducing all > > forms with pure operators with respect to type is a "pure" macro in > > the sense that it will always produce the same expression, and that > > expression has no occurrences of "type". > > Note that in the case of `cl-typep` the expansion may fail to fully > eliminate the "type" parameter, IIRC (i.e. it manages to inline/unroll > part of the recursion but not all of it because some part of the type is > not statically known or too complex). I don't know what "too complex" would entail, but if it's not statically known then the type arg isn't really a constant so it's not really a failure. > > > Then assoc is pure because when all three arguments are constant > > (i.e. the test function is pure), then the value is constant. > > IIRC the reason it's not "pure" (for some definition of "pure") is > because it can signal an error. The byte-opt.el code from v28.2.50 says it's because the third argument may be an impure function: ;; `assoc' and `assoc-default' are excluded since they are ;; impure if the test function is (consider `string-match'). I'm not sure why the possibility of signaling an error alone would be disqualifying. For example, (+ 5 's) signals an error. Also, I don't get why logand isn't considered a pure function - how important is it to be able to run byte-code generated by a 32-bit emacs in a 64-bit emacs (or vice-versa)? I could see it making sense historically, but is it still a useful property of byte-code? ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: inline function expansion 2023-05-20 14:18 ` Lynn Winebarger @ 2023-05-20 15:32 ` Stefan Monnier 2023-05-21 12:47 ` Lynn Winebarger 0 siblings, 1 reply; 25+ messages in thread From: Stefan Monnier @ 2023-05-20 15:32 UTC (permalink / raw) To: Lynn Winebarger; +Cc: Philip Kaludercic, help-gnu-emacs >> You're right, tho in practice number is either nil or non-constant, so >> it doesn't make much difference. > I'm just pointing out it is difficult to tell how to use the > facilities for compile-time evaluation provided by define-inline. Oh, that, yes the `define-inline` facility is hard to use, no doubt. I'm not happy with it. This is also reflected in the lack of doc because it's difficult to document it much better than "look at the code, try it out, and fiddle until it works" :-( >> (define-inline cconv--var-classification (binder form) >> (inline-quote >> (cdr (assoc ,(cons (inline-const-val binder) >> (inline-const-val form)) >> cconv-var-classification)))) > > Don't you need something to add a quote to the cons cell when "binder" > or "form" are not constant? Oh, you're right. >> but here as well, this optimization would never apply because those args >> are never literal constants. Worse: the failure of `inline-const-val` >> would cause the whole inlining to fail :-( > Could inline--do-quote catch the throw? It could, but it doesn't (and it would be an incompatible change). >> IIRC the reason it's not "pure" (for some definition of "pure") is >> because it can signal an error. > > The byte-opt.el code from v28.2.50 says it's because the third > argument may be an impure function: > ;; `assoc' and `assoc-default' are excluded since they are > ;; impure if the test function is (consider `string-match'). > I'm not sure why the possibility of signaling an error alone would be > disqualifying. For example, (+ 5 's) signals an error. Oh, you're right, sorry. So the problem is if the test function is constant but not pure. > Also, I don't get why logand isn't considered a pure function What makes you think it's not? ELISP> (symbol-plist 'logand) (gv-expander #f(compiled-function (do place &rest masks) #<bytecode -0xc7679d4528b2a2a>) side-effect-free t pure t) ELISP> > How important is it to be able to run byte-code generated by > a 32-bit emacs in a 64-bit emacs (or vice-versa)? The Emacs tarball comes with all the `.elc` files, so it's important that `.elc` files be portable across architectures. Stefan ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: inline function expansion 2023-05-20 15:32 ` Stefan Monnier @ 2023-05-21 12:47 ` Lynn Winebarger 0 siblings, 0 replies; 25+ messages in thread From: Lynn Winebarger @ 2023-05-21 12:47 UTC (permalink / raw) To: Stefan Monnier; +Cc: Philip Kaludercic, help-gnu-emacs On Sat, May 20, 2023 at 11:32 AM Stefan Monnier <monnier@iro.umontreal.ca> wrote: > >> You're right, tho in practice number is either nil or non-constant, so > >> it doesn't make much difference. > > I'm just pointing out it is difficult to tell how to use the > > facilities for compile-time evaluation provided by define-inline. > > Oh, that, yes the `define-inline` facility is hard to use, no doubt. > I'm not happy with it. This is also reflected in the lack of doc > because it's difficult to document it much better than "look at the > code, try it out, and fiddle until it works" :-( Couldn't most (all but 2) of the current instances be captured by a simplified interface defsubst* defined along the lines of: (defmacro defsubst* (name args &rest body) (let ((parameters (inline--get-parameters args))) `(define-inline ,name ,args (inline-letevals ,(inline--susbt*-bindings args) (inline-quote (cl-symbol-macrolet ,(mapcar (lambda (v) `(,v (,'\, ,v))) parameters) ,@body)))))) Where I've left the ugly details of handling lambda lists to inline--get-parameters and inline--subst*-bindings. I'm unclear on why defsubst* is an improvement on defsubst. > >> (define-inline cconv--var-classification (binder form) > >> (inline-quote > >> (cdr (assoc ,(cons (inline-const-val binder) > >> (inline-const-val form)) > >> cconv-var-classification)))) > > > > Don't you need something to add a quote to the cons cell when "binder" > > or "form" are not constant? > > Oh, you're right. > > >> but here as well, this optimization would never apply because those args > >> are never literal constants. Worse: the failure of `inline-const-val` > >> would cause the whole inlining to fail :-( > > Could inline--do-quote catch the throw? > > It could, but it doesn't (and it would be an incompatible change). As far as I can tell, the only code that makes use of inline-const-val is cl-typep, so the impact of any incompatibility should be (famous last words) controllable. Is the issue figuring out how to define inline-letevals so that inline-const-val would be able to "see through" it when the parameter is constant but be bound by it when it is not constant? > > The byte-opt.el code from v28.2.50 says it's because the third > > argument may be an impure function: > > ;; `assoc' and `assoc-default' are excluded since they are > > ;; impure if the test function is (consider `string-match'). > > I'm not sure why the possibility of signaling an error alone would be > > disqualifying. For example, (+ 5 's) signals an error. > > Oh, you're right, sorry. So the problem is if the test function is > constant but not pure. > > > Also, I don't get why logand isn't considered a pure function > > What makes you think it's not? > Faulty memory - I was thinking of lsh as mentioned in this comment from byte-opt.el: ;; Pure functions are side-effect free functions whose values depend ;; only on their arguments, not on the platform. For these functions, ;; calls with constant arguments can be evaluated at compile time. ;; For example, ash is pure since its results are machine-independent, ;; whereas lsh is not pure because (lsh -1 -1)'s value depends on the ;; fixnum range. > > How important is it to be able to run byte-code generated by > > a 32-bit emacs in a 64-bit emacs (or vice-versa)? > > The Emacs tarball comes with all the `.elc` files, so it's important > that `.elc` files be portable across architectures. Interesting restriction. Lynn ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: inline function expansion 2023-05-11 7:11 ` Lynn Winebarger 2023-05-12 6:25 ` Emanuel Berg 2023-05-18 14:56 ` Lynn Winebarger @ 2023-05-18 18:29 ` Stefan Monnier 2023-05-19 0:22 ` Lynn Winebarger 2 siblings, 1 reply; 25+ messages in thread From: Stefan Monnier @ 2023-05-18 18:29 UTC (permalink / raw) To: Lynn Winebarger; +Cc: Philip Kaludercic, help-gnu-emacs [ Sorry, this also feel through. ] >> Isn't the idea of inlining that the behaviour/effect of invoking a >> function shouldn't change, just that the resulting code might be more >> efficient? Indeed, but `define-inline` is a tool that lets you define the inliner at the same time as the non-inlined definition, and it's up to the user of `define-inline` to make sure the inliner gives the right result (tho `define-inline` tries to make it easier to DTRT). > I'd like to define inline- variants of pure subrs, e.g. arithmetic > operators, type-predicates, that evaluate during macroexpansion rather > than involving the compiler's optimization phase. `define-inline` is definitely not meant for that use-case, since it's designed to have a single definition that does both: - define the inliner. - define the non-inlined version of the code. In your case, the non-inlined version is written elsewhere :-( Instead, you want to use a "compiler macro". Moving optimizations to the macroexpansion phase can have many benefits, indeed (e.g. it lets those optimization affect the subsequent closure conversion), but also downsides (you may get more warnings about chunks of code over which you have no/little control, like "unused variable" warnings in places where the vars is "used" by code that's optimized away). > I think it will be easier to ensure compile-time generic methods > (e.g. a macro that simply calls a generic method) will (or at least > can) dispatch during macro-expansion, without involving the > byte-compiler's optimization phase. I don't understand this, sorry. > (define-inline inline-+ (&rest args) > (if (seq-every-p #'inline-const-p args) > (apply (eval-when-compile (symbol-function '+)) args) > (inline-quote (,(eval-when-compile (symbol-function '+)) . ,args)))) IIRC you definitely need an `inline-letvals` somewhere here. But also this will define the non-inlined version as something along the lines of: (defun inline-+ (&rest args) (if (seq-every-p ... args) (apply '+ args) (apply (symbol-function '+) args))) which is much too slow IMO. Stefan ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: inline function expansion 2023-05-18 18:29 ` Stefan Monnier @ 2023-05-19 0:22 ` Lynn Winebarger 2023-05-19 13:07 ` Stefan Monnier 0 siblings, 1 reply; 25+ messages in thread From: Lynn Winebarger @ 2023-05-19 0:22 UTC (permalink / raw) To: Stefan Monnier; +Cc: Philip Kaludercic, help-gnu-emacs On Thu, May 18, 2023 at 2:29 PM Stefan Monnier <monnier@iro.umontreal.ca> wrote: > > I think it will be easier to ensure compile-time generic methods > > (e.g. a macro that simply calls a generic method) will (or at least > > can) dispatch during macro-expansion, without involving the > > byte-compiler's optimization phase. > > I don't understand this, sorry. I'm trying to provide myself a tool for generic programming that dispatches at compile time. I've been writing too much repetitive code, but I'm not a fan of ad hoc macros either. Basically, I'm trying to provide a C#-ish system for writing generic code parameterized by interface requirements. Then the generic method can be moved to compile-time, with specialization on constant parameters to dispatch to specialized code. Like cl-typep, except instead of an explicit pcase form, calling a generic method. > > (define-inline inline-+ (&rest args) > > (if (seq-every-p #'inline-const-p args) > > (apply (eval-when-compile (symbol-function '+)) args) > > (inline-quote (,(eval-when-compile (symbol-function '+)) . ,args)))) > > IIRC you definitely need an `inline-letvals` somewhere here. That's the issue - if you use inline-letvals, then the code can't make use of constant values provided as operands. > But also this will define the non-inlined version as something along the > lines of: > > (defun inline-+ (&rest args) > (if (seq-every-p ... args) > (apply '+ args) > (apply (symbol-function '+) args))) > > which is much too slow IMO. I agree. I posted a patch to emacs-devel for define-inline-pure-subr that adapts the technique of define-inline. I wanted to literally inline the subr's into the code, but aside from the issue of not having read-syntax for subrs (for some reason), some parts of core emacs appear to rely on the car of a form being a symbol. So I ended up defining a second symbol to hold the true definition, with the compiler macro inserting a call to that symbol, while the original symbol made into either a defun or defsubst. The code copies the plist of the original symbol to the second symbol so any properties used by the byte-compiler/optimizer will be applied to the new symbol as well. ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: inline function expansion 2023-05-19 0:22 ` Lynn Winebarger @ 2023-05-19 13:07 ` Stefan Monnier 2023-05-20 15:01 ` Lynn Winebarger 0 siblings, 1 reply; 25+ messages in thread From: Stefan Monnier @ 2023-05-19 13:07 UTC (permalink / raw) To: Lynn Winebarger; +Cc: Philip Kaludercic, help-gnu-emacs > I'm trying to provide myself a tool for generic programming that > dispatches at compile time. I still don't understand what you mean by that. Do you mean that the programmers write (my-foo a b c) and during macroexpansion this is turned into (my-foo-for-thurbies a b c) when we somehow figure out that `a` is a "thurby"? > I've been writing too much repetitive > code, but I'm not a fan of ad hoc macros either. Basically, I'm > trying to provide a C#-ish system for writing generic code > parameterized by interface requirements. [ "generic code" and "interface requirements" are ... generic terms with wildly different meanings in different communities, and I'm not familiar enough with the C# world to guess what you mean by that. ] >> > (define-inline inline-+ (&rest args) >> > (if (seq-every-p #'inline-const-p args) >> > (apply (eval-when-compile (symbol-function '+)) args) >> > (inline-quote (,(eval-when-compile (symbol-function '+)) . ,args)))) >> >> IIRC you definitely need an `inline-letvals` somewhere here. > > That's the issue - if you use inline-letvals, then the code can't make > use of constant values provided as operands. Are you sure? I think the real problem is that `inline-const-p` is not a function but a macro, so you can pass it as a first-class function. E.g.: (define-inline my-plus (&rest args) (inline-letevals args (if (seq-every-p (lambda (x) (inline-const-p x)) args) (apply #'+ (mapcar (lambda (x) (inline-const-val x)) args)) (inline-quote (+ . ,args))))) seems to do (more or less) what your code illustrated. >> But also this will define the non-inlined version as something along the >> lines of: >> >> (defun inline-+ (&rest args) >> (if (seq-every-p ... args) >> (apply '+ args) >> (apply (symbol-function '+) args))) >> >> which is much too slow IMO. > > I agree. > > I posted a patch to emacs-devel for define-inline-pure-subr that > adapts the technique of define-inline. I'm still not sure why you're not using a `compiler-macro` which seems to be exactly what you're after. Stefan ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: inline function expansion 2023-05-19 13:07 ` Stefan Monnier @ 2023-05-20 15:01 ` Lynn Winebarger 2023-05-20 15:48 ` Stefan Monnier 0 siblings, 1 reply; 25+ messages in thread From: Lynn Winebarger @ 2023-05-20 15:01 UTC (permalink / raw) To: Stefan Monnier; +Cc: Philip Kaludercic, help-gnu-emacs On Fri, May 19, 2023 at 9:07 AM Stefan Monnier <monnier@iro.umontreal.ca> wrote: > > I'm trying to provide myself a tool for generic programming that > > dispatches at compile time. > > I still don't understand what you mean by that. > Do you mean that the programmers write > > (my-foo a b c) > > and during macroexpansion this is turned into > > (my-foo-for-thurbies a b c) > > when we somehow figure out that `a` is a "thurby"? For my purposes, an "interface" spec is just a list of method and slot signatures associated to some object, which will be realized by a set of bindings specified via special forms and bound to some name. Then a compile-time generic f is a factoring of a regular generic into a generic function of interface realizations that produces a concrete runtime function. I'm thinking to adopt the "place name" technique of naming the concrete function by the form that created it, ie. \(f\ arg1-iface-realizer\ arg2-iface-realizer\ ...\). Note the check is that the realizer provides all the required signatures (a superset), which is why I refer to it as a "constraint" on the interface implementation. Then the generic method could dispatch on simple wrappers created by the iface-realizers. If constructors are invoked at compile time when they appear in constant expressions and have constant arguments, then the generic multi-dispatch method specialized on those realizers can inline the call to the specialized method in the compiler macro, while a residual generic method could do the same dispatch at run-time. But the point is to separate the expression of the algorithm from the expression of the bindings that are used by the algorithm, without resorting to either dynamic dispatch tables or ad hoc macros per algorithm. > > I've been writing too much repetitive > > code, but I'm not a fan of ad hoc macros either. Basically, I'm > > trying to provide a C#-ish system for writing generic code > > parameterized by interface requirements. > > [ "generic code" and "interface requirements" are ... generic terms with > wildly different meanings in different communities, and I'm not familiar > enough with the C# world to guess what you mean by that. ] I only hacked on C#'s generics for about a month a decade ago, so it really is just my memory of the "flavor" of the generics being expressed by constraints on interfaces. > > >> > (define-inline inline-+ (&rest args) > >> > (if (seq-every-p #'inline-const-p args) > >> > (apply (eval-when-compile (symbol-function '+)) args) > >> > (inline-quote (,(eval-when-compile (symbol-function '+)) . ,args)))) > >> > >> IIRC you definitely need an `inline-letvals` somewhere here. > > > > That's the issue - if you use inline-letvals, then the code can't make > > use of constant values provided as operands. > > Are you sure? I think the real problem is that `inline-const-p` is not > a function but a macro, so you can pass it as a first-class function > E.g.: > > (define-inline my-plus (&rest args) > (inline-letevals args > (if (seq-every-p (lambda (x) (inline-const-p x)) args) > (apply #'+ (mapcar (lambda (x) (inline-const-val x)) args)) > (inline-quote (+ . ,args))))) > > seems to do (more or less) what your code illustrated. But then inline-const-val will (and inline-const-p) will get the symbol 'x as the operand, right? > > >> But also this will define the non-inlined version as something along the > >> lines of: > >> > >> (defun inline-+ (&rest args) > >> (if (seq-every-p ... args) > >> (apply '+ args) > >> (apply (symbol-function '+) args))) > >> > >> which is much too slow IMO. > > > > I agree. > > > > I posted a patch to emacs-devel for define-inline-pure-subr that > > adapts the technique of define-inline. > > I'm still not sure why you're not using a `compiler-macro` which seems > to be exactly what you're after. I'm very finicky I suppose. I want to get constant expression evaluation as automatically as possible, to enable the compile-time dispatch cleanly. Or are you saying that generic methods can be directly made into compiler macros? I was thinking I needed the original symbol redefined to avoid multiple macro expansions of the operands, as define-inline-pure-subr expands its operands before returning, so returning the inline-form is problematic. I also consider the property that inlining is uncooperative with advice to be a feature, particularly for pure functions. After all, the advice facility makes every function identified by a symbol impure. Lynn ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: inline function expansion 2023-05-20 15:01 ` Lynn Winebarger @ 2023-05-20 15:48 ` Stefan Monnier 2023-05-27 14:34 ` Lynn Winebarger 0 siblings, 1 reply; 25+ messages in thread From: Stefan Monnier @ 2023-05-20 15:48 UTC (permalink / raw) To: Lynn Winebarger; +Cc: Philip Kaludercic, help-gnu-emacs >> I still don't understand what you mean by that. >> Do you mean that the programmers write >> >> (my-foo a b c) >> >> and during macroexpansion this is turned into >> >> (my-foo-for-thurbies a b c) >> >> when we somehow figure out that `a` is a "thurby"? > > For my purposes, an "interface" spec is just a list of method and slot > signatures associated to some object, which will be realized by a set > of bindings specified via special forms and bound to some name. > Then a compile-time generic f is a factoring of a regular generic into > a generic function of interface realizations that produces a concrete > runtime function. I'm thinking to adopt the "place name" technique of > naming the concrete function by the form that created it, ie. \(f\ > arg1-iface-realizer\ arg2-iface-realizer\ ...\). Note the check is > that the realizer provides all the required signatures (a superset), > which is why I refer to it as a "constraint" on the interface > implementation. > > Then the generic method could dispatch on simple wrappers created by > the iface-realizers. If constructors are invoked at compile time when > they appear in constant expressions and have constant arguments, then > the generic multi-dispatch method specialized on those realizers can > inline the call to the specialized method in the compiler macro, while > a residual generic method could do the same dispatch at run-time. > > But the point is to separate the expression of the algorithm from the > expression of the bindings that are used by the algorithm, without > resorting to either dynamic dispatch tables or ad hoc macros per > algorithm. I'm still lost. An example might help. >> (define-inline my-plus (&rest args) >> (inline-letevals args >> (if (seq-every-p (lambda (x) (inline-const-p x)) args) >> (apply #'+ (mapcar (lambda (x) (inline-const-val x)) args)) >> (inline-quote (+ . ,args))))) >> >> seems to do (more or less) what your code illustrated. > > But then inline-const-val will (and inline-const-p) will get the > symbol 'x as the operand, right? I don't think so. Have you tried it (Edebug should work correctly)? ELISP> (macroexpand-all '(my-plus 5 y)) (+ 5 y) ELISP> (macroexpand-all '(my-plus 5 6)) 11 (#o13, #xb, ?\C-k) ELISP> >> I'm still not sure why you're not using a `compiler-macro` which seems >> to be exactly what you're after. > > I'm very finicky I suppose. I want to get constant expression > evaluation as automatically as possible, to enable the compile-time > dispatch cleanly. Or are you saying that generic methods can be > directly made into compiler macros? And here I'm lost as well. AFAICT there's just as little "directly made into" for define-inline as for compiler-macros (`define-inline` is just a way to define a function and its `compiler-macro` in one go). > I was thinking I needed the original symbol redefined to avoid > multiple macro expansions of the operands, `compiler-macro`s can decide to keep the call as-is without introducing an infinite macro-expansion loop. > After all, the advice facility makes every function identified by > a symbol impure. It's the mutability of the symbol (i.e. `fset`) rather than the advice facility itself, but yes :-) Stefan ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: inline function expansion 2023-05-20 15:48 ` Stefan Monnier @ 2023-05-27 14:34 ` Lynn Winebarger 2023-05-28 14:12 ` Lynn Winebarger 2023-05-28 14:57 ` Stefan Monnier 0 siblings, 2 replies; 25+ messages in thread From: Lynn Winebarger @ 2023-05-27 14:34 UTC (permalink / raw) To: Stefan Monnier; +Cc: Philip Kaludercic, help-gnu-emacs On Sat, May 20, 2023 at 11:48 AM Stefan Monnier <monnier@iro.umontreal.ca> wrote: > > For my purposes, an "interface" spec is just a list of method and slot > > signatures associated to some object, which will be realized by a set > > of bindings specified via special forms and bound to some name. > > Then a compile-time generic f is a factoring of a regular generic into > > a generic function of interface realizations that produces a concrete > > runtime function. I'm thinking to adopt the "place name" technique of > > naming the concrete function by the form that created it, ie. \(f\ > > arg1-iface-realizer\ arg2-iface-realizer\ ...\). Note the check is > > that the realizer provides all the required signatures (a superset), > > which is why I refer to it as a "constraint" on the interface > > implementation. > > > > Then the generic method could dispatch on simple wrappers created by > > the iface-realizers. If constructors are invoked at compile time when > > they appear in constant expressions and have constant arguments, then > > the generic multi-dispatch method specialized on those realizers can > > inline the call to the specialized method in the compiler macro, while > > a residual generic method could do the same dispatch at run-time. > > > > But the point is to separate the expression of the algorithm from the > > expression of the bindings that are used by the algorithm, without > > resorting to either dynamic dispatch tables or ad hoc macros per > > algorithm. > > I'm still lost. An example might help. For my purposes, interfaces and realizations of interfaces are just a way to specify bindings of symbols in a more structured and encapsulated way than raw macros. I'm still spit-balling here, but I'm thinking a generic compile-time-dispatched (inlineable) "max" might be defined along these lines: ;; ordering interface has two parameters (define-interface ordering (bool-T elt-T) ;; return value of `lt' method satisfies bool-T interface (method (bool-T lt) (elt-T a) (elt-T b))) (define-realized-interface integer-ordering (ordering boolean integer) ;; bind lt method to built-in `<' (method lt <)) (defgeneric max (< a b) "Determine the larger of a and b according to <") (define-inline-method max ((ordering <) &opaque a b) "Determine the larger of a and b according to ordering <" ;; < is treated syntactically as a dispatcher (if (< lt a b) a b)) ;; because elisp does not allow applications in the operator position (define-inlined-method integer-max (max integer-ordering)) ;; Alternatively, ;; (integer-ordering lt) reduces at compile time to something like ;; #s(interface-method ;; #s(interface-realization ;; #s(interface ordering boolean integer) ;; <) ;; lt ;; <) ;; which `max' is specialized for by define-inline-method above, so (defun integer-max (a b) ;; inlined by compile-time dispatch of the max generic method (max (integer-ordering lt) a b)) ;; These should produce t (= (max integer-ordering 5 6) (integer-max 5 6)) (= (max (integer-ordering lt) 5 6) (integer-max 5 6)) (macroexp-const-p (macroexpand-all '(max (integer-ordering lt) 5 6))) > > >> (define-inline my-plus (&rest args) > >> (inline-letevals args > >> (if (seq-every-p (lambda (x) (inline-const-p x)) args) > >> (apply #'+ (mapcar (lambda (x) (inline-const-val x)) args)) > >> (inline-quote (+ . ,args))))) > >> > >> seems to do (more or less) what your code illustrated. > > > > But then inline-const-val will (and inline-const-p) will get the > > symbol 'x as the operand, right? > > I don't think so. Have you tried it (Edebug should work correctly)? Ok, now I see why inline--testconst-p expands to include a call to macroexp-const-p. > >> I'm still not sure why you're not using a `compiler-macro` which seems > >> to be exactly what you're after. > > > > I'm very finicky I suppose. I want to get constant expression > > evaluation as automatically as possible, to enable the compile-time > > dispatch cleanly. Or are you saying that generic methods can be > > directly made into compiler macros? > > And here I'm lost as well. AFAICT there's just as little "directly made > into" for define-inline as for compiler-macros (`define-inline` is just > a way to define a function and its `compiler-macro` in one go). Hopefully the example above clarified what I'm talking about - `max' is specialized at compile-time, and reduced completely at compile-time as well if all the arguments are constant. Although I would change "inline-const-p" to test for function purity (exclude stateful closures or otherwise impure functions). > > I was thinking I needed the original symbol redefined to avoid > > multiple macro expansions of the operands, > > `compiler-macro`s can decide to keep the call as-is without introducing > an infinite macro-expansion loop. > I was concerned about an exponential rather than infinite number of expansions , though I may have just been thinking conservatively. Lynn ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: inline function expansion 2023-05-27 14:34 ` Lynn Winebarger @ 2023-05-28 14:12 ` Lynn Winebarger 2023-05-28 14:57 ` Stefan Monnier 1 sibling, 0 replies; 25+ messages in thread From: Lynn Winebarger @ 2023-05-28 14:12 UTC (permalink / raw) To: Stefan Monnier; +Cc: Philip Kaludercic, help-gnu-emacs On Sat, May 27, 2023 at 10:34 AM Lynn Winebarger <owinebar@gmail.com> wrote: > > On Sat, May 20, 2023 at 11:48 AM Stefan Monnier > <monnier@iro.umontreal.ca> wrote: > > > For my purposes, an "interface" spec is just a list of method and slot > > > signatures associated to some object, which will be realized by a set > > > of bindings specified via special forms and bound to some name. > > > Then a compile-time generic f is a factoring of a regular generic into > > > a generic function of interface realizations that produces a concrete > > > runtime function. I'm thinking to adopt the "place name" technique of > > > naming the concrete function by the form that created it, ie. \(f\ > > > arg1-iface-realizer\ arg2-iface-realizer\ ...\). Note the check is > > > that the realizer provides all the required signatures (a superset), > > > which is why I refer to it as a "constraint" on the interface > > > implementation. > > > > > > Then the generic method could dispatch on simple wrappers created by > > > the iface-realizers. If constructors are invoked at compile time when > > > they appear in constant expressions and have constant arguments, then > > > the generic multi-dispatch method specialized on those realizers can > > > inline the call to the specialized method in the compiler macro, while > > > a residual generic method could do the same dispatch at run-time. > > > > > > But the point is to separate the expression of the algorithm from the > > > expression of the bindings that are used by the algorithm, without > > > resorting to either dynamic dispatch tables or ad hoc macros per > > > algorithm. > > > > I'm still lost. An example might help. > > For my purposes, interfaces and realizations of interfaces are just a > way to specify bindings of symbols in a more structured and > encapsulated way than raw macros. > I'm still spit-balling here, but I'm thinking a generic > compile-time-dispatched (inlineable) "max" might be defined along > these lines: > My intention below is that define-inline-method is like applying the generic function to the abstract interfaces, producing a function that consumes the body as an argument. So maybe there should be a (define-inline-generic max (< a b)) that creates the dual-definition of max as a (generic) compiler macro and generic function, with the method specializing max on "realized-interface" arguments that successfully unify against the supplied interface spec: (max <-iface a-iface b-iface) = (lambda (<-realized a-realized b-realized) ((lambda (a b) ...) (realized-interface-value a) (realized-interface-value b))) where "realized-interface-value" removes the wrapper used for specialization. Note that it also only does so for "opaque" arguments, to allow the "<" argument to be treated as a fixed constant when the call site provides a constant. This definition should specialize both the generic compiler macro and the generic function on realizations of the specified interfaces for the arguments of max. Technically, we need the following definitions: ;; these should be predefined constants (define-interface null-interface) (define-realized-interface null-realized) In the code below, not specifying an interface for the a and b arguments is the same as specifying the null-interface. > ;; ordering interface has two parameters > (define-interface ordering (bool-T elt-T) > ;; return value of `lt' method satisfies bool-T interface > (method (bool-T lt) (elt-T a) (elt-T b))) > > (define-realized-interface integer-ordering (ordering boolean integer) > ;; bind lt method to built-in `<' > (method lt <)) > > (defgeneric max (< a b) > "Determine the larger of a and b according to <") > > (define-inline-method max ((ordering <) &opaque a b) > "Determine the larger of a and b according to ordering <" > ;; < is treated syntactically as a dispatcher > (if (< lt a b) a b)) > And this > ;; because elisp does not allow applications in the operator position > (define-inlined-method integer-max (max integer-ordering)) should be (define-inlined-method integer-max (max integer-ordering null-realized null-realized)) > ;; Alternatively, > ;; (integer-ordering lt) reduces at compile time to something like > ;; #s(interface-method > ;; #s(interface-realization > ;; #s(interface ordering boolean integer) > ;; <) > ;; lt > ;; <) > ;; which `max' is specialized for by define-inline-method above, so > (defun integer-max (a b) > ;; inlined by compile-time dispatch of the max generic method > (max (integer-ordering lt) a b)) > > ;; These should produce t > (= (max integer-ordering 5 6) (integer-max 5 6)) > (= (max (integer-ordering lt) 5 6) (integer-max 5 6)) > (macroexp-const-p (macroexpand-all '(max (integer-ordering lt) 5 6))) > Lynn ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: inline function expansion 2023-05-27 14:34 ` Lynn Winebarger 2023-05-28 14:12 ` Lynn Winebarger @ 2023-05-28 14:57 ` Stefan Monnier 2023-05-28 22:42 ` Lynn Winebarger 1 sibling, 1 reply; 25+ messages in thread From: Stefan Monnier @ 2023-05-28 14:57 UTC (permalink / raw) To: Lynn Winebarger; +Cc: Philip Kaludercic, help-gnu-emacs > For my purposes, interfaces and realizations of interfaces are just a > way to specify bindings of symbols in a more structured and > encapsulated way than raw macros. > I'm still spit-balling here, but I'm thinking a generic > compile-time-dispatched (inlineable) "max" might be defined along > these lines: > > ;; ordering interface has two parameters > (define-interface ordering (bool-T elt-T) > ;; return value of `lt' method satisfies bool-T interface > (method (bool-T lt) (elt-T a) (elt-T b))) > > (define-realized-interface integer-ordering (ordering boolean integer) > ;; bind lt method to built-in `<' > (method lt <)) > > (defgeneric max (< a b) > "Determine the larger of a and b according to <") > > (define-inline-method max ((ordering <) &opaque a b) > "Determine the larger of a and b according to ordering <" > ;; < is treated syntactically as a dispatcher > (if (< lt a b) a b)) > > ;; because elisp does not allow applications in the operator position > (define-inlined-method integer-max (max integer-ordering)) > ;; Alternatively, > ;; (integer-ordering lt) reduces at compile time to something like > ;; #s(interface-method > ;; #s(interface-realization > ;; #s(interface ordering boolean integer) > ;; <) > ;; lt > ;; <) > ;; which `max' is specialized for by define-inline-method above, so > (defun integer-max (a b) > ;; inlined by compile-time dispatch of the max generic method > (max (integer-ordering lt) a b)) > > ;; These should produce t > (= (max integer-ordering 5 6) (integer-max 5 6)) > (= (max (integer-ordering lt) 5 6) (integer-max 5 6)) > (macroexp-const-p (macroexpand-all '(max (integer-ordering lt) 5 6))) OK, that helps me understand a bit what you're talking about, thanks. This example seems rather uninteresting (lots of extra code for very little gain), so I strongly suspect this is not your prime-motivator. What's the over-arching goal? I think `define-inline` is definitely not a good starting point for the above. OTOH you might be able to layer your `define-inlined-method` on top of the current `cl-generic.el`. AFAICT the main thing you need is some way to write a "call with enough «type» annotations" such that the dispatch can be performed at compile time. >> >> I'm still not sure why you're not using a `compiler-macro` which seems >> >> to be exactly what you're after. >> > >> > I'm very finicky I suppose. I want to get constant expression >> > evaluation as automatically as possible, to enable the compile-time >> > dispatch cleanly. Or are you saying that generic methods can be >> > directly made into compiler macros? >> >> And here I'm lost as well. AFAICT there's just as little "directly made >> into" for define-inline as for compiler-macros (`define-inline` is just >> a way to define a function and its `compiler-macro` in one go). > > Hopefully the example above clarified what I'm talking about Somewhat. Still doesn't explain why you're focusing on `define-inline` rather than `compiler-macro`. A compiler macro is just like a macro except: - It shares its name with a function. - The returned expansion should behave identically to the way the function call would behave. - It's unspecified when or even *if* it's expanded (if it's not expanded, the function is called at runtime instead). - It receives an additional argument (the whole sexp), which it can use to say "I don't want to expand anything this time, just call the function at runtime instead". IOW it's a mechanism to implement compile-time (well, macroexpansion-time in our case) optimizations. > Although I would change "inline-const-p" to test for function purity > (exclude stateful closures or otherwise impure functions). I think that would be a mistake. "const-p" doesn't mean that the return value is pure but that the expression itself always returns the same value. Stefan ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: inline function expansion 2023-05-28 14:57 ` Stefan Monnier @ 2023-05-28 22:42 ` Lynn Winebarger 2023-05-29 2:59 ` Stefan Monnier 0 siblings, 1 reply; 25+ messages in thread From: Lynn Winebarger @ 2023-05-28 22:42 UTC (permalink / raw) To: Stefan Monnier; +Cc: Philip Kaludercic, help-gnu-emacs On Sun, May 28, 2023 at 10:57 AM Stefan Monnier <monnier@iro.umontreal.ca> wrote: > > For my purposes, interfaces and realizations of interfaces are just a > > way to specify bindings of symbols in a more structured and > > encapsulated way than raw macros. > > I'm still spit-balling here, but I'm thinking a generic > > compile-time-dispatched (inlineable) "max" might be defined along > > these lines: > > > > ;; ordering interface has two parameters > > (define-interface ordering (bool-T elt-T) > > ;; return value of `lt' method satisfies bool-T interface > > (method (bool-T lt) (elt-T a) (elt-T b))) > > > > (define-realized-interface integer-ordering (ordering boolean integer) > > ;; bind lt method to built-in `<' > > (method lt <)) > > > > (defgeneric max (< a b) > > "Determine the larger of a and b according to <") > > > > (define-inline-method max ((ordering <) &opaque a b) > > "Determine the larger of a and b according to ordering <" > > ;; < is treated syntactically as a dispatcher > > (if (< lt a b) a b)) > > > > ;; because elisp does not allow applications in the operator position > > (define-inlined-method integer-max (max integer-ordering)) > > ;; Alternatively, > > ;; (integer-ordering lt) reduces at compile time to something like > > ;; #s(interface-method > > ;; #s(interface-realization > > ;; #s(interface ordering boolean integer) > > ;; <) > > ;; lt > > ;; <) > > ;; which `max' is specialized for by define-inline-method above, so > > (defun integer-max (a b) > > ;; inlined by compile-time dispatch of the max generic method > > (max (integer-ordering lt) a b)) > > > > ;; These should produce t > > (= (max integer-ordering 5 6) (integer-max 5 6)) > > (= (max (integer-ordering lt) 5 6) (integer-max 5 6)) > > (macroexp-const-p (macroexpand-all '(max (integer-ordering lt) 5 6))) > > OK, that helps me understand a bit what you're talking about, thanks. > > This example seems rather uninteresting (lots of extra code for very little > gain), so I strongly suspect this is not your prime-motivator. Both parts of that are true - I was trying to come up with the smallest example that would illustrate the essence of the interface specification and dispatch mechanism. > What's the over-arching goal? The overhead makes more sense if you want to replace NxM functions by N interface realizations and M generic functions mediated through the same interface(s). My specific itch came about when I started rewriting my "unboxed" package file management code to run in asynchronous batches, where there are multiple types of files being managed, namely "source" files (files in the package archive), installed files, and generated files (e.g. elc and eln files), and I need to keep indexed collections of each type in at least two contexts - one for the asynchronous batch job, and one in the larger transaction split into batch jobs. In this case, there is one (or more) generic function, (each) with two compile-time interface parameters, one with 3 realizations and one with 2 (or more) realizations. There are obviously other applications. For example, a while ago I provided an implementation of Nuutilla's transitive closure algorithm with a set of very particular implementation choices. With the sort of mechanism described above, I could parameterize the code in terms of the node, stack, and set implementation for various use cases/performance characteristics. > > I think `define-inline` is definitely not a good starting point for > the above. If you mean literally the current implementation of define-inline, I'd agree, except for the trick of defining both a compile-time (macro) and a residual function. I definitely see what I've described above as a generalization of "define-inline" to generic functions (sans the need for the inline-* syntax variants), particularly in the sense of being able to see the inlining at source level using macroexpand-all. AFAICT, there is no (meaningful) way to inline generic functions currently, Even if there were, what you would see after macroexpand-all would be the code for performing the dynamic dispatch, which is not particularly desirable. What I'd like to see after inlining a generic function is a call to the specific method implementing the generic function in that case. In that case the inlined code is about the same size as the original code, but skips both a function call and the dynamic calculation of the dispatch. Plus, as a user, it's meaningful that the inlining transforms a call to a user-defined function by a call to a (more specific) user-defined function, rather than a bunch of dispatch code that is really full of hairy implementation details (of generic functions). To make the above concrete, I mentioned in my prior follow-up email that a "define-inline-generic" form for the generic max would produce a closure of the form (lambda (<-realized a-realized b-realized) ((lambda (a b) ...) (realized-interface-value a) (realized-interface-value b))) But that inner lambda would actually be bound to a symbol (inspired by the symbols used for generalized variables) generated by #0=(intern (format "%S" `(max ,<-realized ,a-realized ,b-realized))) So that the inlined form of (max (integer-ordering lt) a b) would be (#0 a b), where the "realized-interface-value" of the opaque parameters is simply the expression in the corresponding operand position. Another aspect that may not be clear is that the appearance of the parameters in operator position correspond to where "define-inline" would use "unquote", and everything else is implicitly "inline-quote"-ed. Finally, the system I described provides a kind of modular macro system that could address some of the issues with macros noted in the thread "bug#62762: circular dependencies in elisp files and make" on the bug-gnu-emacs mailing list. That is, if inlined-generic functions were used in place of macros (where it makes sense), then you would wind up with calls to those specialized functions that have the realized-interface object encoded in the symbol, which is a kind of dynamic linkage (assuming those function calls were not further inlined). I'm not saying every instance of macros could be replaced by one of these compile-time generics, but I'm sure there are some places where they could be employed. > OTOH you might be able to layer your `define-inlined-method` > on top of the current `cl-generic.el`. AFAICT the main thing you need is > some way to write a "call with enough «type» annotations" such that the > dispatch can be performed at compile time. I think that's the main point of designating parameters as "opaque", so they aren't really specialized against, and only the non-opaque parameters need to be wrapped in a form like (integer-ordering lt). I'm still not certain that's enough to avoid needing a full-fledged partial-evaluator embedded in the macroexpansion phase. > >> >> I'm still not sure why you're not using a `compiler-macro` which seems > >> >> to be exactly what you're after. > >> > > >> > I'm very finicky I suppose. I want to get constant expression > >> > evaluation as automatically as possible, to enable the compile-time > >> > dispatch cleanly. Or are you saying that generic methods can be > >> > directly made into compiler macros? > >> > >> And here I'm lost as well. AFAICT there's just as little "directly made > >> into" for define-inline as for compiler-macros (`define-inline` is just > >> a way to define a function and its `compiler-macro` in one go). > > > > Hopefully the example above clarified what I'm talking about > > Somewhat. Still doesn't explain why you're focusing on `define-inline` > rather than `compiler-macro`. A compiler macro is just like a macro > except: I suppose I see the above as a kind of generalization of the kind of thing your paper described "define-inline" doing for "cl-typep" to generic functions. I mean, I want the max inliner to be "max" itself, specialized on some either s-expressions directly or some specially-wrapped form of s-expressions. I mean, that would be the most elegant solution to my mind. > - It shares its name with a function. > - The returned expansion should behave identically to the > way the function call would behave. > - It's unspecified when or even *if* it's expanded (if it's not > expanded, the function is called at runtime instead). > - It receives an additional argument (the whole sexp), which it can use > to say "I don't want to expand anything this time, just call the > function at runtime instead". > > IOW it's a mechanism to implement compile-time (well, > macroexpansion-time in our case) optimizations. > > > Although I would change "inline-const-p" to test for function purity > > (exclude stateful closures or otherwise impure functions). > > I think that would be a mistake. "const-p" doesn't mean that the return > value is pure but that the expression itself always returns the same value. I think you mean the argument value, not the return value? But that's exactly what I mean - a pure function is immutable, and other functions change with state, so that in the extensional sense they are not equal even if they are eq. For the purpose of partial evaluation, which I believe is the point of inline-const-p, an expression `expr' is constant if (equal (eval expr state0) (eval expr state1)) is true for all possible values of state0 and state1 (and eq is inherently dependent on those state values, so is not a useful test for this definition). Clearly exprs that are syntactic literals are constants by this definition. Also, if `f' is a pure function, then (f C1 C2 .. Cn) for constant expressions C1,..Cn is also a constant expression. But that is not the case for impure functions. An example is `format', which depends on the setting of variables used by the print functions. (format "%S" '(<some complicated structure>)) may produce distinct strings depending on the state, despite both arguments being literals. Considering the semantic value of a function to be given a mapping from "State -> Value -> Value", then `+' is constant function of state while `format' is not. Or, `+' is a constant, and `format' is not. On the other hand, if that call to format is the body of a let expression binding those (dynamic) variables to constants, then the whole let expression will be a constant expression. Likewise, lisp constructors that have a readable print-syntax are not inherently constant even if all arguments are constant expressions, but if such a constant constructor expression appears as an argument to a pure function in which all arguments are likewise either constant or constant constructor expressions, then the expression as a whole (with the pure function in operator position) will be constant according to the definition above (i.e. (equal (eval expr state0) (eval expr state1)) is true for all state0 and state1). Lynn ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: inline function expansion 2023-05-28 22:42 ` Lynn Winebarger @ 2023-05-29 2:59 ` Stefan Monnier 2023-06-06 22:38 ` Lynn Winebarger 0 siblings, 1 reply; 25+ messages in thread From: Stefan Monnier @ 2023-05-29 2:59 UTC (permalink / raw) To: Lynn Winebarger; +Cc: Philip Kaludercic, help-gnu-emacs >> I think `define-inline` is definitely not a good starting point for >> the above. > > If you mean literally the current implementation of define-inline, I'd > agree, except for the trick of defining both a compile-time (macro) > and a residual function. I see. I'm maybe too close to `define-inline`s design to see the larger picture (FWIW, the driving design is to take a compiler-macro and use it to generate the body of the function it is meant to optimize, because it puts the user of `define-inline` in charge of making it work, whereas the usual design is to start with the *function*'s body and automatically infer how to inline it, but that requires figuring out what should be inlined, avoiding name captures, etc..). >> OTOH you might be able to layer your `define-inlined-method` >> on top of the current `cl-generic.el`. AFAICT the main thing you need is >> some way to write a "call with enough «type» annotations" such that the >> dispatch can be performed at compile time. > I think that's the main point of designating parameters as "opaque", > so they aren't really specialized against, and only the non-opaque > parameters need to be wrapped in a form like (integer-ordering lt). > I'm still not certain that's enough to avoid needing a full-fledged > partial-evaluator embedded in the macroexpansion phase. Indeed, the difficulty I see is how to propagate info for cascading inlines (when inlining one call exposes info that (should) make another call inlinable). In most compilers, this is just "normal business" done by combining inlining with constant propagation, constant folding, etc.. (which you can call "partial evaluation"). But if you want to do it as part of macroexpansion, then you can't rely on the compiler's constant propagation, and constant folding. >> > Although I would change "inline-const-p" to test for function purity >> > (exclude stateful closures or otherwise impure functions). >> >> I think that would be a mistake. "const-p" doesn't mean that the return >> value is pure but that the expression itself always returns the same value. > > I think you mean the argument value, not the return value? No, I mean that `inline-const-p` tells us something about the EXP it receives, and this something is not that this EXP's return value is pure, but that this EXP always returns the same value. It's arguably a misnomer, what it really means is "do we already know what EXP will return?". This is evidenced by the fact that when we generate the function, the `inline-const-p` call is just replaced by t (because once we get to the function, EXP has already been evaluated so of course we know the value). > But that's exactly what I mean - a pure function is immutable, and > other functions change with state, so that in the extensional sense > they are not equal even if they are eq. > > For the purpose of partial evaluation, which I believe is the point of > inline-const-p, an expression `expr' is constant if > (equal (eval expr state0) (eval expr state1)) I agree. And indeed if `exp` is an expression which always returns `insert` or `buffer-substring`, then (equal (eval expr state0) (eval expr state1)) is true, even though `insert/buffer-substring` are definitely not pure functions. > Clearly exprs that are syntactic literals are constants by this > definition. Also, if `f' is a pure function, then (f C1 C2 .. Cn) for > constant expressions C1,..Cn is also a constant expression. Indeed, tho currently `inline-const-p` doesn't take advantage of that. Stefan ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: inline function expansion 2023-05-29 2:59 ` Stefan Monnier @ 2023-06-06 22:38 ` Lynn Winebarger 0 siblings, 0 replies; 25+ messages in thread From: Lynn Winebarger @ 2023-06-06 22:38 UTC (permalink / raw) To: Stefan Monnier; +Cc: Philip Kaludercic, help-gnu-emacs On Sun, May 28, 2023 at 10:59 PM Stefan Monnier <monnier@iro.umontreal.ca> wrote: > >> I think `define-inline` is definitely not a good starting point for > >> the above. > > > > If you mean literally the current implementation of define-inline, I'd > > agree, except for the trick of defining both a compile-time (macro) > > and a residual function. > > I see. I'm maybe too close to `define-inline`s design to see the larger > picture (FWIW, the driving design is to take a compiler-macro and use > it to generate the body of the function it is meant to optimize, > because it puts the user of `define-inline` in charge of making it > work, whereas the usual design is to start with the *function*'s body > and automatically infer how to inline it, but that requires figuring > out what should be inlined, avoiding name captures, etc..). It might be worth revisiting how I got to this approach. As I mentioned, this arose when trying to implement processing files of a set of packages, which may include hundreds or even thousands of packages (so tens of thousands of records for individual files), in asynchronous chunks to reduce hours of processing to minutes if enough cores are available. My original (synchronous) approach used closures in the file records to do the "right thing" for the type of file. However, the collection of file records ("database") is deeply recursive, so just exporting the closure to an asynchronous job wrote out the entire collection, prompting me to look at "generic" functions for dispatching correctly without embedding a closure in the file records. Such a procedure needed at least two arguments for specialization, since each type of file record has different indexing keys for different phases of processing (whether synchronous or asynchronous). For example, I copy all elisp source files from the packages in a transaction before updating the autoloads file in the target directory (containing all of them in an "unboxed" location), and then byte-compiling them. A key that is guaranteed unique in one context (where files are in the source directory) may not be so guaranteed in the target directory, and the algorithm should be able to detect that as an error condition at least. In other cases, the destination files will have different paths relative to the target directory than in the source directory (usually package-relative for data files) to guarantee uniqueness. So originally I was looking at how to use generic functions specialized to dispatch on the type of file record structure and a (constant) symbol for the correct key in a given context. That's why I was interested particularly in the definition of cl-typep. I consider hard-coding those symbols to be a very ugly approach. I actually started by writing up specialized functions for each case, but it's too much repetition of the same code with slightly different "parameters" (in the sense of macros). > >> OTOH you might be able to layer your `define-inlined-method` > >> on top of the current `cl-generic.el`. AFAICT the main thing you need is > >> some way to write a "call with enough «type» annotations" such that the > >> dispatch can be performed at compile time. > > I think that's the main point of designating parameters as "opaque", > > so they aren't really specialized against, and only the non-opaque > > parameters need to be wrapped in a form like (integer-ordering lt). > > I'm still not certain that's enough to avoid needing a full-fledged > > partial-evaluator embedded in the macroexpansion phase. > > Indeed, the difficulty I see is how to propagate info for cascading > inlines (when inlining one call exposes info that (should) make another > call inlinable). In most compilers, this is just "normal business" done > by combining inlining with constant propagation, constant folding, > etc.. (which you can call "partial evaluation"). But if you want to do > it as part of macroexpansion, then you can't rely on the compiler's > constant propagation, and constant folding. Yes. The problem with leaving it in the compiler proper is that the output is byte-code. I want a source-to-source transformation so I can check that the output is what I would expect. But as I mentioned below, if I attempt to do it without taking over the entire macroexpansion process, there's a risk of an exponential number of macro-expansion visits to AST nodes, especially since some of the checks for const-ness may already require multiple visits (for constructor expressions - one to check that all arguments are const, and one to actually evaluate it at compile-time *if* the whole expression is constant). I want to be clear that I don't see "interface" here in terms of types or type inference, but as a way of writing modular macros. There could be some type-inferencing built on top of this mechanism, but that's not really my purpose. Like define-inline, I expect define-inline-generic/define-inline-method (or whatever) to be responsible for indicating where the constant-folding opportunities are. I just prefer marking the variables and generating all the rest where possible. If the appearance of parameters in the operator position is considered to be a variant of "unquote", where the code that would be in the "unquote" form is specified in the interface realization (this is another way of giving the user control), then the only remaining question is where the corresponding form of "inline-quote" should go. I think the correct answer must be "around the whole body" of the function, since the user can always use interfaces to define the sub-expressions that should be evaluated at compile time if constant (and not marked opaque). Alternatively, arguments not marked "&opaque" could be marked by "&constexpr" or similar instead, and dropping the &opaque marker. I think I prefer marking &opaque because it's an unconditional state of individual parameters (i.e. never evaluate at compile-time) where &constexpr (or whatever) would mean "evaluate at compile-time if all &constexpr operands expressions are constant". I believe this could be used for an alternative, possibly friendlier, form of define-inline, say define-inline*, so that ;; parameters appear as "unquote" operators in body... (define-inline-template* f* (x1 .. xN &opaque y1 .. yM) body ...) (define-inline* f (f* realization-x1 ... realization-xN realization-y1 ... realization-yN)) would become (define-inline f (x1 ... xN y1 ... yM) (inline-letevals (y1 ... yM) (let ((x1 (inline-const-value x1)) ... (xN (inline-const-value xN))) (inline-quote (progn body)[x1/realization-x1,...xN/realization-xN,y1/realization-y1,...yM/realization-yM])))) using the bracket notation for capture-free substitution. > >> > Although I would change "inline-const-p" to test for function purity > >> > (exclude stateful closures or otherwise impure functions). > >> > >> I think that would be a mistake. "const-p" doesn't mean that the return > >> value is pure but that the expression itself always returns the same value. > > > > I think you mean the argument value, not the return value? > > No, I mean that `inline-const-p` tells us something about the EXP it receives, > and this something is not that this EXP's return value is pure, but that > this EXP always returns the same value. It's arguably a misnomer, what > it really means is "do we already know what EXP will return?". Ok, you meant the return value of EXP, or of (eval EXP), not of inline-const-p. > > This is evidenced by the fact that when we generate the function, > the `inline-const-p` call is just replaced by t (because once we get to > the function, EXP has already been evaluated so of course we know the > value). > > > But that's exactly what I mean - a pure function is immutable, and > > other functions change with state, so that in the extensional sense > > they are not equal even if they are eq. > > > > For the purpose of partial evaluation, which I believe is the point of > > inline-const-p, an expression `expr' is constant if > > (equal (eval expr state0) (eval expr state1)) > > I agree. And indeed if `exp` is an expression which always returns > `insert` or `buffer-substring`, then > > (equal (eval expr state0) (eval expr state1)) > > is true, even though `insert/buffer-substring` are definitely not > pure functions. I'm trying to understand your conclusion, and it seems to me there is some kind of value/reference distinction going on here. I used "equal" and "eval" above, but it might be better to use "equal*" and "eval*" because we must compare values completely by extension as "eq" is entirely missing. So, for example, symbols must be compared by name and some state-independent representation of the ob-stack in which they are interned, with a nil ob-stack meaning never equal*. What this would entail for "functions" that are not side-effect-free is an interesting question. The answer depends on how you intend to test for equality. If I understand your point, since the free variables in (functions like) insert and buffer-substring resolve in whatever state is passed in, "function name references" should be judged as equal, where closures with no free variables can be tested extensionally by comparing their code and environments. OTOH, if the purpose of a "const-p" test is to discriminate when an expression can be safely evaluated at compile-time, then this notion of equality must fail. For example, if we define state1 := (eval '(mapcar #'insert '("foo" "bar")) state0) state2 := (eval '(eval-when-compile (mapcar #'insert '("foo" "bar"))) state0) then state1 will not equal state2. But if you want to dispatch pcase on a subr value, i.e. (pcase (inline-const-value x) (<clause> ... (#'insert <some code>) <clause> ...), then presumably dispatching the pcase at compile-time is the desired result. However, (funcall (inline-const-value x) "foo") would not give the desired result (if x were bound to #'insert or #'buffer-substring). So, I would say, if function "f" has free variables x1,...,xN, then "(eval* #'f state)" above must return something equivalent to evaluating the expression (with lexical-binding in effect): (let ((x1 (eval* 'x1 state)) ... (xN (eval* 'xN state)) (f #'f)) (lambda (&rest args) (apply f args))) Or, we could replace the (eval* 'EXP state) with (eval-when-compile EXP) for an equivalent effect. If "f" is side-effect free, then the last expression is a pure function. If one of the variables xI has a non-serializable value (like #'insert or #'buffer-substring), then the result is an error when reading the value back in. Unfortunately, since cl-typep is the only piece of code that (as far as I know) actually makes use of inline-const-value, and, AFAIK, would never expect a function like #'insert or #'buffer-substring in the type argument, it's not clear how inline-const-value is intended to behave in the "pcase" and "funcall" examples respectively. This discussion has been helpful to me in hashing out the "right" definition for what I'm calling interfaces/interface-realizations and inlined-generics and inline-methods. Thanks for the time you've spent reading and responding. Lynn ^ permalink raw reply [flat|nested] 25+ messages in thread
end of thread, other threads:[~2023-06-06 22:38 UTC | newest] Thread overview: 25+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2023-05-07 14:32 inline function expansion Lynn Winebarger 2023-05-07 17:51 ` Basile Starynkevitch 2023-05-07 19:48 ` Philip Kaludercic 2023-05-07 20:16 ` Lynn Winebarger 2023-05-08 0:21 ` Emanuel Berg 2023-05-08 11:12 ` Lynn Winebarger 2023-05-08 2:03 ` Lynn Winebarger 2023-05-11 7:11 ` Lynn Winebarger 2023-05-12 6:25 ` Emanuel Berg 2023-05-18 14:56 ` Lynn Winebarger 2023-05-19 13:31 ` Stefan Monnier 2023-05-20 14:18 ` Lynn Winebarger 2023-05-20 15:32 ` Stefan Monnier 2023-05-21 12:47 ` Lynn Winebarger 2023-05-18 18:29 ` Stefan Monnier 2023-05-19 0:22 ` Lynn Winebarger 2023-05-19 13:07 ` Stefan Monnier 2023-05-20 15:01 ` Lynn Winebarger 2023-05-20 15:48 ` Stefan Monnier 2023-05-27 14:34 ` Lynn Winebarger 2023-05-28 14:12 ` Lynn Winebarger 2023-05-28 14:57 ` Stefan Monnier 2023-05-28 22:42 ` Lynn Winebarger 2023-05-29 2:59 ` Stefan Monnier 2023-06-06 22:38 ` Lynn Winebarger
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).