www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Re: order of static constructor execution

reply Regan Heath <regan netmail.co.nz> writes:
Walter Bright Wrote:
 I'm not happy with this solution, but it seems to be the best compromise 
 I can come up with.
 
 What do you think?

The first thought that occurred to me was "how this was handled in other languages". As C# is one of the more recent languages, and has some static construction I thought I'd try to create a loop-like-dependency test case. C# does not have modules, but it seems to me that a class with only static members and a static constructor is roughly analogous. I'm not sure how similar a test case this really is, to be honest it's been a while since I wrote some D and I haven't used mixin and static construction in depth but perhaps someone can take my example and improve it. I can at least explain what C# is doing in my test case and why, and it raises a question about how the D implementation works. First, the C# test case, based on ... http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=107373 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace StaticConstruct { class Program { static void Main(string[] args) { System.Console.Out.WriteLine("A = {0}", A.a); System.Console.Out.WriteLine("B = {0}", B.b); } } class A { public static int a; static A() { a = 1; } } class B { public static int b; static B() { b = 2; } } } Simple enough, neither A nor B actually depend on each other. But what does C# do with the above. It's actually pretty simple, when it executes the first WriteLine it evaluates A.a, when it does that it calls the static constructor for A. Likewise for B.b the same thing occurs. So, it's effectively performing lazy construction/evaluation. Does D do this, or does D attempt to construct _all_ modules on program start? Could this be the solution, to make module construction lazy? Lets try a few more complex examples... 1) Change "public static int a;" to "public static int a = B.b;" (create dependency on B) Change "public static int b;" to "public static int b = A.a;" (create dependency on A) This creates a circular dependency. What happens: 01.When it executes the A.a WriteLine it needs to evaluate A.a, so it needs to initialize A. 02.To initialise A it must initialize the 'globals' i.e. "public static int a = B.b;" 03.This causes it to evaluate B.b, causing initialization of B 04.To initialise B it must initialize the 'globals' i.e. public static int b = A.a;". 05.At this point it assigns 0 to b, as A.a is currently 0 and as it has already entered initialization of A (preventing infinite loop) 06.It then calls the static constructor for B, assigning b = 2; 07.It then completes "public static int a = B.b;" assigning a = 2; 08.It then calls the static constructor for A, assigning a = 1; 09.It returns control to the WriteLine outputting "A = 1" 10.The call to the 2nd WriteLine simply outputs "B = 2" So, here we see that the lazy construction/evaluation, paired with a flag for detecting re-entrant initialization resolves the circular dependency. 2) Add a static function to 'module' A, eg. class A { public static int a = B.b; static A() { a = 42; } public static int Foo() { return A.a; } } and a call to main: System.Console.Out.WriteLine("A.Foo = {0}", A.Foo()); System.Console.Out.WriteLine("A = {0}", A.a); System.Console.Out.WriteLine("B = {0}", B.b); So, what happens now: When it executes the Foo() WriteLine it triggers initialization of A (because foo is a static member of A), this triggers the process shown in 1) above, lines 02 thru 08 before returning control to the WriteLine. As a consequence all the initialization is done by the next WriteLine call, so it simply completes etc. Thoughts?
Mar 12 2010
parent reply Walter Bright <newshound1 digitalmars.com> writes:
Doing lazy initialization certainly works, but it would require all 
static member access to go through a check for initialization, first. 
The cost of this check persists for a statically compiled language; for 
a JITted language like C# the access can be rewritten to remove the check.
Mar 12 2010
next sibling parent BCS <none anon.com> writes:
Hello Walter,

 Doing lazy initialization certainly works, but it would require all
 static member access to go through a check for initialization, first.
 The cost of this check persists for a statically compiled language;
 for a JITted language like C# the access can be rewritten to remove
 the check.

That and I have some patterns I like using where I use "static this()" to inject results without any change to the code base. For that to work, they need to run before things get referenced. void delegate(string)[string] args; // static this injects into here. void main(string[] argv) { foreach(string s; argv) args[GetBefor('=',s)](GetAfter('=',s)); } -- ... <IXOYE><
Mar 12 2010
prev sibling next sibling parent Fawzi Mohamed <fawzi gmx.ch> writes:
On 12-mar-10, at 19:17, Walter Bright wrote:

 Doing lazy initialization certainly works, but it would require all  
 static member access to go through a check for initialization,  
 first. The cost of this check persists for a statically compiled  
 language; for a JITted language like C# the access can be rewritten  
 to remove the check.

well if one tracks what gets initialized and what is accessed then one could associate a lazy constructor with each static value, and call the static initializer explicitly before access in the static methods, so that the cost is paid only during startup. But that is quite some work, because static initializers in D are so flexible, and so tracking what they initialize and what they access is some work (even if theoretically the compiler can know it). Also invalid initializations could be catched (but only at runtime) having 3 initialization states: on initialized, during initialization, initialized. I still think that something like my proposal dependOnly(modules) for static initializers is a valid alternative. Fawzi
Mar 12 2010
prev sibling parent reply Regan Heath <regan netmail.co.nz> writes:
Walter Bright wrote:
 Doing lazy initialization certainly works, but it would require all 
 static member access to go through a check for initialization, first. 
 The cost of this check persists for a statically compiled language; for 
 a JITted language like C# the access can be rewritten to remove the check.

Yeah, I figured that was going to be the stumbling block. Is it possible for the compiler to figure out an order of execution during compile instead? Then it could simply use that to order construction instead of import/lexical ordering and there would be no runtime cost. I expect this either isn't possible or falls into the 'possible up to a point' category. R
Mar 15 2010
parent reply Walter Bright <newshound1 digitalmars.com> writes:
Regan Heath wrote:
 I expect this either isn't possible or falls into the 'possible up to a 
 point' category.

It's possible up to a point, that point being the parts of the program the compiler does not see.
Mar 15 2010
parent reply Regan Heath <regan netmail.co.nz> writes:
Walter Bright wrote:
 Regan Heath wrote:
 I expect this either isn't possible or falls into the 'possible up to 
 a point' category.

It's possible up to a point, that point being the parts of the program the compiler does not see.

So.. isn't that the same point the current import dependency method stops at? And if so, does that mean this method would create a different possbily loop free tree for static construction? R
Mar 15 2010
parent reply Walter Bright <newshound1 digitalmars.com> writes:
Regan Heath wrote:
 Walter Bright wrote:
 Regan Heath wrote:
 I expect this either isn't possible or falls into the 'possible up to 
 a point' category.

It's possible up to a point, that point being the parts of the program the compiler does not see.

So.. isn't that the same point the current import dependency method stops at?

No, because that's a runtime check.
Mar 15 2010
parent reply Regan Heath <regan netmail.co.nz> writes:
Walter Bright wrote:
 Regan Heath wrote:
 Walter Bright wrote:
 Regan Heath wrote:
 I expect this either isn't possible or falls into the 'possible up 
 to a point' category.

It's possible up to a point, that point being the parts of the program the compiler does not see.

So.. isn't that the same point the current import dependency method stops at?

No, because that's a runtime check.

No, I'm no longer suggesting a runtime solution. How does 'unreachable code' detection in C/C++ compilers work? Can you use something similar to figure out the order module static data is used? R
Mar 15 2010
parent reply Walter Bright <newshound1 digitalmars.com> writes:
Regan Heath wrote:
 Walter Bright wrote:
 Regan Heath wrote:
 Walter Bright wrote:
 Regan Heath wrote:
 I expect this either isn't possible or falls into the 'possible up 
 to a point' category.

It's possible up to a point, that point being the parts of the program the compiler does not see.

So.. isn't that the same point the current import dependency method stops at?

No, because that's a runtime check.

No, I'm no longer suggesting a runtime solution. How does 'unreachable code' detection in C/C++ compilers work?

It requires the source code so the compiler can look at it.
 Can you use something similar to figure out the order module static data is
used?

Yes, but that fails when the compiler doesn't have the source code to look at.
Mar 15 2010
parent reply Regan Heath <regan netmail.co.nz> writes:
Walter Bright wrote:
 Regan Heath wrote:
 Walter Bright wrote:
 Regan Heath wrote:
 Walter Bright wrote:
 Regan Heath wrote:
 I expect this either isn't possible or falls into the 'possible up 
 to a point' category.

It's possible up to a point, that point being the parts of the program the compiler does not see.

So.. isn't that the same point the current import dependency method stops at?

No, because that's a runtime check.

No, I'm no longer suggesting a runtime solution. How does 'unreachable code' detection in C/C++ compilers work?

It requires the source code so the compiler can look at it.
 Can you use something similar to figure out the order module static 
 data is used?

Yes, but that fails when the compiler doesn't have the source code to look at.

Ahh.. I was naively assuming you were compiling all the source at the same time, but of course that's not necessarily true. So, this needs to work when compiling modules seperately and then linking, likewise libraries, right? Is that the problem you're referring to? Couldn't you store a list of dependencies in usage order in the output of the compile (the .o[bj] file) and use these lists when linking to resolve module init order. You would need to know the 'main' module for a starting point, but from there you should be able to create an ordering. You'd probably want to treat a static library as a single dependency, likewise C libraries etc. R
Mar 16 2010
parent reply Walter Bright <newshound1 digitalmars.com> writes:
Regan Heath wrote:
 So, this needs to work when compiling modules seperately and then 
 linking, likewise libraries, right?  Is that the problem you're 
 referring to?

Yes.
 Couldn't you store a list of dependencies in usage order in the output 
 of the compile (the .o[bj] file) and use these lists when linking to 
 resolve module init order.  You would need to know the 'main' module for 
 a starting point, but from there you should be able to create an 
 ordering.  You'd probably want to treat a static library as a single 
 dependency, likewise C libraries etc.

Then you'd have to essentially build the linker into the compiler.
Mar 16 2010
parent Regan Heath <regan netmail.co.nz> writes:
Walter Bright wrote:
 Regan Heath wrote:
 So, this needs to work when compiling modules seperately and then 
 linking, likewise libraries, right?  Is that the problem you're 
 referring to?

Yes.
 Couldn't you store a list of dependencies in usage order in the output 
 of the compile (the .o[bj] file) and use these lists when linking to 
 resolve module init order.  You would need to know the 'main' module 
 for a starting point, but from there you should be able to create an 
 ordering.  You'd probably want to treat a static library as a single 
 dependency, likewise C libraries etc.

Then you'd have to essentially build the linker into the compiler.

Ah.. I'd have thought using 'import' as you currently do was the same sort of thing, but I guess I don't know enough about how compilers work :) The thing that suddenly seemed odd to me, and the reason I wanted to see how C# did it was the fact that you're using 'import' to determine whether/when something needs to be initialised, when in fact 'import' doesn't guarantee the thing is actually used at all. I can import something erroneously into a module, then never actually use anything in that module, or, use only one pure function that doesn't use the static needs-to-be-initialised data. Using 'import' is always going to produce more false positives, but I guess it also cannot produce a false negative. R
Mar 17 2010