www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Forcing inlining of delegates and lazy

reply ABrightLight <example example.com> writes:
Hello. I am trying to figure out some way to get delegates or 
lazy parameters to get inlined into the function they are called 
with. For example, if we take this starting code:

```d
void foo () {
   bar.blah(args);
   bar.otherBlah;

   foreach (item; bar.collection) {
     item.action;
   }

   bar.moreBlah;
}
```
I like to rewrite this as:
```d
void withBoilerplate (lazy void expr) {
   bar.blah(args);
   bar.otherBlah;

   expr;

   bar.moreBlah;
}

void foo () {
   withBoilerplate(
     foreach (item; bar.collection) {
       item.action;
     }
   );
}
```
You may notice that these sorts of rewrites are very common in 
languages with AST macros, such as lisp. For example the 
equivalent would be:
```lisp
(defmacro with-boilerplate (expr)
   `(progn
      (blah bar args)
      (other-blah bar)

      ,expr

      (more-blah bar)))

(defun foo ()
   (with-boilerplate
     (loop for item across (collection bar) do
       (action item))))
```
These sorts of macros greatly aid in readability and 
maintainability.

The drawback to the lazy/delegate approach so far is the extra 
indirection of the function call. And the use of `pragma(inline)` 
hasn't helped. Is there some way to get it to inline so that I 
can use this sort of pattern in hot-code paths without the worry 
that I'm introducing slowdowns in the name of better readability 
and maintainability?
Aug 31
next sibling parent reply monkyyy <crazymonkyyy gmail.com> writes:
On Monday, 31 August 2026 at 17:36:55 UTC, ABrightLight wrote:
 Hello. I am trying to figure out some way to get delegates or 
 lazy parameters to get inlined into the function they are 
 called with.
 ```lisp
 ```
 These sorts of macros greatly aid in readability and 
 maintainability.

 The drawback to the lazy/delegate approach so far is the extra 
 indirection of the function call.
Are you not making a type error? A delegate is a runtime function pointer and context, macros and inlining are ct depending on use I think whatever your trying to do would naturally work if it was static functions gathered up at compile time
Aug 31
parent ABrightLight <example example.com> writes:
On Monday, 31 August 2026 at 18:18:59 UTC, monkyyy wrote:
 On Monday, 31 August 2026 at 17:36:55 UTC, ABrightLight wrote:
 Hello. I am trying to figure out some way to get delegates or 
 lazy parameters to get inlined into the function they are 
 called with.
 ```lisp
 ```
 These sorts of macros greatly aid in readability and 
 maintainability.

 The drawback to the lazy/delegate approach so far is the extra 
 indirection of the function call.
Are you not making a type error? A delegate is a runtime function pointer and context, macros and inlining are ct depending on use I think whatever your trying to do would naturally work if it was static functions gathered up at compile time
It shouldn't be a type error. My thought is that since the lazy expression (lowers to a delegate) is actually known to be eager here, and it's not pointing to some delegate made elsewhere (it's essentially a literal), it should be entirely possible to just inline into the equivalent form in the first sample that does not rely on lazy.
Aug 31
prev sibling next sibling parent reply ABrightLight <example example.com> writes:
On Monday, 31 August 2026 at 17:36:55 UTC, ABrightLight wrote:
 Hello. I am trying to figure out some way to get delegates or 
 lazy parameters to get inlined into the function they are 
 called with. For example, if we take this starting code:

 ```d
 void foo () {
   bar.blah(args);
   bar.otherBlah;

   foreach (item; bar.collection) {
     item.action;
   }

   bar.moreBlah;
 }
 ```
 I like to rewrite this as:
 ```d
 void withBoilerplate (lazy void expr) {
   bar.blah(args);
   bar.otherBlah;

   expr;

   bar.moreBlah;
 }

 void foo () {
   withBoilerplate(
     foreach (item; bar.collection) {
       item.action;
     }
   );
 }
 ```
 You may notice that these sorts of rewrites are very common in 
 languages with AST macros, such as lisp. For example the 
 equivalent would be:
 ```lisp
 (defmacro with-boilerplate (expr)
   `(progn
      (blah bar args)
      (other-blah bar)

      ,expr

      (more-blah bar)))

 (defun foo ()
   (with-boilerplate
     (loop for item across (collection bar) do
       (action item))))
 ```
 These sorts of macros greatly aid in readability and 
 maintainability.

 The drawback to the lazy/delegate approach so far is the extra 
 indirection of the function call. And the use of 
 `pragma(inline)` hasn't helped. Is there some way to get it to 
 inline so that I can use this sort of pattern in hot-code paths 
 without the worry that I'm introducing slowdowns in the name of 
 better readability and maintainability?
To clarify in case the code isn't clear - there is a lot of code I have that essentially does some boilerplate prior to some expression and then there is more more after the expression. To avoid having to keep inserting the same code around the actual expression, I place the boilerplate into a function that accepts the body/expression to "insert" between the boilerplate. I don't think any mixin will be able to do this as cleanly or as clearly.
Aug 31
next sibling parent monkyyy <crazymonkyyy gmail.com> writes:
On Monday, 31 August 2026 at 20:08:28 UTC, ABrightLight wrote:
 
 I don't think any mixin will be able to do this as cleanly or 
 as clearly.
your not saying enough to guess what the hard part is, if I take you at your word uda file seems like it should work to grab functions: ```d --- bar.d import std; "grab": void sayhi(){ "hi".writeln; } void saybye(){ "bye".writeln; } --- main.d import std; void boilplate(alias F)(){ "runnning ".writeln(F.stringof); F(); "finished ".writeln(F.stringof); } void runbars(){ import bar; static foreach(F;getSymbolsByUDA!(bar,"grab")){ boilplate!F; }} unittest{ runbars(); } ```
Aug 31
prev sibling parent limepoutine <ztang013 foxmail.com> writes:
On Monday, 31 August 2026 at 20:08:28 UTC, ABrightLight wrote:
 On Monday, 31 August 2026 at 17:36:55 UTC, ABrightLight wrote:
 Hello. I am trying to figure out some way to get delegates or 
 lazy parameters to get inlined into the function they are 
 called with. For example, if we take this starting code:

 ```d
 void foo () {
   bar.blah(args);
   bar.otherBlah;

   foreach (item; bar.collection) {
     item.action;
   }

   bar.moreBlah;
 }
 ```
 I like to rewrite this as:
 ```d
 void withBoilerplate (lazy void expr) {
   bar.blah(args);
   bar.otherBlah;

   expr;

   bar.moreBlah;
 }

 void foo () {
   withBoilerplate(
     foreach (item; bar.collection) {
       item.action;
     }
   );
 }
 ```
 You may notice that these sorts of rewrites are very common in 
 languages with AST macros, such as lisp. For example the 
 equivalent would be:
 ```lisp
 (defmacro with-boilerplate (expr)
   `(progn
      (blah bar args)
      (other-blah bar)

      ,expr

      (more-blah bar)))

 (defun foo ()
   (with-boilerplate
     (loop for item across (collection bar) do
       (action item))))
 ```
 These sorts of macros greatly aid in readability and 
 maintainability.

 The drawback to the lazy/delegate approach so far is the extra 
 indirection of the function call. And the use of 
 `pragma(inline)` hasn't helped. Is there some way to get it to 
 inline so that I can use this sort of pattern in hot-code 
 paths without the worry that I'm introducing slowdowns in the 
 name of better readability and maintainability?
To clarify in case the code isn't clear - there is a lot of code I have that essentially does some boilerplate prior to some expression and then there is more more after the expression. To avoid having to keep inserting the same code around the actual expression, I place the boilerplate into a function that accepts the body/expression to "insert" between the boilerplate. I don't think any mixin will be able to do this as cleanly or as clearly.
Lisp macros do really translate well to template mixins. That being said, D has no `(gensym)`, no unit types, nor many of lisp conveniences we're familiar with, so the implementation would be a bit awkward. ``` import std.stdio; import std.traits; bool inUse; mixin template withBoilerplate(alias dg) if (isCallable!dg) { static if (is(typeof(dg()) ReturnType == void)) { typeof(null) withBoilerplateResult = { inUse = true; dg(); inUse = false; return null; }(); } else { ReturnType withBoilerplateResult = { inUse = true; ReturnType value = dg(); inUse = false; return value; }(); } } void main() { mixin withBoilerplate!({ writeln("Hello, world"); }); mixin withBoilerplate!({ writeln("Goodbye, world"); }); } ```
Sep 01
prev sibling next sibling parent reply Mindy Batek (0xEAB) <desisma heidel.beer> writes:
On Monday, 31 August 2026 at 17:36:55 UTC, ABrightLight wrote:
 And the use of `pragma(inline)` hasn't helped.
Have you tried LLVM’s `always_inline`? Supposedly available as `llvmAttr("always_inline")`. Docs: <https://clang.llvm.org/docs/AttributeReference.html#always-inline-force-inline>
Aug 31
parent FinalEvilution <FinalEvilution gmail.com> writes:
On Tuesday, 1 September 2026 at 00:02:25 UTC, Mindy Batek (0xEAB) 
wrote:
 On Monday, 31 August 2026 at 17:36:55 UTC, ABrightLight wrote:
 And the use of `pragma(inline)` hasn't helped.
Have you tried LLVM’s `always_inline`? Supposedly available as `llvmAttr("always_inline")`. Docs: <https://clang.llvm.org/docs/AttributeReference.html#always-inline-force-inline>
I just checked and ldc uses the LLVM IR version "alwaysinline" ` llvmAttr("alwaysinline")` I tested both the pragma and the llvmAttr version and both produce the exact same llvm ir with an alwaysinline attribute attached to the function. ``` ; Function Attrs: alwaysinline uwtable define void _D5clean5timesFiLvZv(i32 %n_arg, { ptr, ptr } ... "target-cpu"="x86-64" "target-features"="+cx16" } ``` ``` import std.stdio : writeln; import ldc.attributes; void main() { int x; 3.times(writeln(x++)); } //pragma(inline, true) // llvmAttr("alwaysinline") void times(int n, lazy void exp) { while (n--) exp(); } ```
Aug 31
prev sibling parent reply FinalEvilution <FinalEvilution gmail.com> writes:
On Monday, 31 August 2026 at 17:36:55 UTC, ABrightLight wrote:
 Hello. I am trying to figure out some way to get delegates or 
 lazy parameters to get inlined into the function they are 
 called with. For example, if we take this starting code:
I tried building a simple program with the ldc compiler and optimization and it inlined correctly. At least in this case. ``` import std.stdio : writeln; void main() { int x; 3.times(writeln(x++)); } pragma(inline, true) void times(int n, lazy void exp) { while (n--) exp(); } ``` Are you possibly using dmd? It doesn't have the fancy optimizing backends that gdc and ldc have.
Aug 31
parent reply ABrightLight <example example.com> writes:
On Tuesday, 1 September 2026 at 03:01:00 UTC, FinalEvilution 
wrote:
 On Monday, 31 August 2026 at 17:36:55 UTC, ABrightLight wrote:
 Hello. I am trying to figure out some way to get delegates or 
 lazy parameters to get inlined into the function they are 
 called with. For example, if we take this starting code:
I tried building a simple program with the ldc compiler and optimization and it inlined correctly. At least in this case. ``` import std.stdio : writeln; void main() { int x; 3.times(writeln(x++)); } pragma(inline, true) void times(int n, lazy void exp) { while (n--) exp(); } ``` Are you possibly using dmd? It doesn't have the fancy optimizing backends that gdc and ldc have.
I was using LDC and my code would lower to a jump to the delegate. I'll see if the llvm specific option works.
Sep 01
parent reply FinalEvilution <FinalEvilution gmail.com> writes:
On Tuesday, 1 September 2026 at 14:01:40 UTC, ABrightLight wrote:
 I was using LDC and my code would lower to a jump to the 
 delegate. I'll see if the llvm specific option works.
If the option doesn't work out would you be able to provide a reduced test case? I've become rather curious as to what's going on.
Sep 01
parent reply ABrightLight <example example.com> writes:
On Tuesday, 1 September 2026 at 15:58:12 UTC, FinalEvilution 
wrote:
 On Tuesday, 1 September 2026 at 14:01:40 UTC, ABrightLight 
 wrote:
 I was using LDC and my code would lower to a jump to the 
 delegate. I'll see if the llvm specific option works.
If the option doesn't work out would you be able to provide a reduced test case? I've become rather curious as to what's going on.
Actually in your sample, if you go over to godbolt.org and compile with LDC and -O5 (or -Oz) you'll see that that `expr();` ends up being: ``` mov rdi, r14 call rbx ```
Sep 01
parent reply FinalEvilution <FinalEvilution gmail.com> writes:
On Tuesday, 1 September 2026 at 17:07:59 UTC, ABrightLight wrote:
 On Tuesday, 1 September 2026 at 15:58:12 UTC, FinalEvilution 
 wrote:
 If the option doesn't work out would you be able to provide a 
 reduced test case? I've become rather curious as to what's 
 going on.
Actually in your sample, if you go over to godbolt.org and compile with LDC and -O5 (or -Oz) you'll see that that `expr();` ends up being: ``` mov rdi, r14 call rbx ```
-O5 and -O4 are just aliases and are equivalent to -O3. I just checked myself on goldbolt and ldc inlines and unrolls the loop into `_Dmain` without any calls to `times`. https://godbolt.org/z/sWYzT9sW6 The unused `times` function cannot be eliminated as the binary hasn't been linked yet. Here's another version without the noise of the standard library. Uncomment the `//pragma(inline, false)` to see the difference. https://godbolt.org/z/4Y5q3nv4e The `foo` function cannot be inlined as there is no function body. It is only used for demonstration.
Sep 01
parent reply ABrightLight <example example.com> writes:
On Tuesday, 1 September 2026 at 22:09:04 UTC, FinalEvilution 
wrote:
 On Tuesday, 1 September 2026 at 17:07:59 UTC, ABrightLight 
 wrote:
 On Tuesday, 1 September 2026 at 15:58:12 UTC, FinalEvilution 
 wrote:
 If the option doesn't work out would you be able to provide a 
 reduced test case? I've become rather curious as to what's 
 going on.
Actually in your sample, if you go over to godbolt.org and compile with LDC and -O5 (or -Oz) you'll see that that `expr();` ends up being: ``` mov rdi, r14 call rbx ```
-O5 and -O4 are just aliases and are equivalent to -O3. I just checked myself on goldbolt and ldc inlines and unrolls the loop into `_Dmain` without any calls to `times`. https://godbolt.org/z/sWYzT9sW6 The unused `times` function cannot be eliminated as the binary hasn't been linked yet. Here's another version without the noise of the standard library. Uncomment the `//pragma(inline, false)` to see the difference. https://godbolt.org/z/4Y5q3nv4e The `foo` function cannot be inlined as there is no function body. It is only used for demonstration.
Regarding the compiler flags, I was aware- but I appreciate the tip nevertheless. I expanded the example to: ```d void main () { test1; test2; } void test1 () { int x; 3.times(foo(x++)); } void test2 () { 3.timesAgain; } pragma(inline, true) void times(int n, lazy void exp) { while (n--) { exp(); } } pragma(inline, true) void timesAgain (int n) { int x; while (n--) { foo(x++); } } void foo(int val); ``` It looks like you're right. `main` ends up with 3 calls to `foo` in the first example and then another 3 calls to `foo` as part of the second test, even thought the `times` and `timesAgain` functions remain in the binary and have different instructions. I'll go ahead and rely on this in hotcode. Thanks for your help!
Sep 01
parent FinalEvilution <FinalEvilution gmail.com> writes:
On Wednesday, 2 September 2026 at 01:11:12 UTC, ABrightLight 
wrote:
 It looks like you're right.
 `main` ends up with 3 calls to `foo` in the first example and 
 then another 3 calls to `foo` as part of the second test, even 
 thought the `times` and `timesAgain` functions remain in the 
 binary and have different instructions.

 I'll go ahead and rely on this in hotcode. Thanks for your help!
Your welcome! I find these small optimization problems to be good knowledge exercises for myself. For example i didn't know that each freestanding `writeln` would turn into 3 function calls, even when in sequence. The more you know. https://godbolt.org/z/nPrav4331 If you get stumped on anything else feel free to ask on the Learn forum. If it's in an area i, or someone else know, we may be able to help.
Sep 01