1

I have a D module which I hope contains public and private parts. I have tried using the keywords private and static before function definitions. I have a function that I wish to make externally-callable / public and ideally I would like it to be inlined at the call-site. This function calls other module-internal functions that are intended to be private, i.e. not externally callable. Calls to these are successfully inlined within the module and a lot of the cruft is disposed of by CTFE plus known-constant propagation. However the GDC compiler also generates copies of these internal routines, even though they have been inlined where needed and they are not supposed to be externally callable. I'm compiling with -O3 -frelease. What should I be doing - should I expect this even if I use static and/or private?

I have also taken a brief look at this thread concerning GCC hoping for insight.

As I mentioned earlier, I've tried both using private and static on these internal functions, but I can't seem to suppress the code generation. I could understand this if a debugger needed to have copies of these routines to set breakpoints in. I need to stress that this could perhaps be sorted out somehow at link-time, for all I know. I haven't tried linking the program, I'm just looking at the generated code in the Matt Godbolt D Compiler Explorer using GDC. Everything can be made into templates with a zero-length list of template parameters (e.g. auto my_fn()( in arg_t x ) ), tried that, it doesn't help but does no harm.

A couple of other things to try: I could try and make a static class with private parts, as a way of implementing a package, Ada-style. (Needs to be single-instance strictly.) I've never done any C++, only massive amounts of asm and C professionally. So that would be a learning curve.

The only other thing I can think of is to use nested function definitions, Pascal/Ada-style, move the internal routines to be inside the body of their callers. But that has a whole lot of disadvantages.

Rough example

module junk;

auto my_public_fn() {  return my_private_fn();  }

private
static // 'static' and/or 'private', tried both
auto my_private_fn() { xxx ; return whatever; }
Community
  • 1
  • 1
Cecil Ward
  • 597
  • 2
  • 13
  • I haven't been able to compare LDC with GDC because currently the routines contain some GCC-specific extended asm code. I noticed that the unwanted function bodies are not just those that contain asm btw. – Cecil Ward Feb 27 '17 at 19:53
  • 1
    you definitely don't need `static`. it has no meaning on top-level declarations. – Cauterite Feb 28 '17 at 15:22
  • 1
    "I haven't tried linking the program" — while I don't know much about GDC, it is possible that dead-code-elimination/COMDAT-folding occurs at link-time to makes these functions disappear. Perhaps GDC doesn't bother attempting DCE at this level because the linker is expected to take care of it anyway. I'm just speculating though … – Cauterite Feb 28 '17 at 15:27

1 Answers1

2

I just had a short discussion with Iain about this and implementing this is not as simple as it seems.

First of all static has many meanings in D, but the C meaning of translation unit local function is not one of them ;-)

So marking these functions as private seems intuitive. After all, if you can't access a function from outside of the translation unit and you never leak an address to the function why not remove it? It could be either completely unused or inlined into all callers in this case.

Now here's the catch: We can't know for sure if a function is unused:

private void fooPrivate() {}

/*template*/ void fooPublic()()
{
    fooPrivate();
}

When compiling the file GDC knows nothing about the fooPublic template (as templates can only be fully analyzed when instantiated), so fooPrivate appears to be unused. When later using fooPublic in a different file GDC will rely on fooPrivate being already emitted in the original source - after all it's not a template so it's not being emitted into the new module.

There might be workarounds but this whole problem seems nontrivial. We could also introduce a custom gcc.attribute attribute for this. It would cause the same problems with templates, but as it's a specific annotation for one usecase (unlike private) we could rely on the user to do the right thing.

jpf
  • 527
  • 3
  • 7
  • Would it be possible for a linker to sort this out, like implementing obj modules as a library containing separate pieces? (Had to do something like this by hand once on a huge project decades ago.) Easy enough for _me_ to say, I'm not the one doing the work. Could be a nightmare. I've no idea what GCC C/C++ does. – Cecil Ward Mar 03 '17 at 07:17
  • I don't know how much it would be worth the effort. I was bleating because I wrote functions that were solely employed by CTFE, or always inlined yet the generated body was just never used, left sitting there. – Cecil Ward Mar 03 '17 at 07:20
  • It would matter rather less if you could easily control function body placement, hotness vs coldness (GCC has this?) so the user places all the unused function bodies together up at the 'cold' end of the obj, or better all such subsections into their own external section to be concatenated together. – Cecil Ward Mar 03 '17 at 07:26
  • `Would it be possible for a linker to sort this out`: In some cases yes. For example if the private function is part of the main executable the linker (GCC -ffunction-sections and LD --gc-sections) or probably even standard LTO could remove the private function. But in case the private function is in a library it must be kept as a template could be instantiated in a different library / the main executable. `solely employed by CTFE` this is also a real problem for embedded projects, where binary size matters. I think for CTFE only functions we could recognize `if (__ctfe){}` guarded functions – jpf Mar 04 '17 at 09:03
  • And then probably remove these functions. Or maybe add a special attribute for this. OTOH embedded systems don't have shared libararies and can use whole program optimization more easily. Completely inlined functions are more difficult, although maybe a `force-inline` flag could eliminate the function from the object file... `hotness vs coldness` is not implemented in GDC but shouldn't be difficult to add to `gcc.attribute`. Please file a enhancement request on https://bugzilla.gdcproject.org if you need these function attributes. – jpf Mar 04 '17 at 09:06
  • Something I myself have done is using ctfe-only functions that exist only to be called in asserts. For example, using `assert( production_fn(x) == simple_slow_alg_ctfe(x) )` as a sanity check, comparing two algorithms, the ctfe-only one being extremely simple / obvious. I presume this is a bad example, because it should really be a non-issue which I could sort out myself using `debug` blocks for conditional compilation? – Cecil Ward Mar 06 '17 at 06:11
  • I've also used long-winded ctfe-only functions to initialise read-only tables. – Cecil Ward Mar 06 '17 at 06:12