Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Static initializer side effects occur at very different time on C# target #4091

Closed
SmerkyG opened this issue Mar 27, 2015 · 11 comments · Fixed by #11551
Closed

Static initializer side effects occur at very different time on C# target #4091

SmerkyG opened this issue Mar 27, 2015 · 11 comments · Fixed by #11551
Assignees
Milestone

Comments

@SmerkyG
Copy link
Contributor

SmerkyG commented Mar 27, 2015

Static initializers are run at an unusual time on the C# target, leading to very different static initializer side effect results from other platforms. In C#, only creating an instance of Foo or referring to any of Foo's static members will trigger a just-in-time call to Foo's static initializers via the C# Foo static constructor. This is not true on other platforms such as flash, where the static initializer will be called much earlier, seemingly during the boot phase. (See http://old.haxe.org/doc/advanced/magic)

This can cause major problems for code that relies on static initializer side effects having occurred by the time non-static normal code is run. Example use cases include things like classes registering themselves by some sort of moniker. In these cases the registration will not have occurred yet in C# unless someone has already explicitly created a new Foo or referred to one of its static members, even though that will work fine on other targets. (Maybe not all?)

import haxe.ds.StringMap;

class Main {
    static var classes:StringMap<Class<Dynamic>> = new StringMap();
    public static function register(type:Class<Dynamic>,name:String):Void {
        classes.set(name, type);
    }
    static function main():Void {
        // traces true in flash, false in C#
        trace(classes.exists("Foo"));
        var foo = new Foo();
        // traces true in both flash and C#
        trace(classes.exists("Foo"));
    }
}

class Foo {
    static var static_init = {
        Main.register(Foo, "Foo");
        1;
    };
    public function new():Void {}
}
@Simn Simn added this to the 3.3 milestone Mar 28, 2015
@Simn Simn added platform-java Everything related to Java platform-cs Everything related to c# labels Mar 28, 2015
@Simn
Copy link
Member

Simn commented Mar 28, 2015

I wish this was defined to be undefined behavior, but alas it isn't. It requires running every single static initialization as part of the boot process which entails loading every single type that has a static initialization. Depending on the nature of the application this is quite a bit of a concession with regards to boot time. And all that for an incomplete feature (static initialization order in the general case is undecidable).

Well, I guess Java and C# are going to have to comply.

@waneck
Copy link
Member

waneck commented Apr 30, 2015

Is this not undefined behaviour? I am quite sure that it is: #125 (btw, check the author ;) )

@waneck
Copy link
Member

waneck commented Apr 30, 2015

I think it's quite bad if we need to make this a defined behaviour. Making sure that all __init__ are run are pretty easy to achieve with macros, and would be quite a penalty for any target that lazily loads the types (like C#, Java, PHP and maybe others)

@SmerkyG
Copy link
Contributor Author

SmerkyG commented Apr 30, 2015

Both ways seem to have serious downsides, but generally speaking it seems good for a programming language intended to be compilable to different targets to actually act the same (or reasonably similarly in the face of hard limits) across its targets. Maybe the ideal way would be to have it default to init on boot, but have a compiler flag to optionally turn off that boot-time behavior?

@ncannasse
Copy link
Member

#125 was about init, which is unspecified. Regarding statics initialization, we usually use some kind of heuristic to sort the classes based on their dependencies, they are then passed to the code generator in the sorted order. I guess for C# that would mean to have a single staticsInit() function that assign all statics for all the types before calling main()

@nadako
Copy link
Member

nadako commented Apr 30, 2015

to be honest i'd like to keep current behaviour instead of having to remember to call some init function when using haxe/c# generated code from native c# target code.

@waneck
Copy link
Member

waneck commented Apr 30, 2015

I think it's unfortunate if we have to do that - there is benefit in lazily loading types as needed, and what we gain instead seems to be quite dubious. I'd propose that we only enforce this if a flag is defined.

@SmerkyG
Copy link
Contributor Author

SmerkyG commented Apr 30, 2015

This is a long shot, but does the new static analyzer know which static data is affected via side-effects from other statics? If so, the system could boot-time initialize only the ones that really require it.

@Simn
Copy link
Member

Simn commented Apr 30, 2015

No interprocedural analysis. I'd add "yet" but I doubt I'll ever travel down that path.

@ncannasse
Copy link
Member

I agree this should not be a requirement

@Simn Simn modified the milestones: 3.3.0-rc1, 3.4 Feb 23, 2016
@Simn Simn modified the milestones: 3.4, 4.0 Jan 9, 2017
@Simn Simn modified the milestones: Release 4.0, Backlog Apr 17, 2018
@Simn Simn added documentation and removed platform-cs Everything related to c# platform-java Everything related to Java labels Sep 4, 2019
@Simn
Copy link
Member

Simn commented Sep 4, 2019

I think this has become a documentation issue.

@Simn Simn modified the milestones: Backlog, Later Mar 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants