-
Notifications
You must be signed in to change notification settings - Fork 569
Adding Type Initialization Grouping and Ordering #1378
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
Conversation
0f22a05
to
ea71a06
Compare
It may be that I'm missing something, but this seems more complicated than I expected. To keep things concrete, I made a small reproduction example, which is attached: 049-generic-circle.tar.gz It's a very minimal illustration of the problem you've been working on: // main.go
package main
import "gencicrle/foo"
func main() {
e := foo.Entity{}
println(e.Ref.Next)
}
// foo/foo.go
package foo
import "gencicrle/bar"
type Entity struct {
Ref bar.Bar[Entity]
}
// bar/bar.go
package bar
type Bar[G any] struct {
Next *G
} If I run it as-is, I get this error: $ GOPHERJS_EXPERIMENT="generics" gopherjs build . && node 049-generic-circle.js
/home/aleks/tmp/049-generic-circle/049-generic-circle.js:2644
ptrType = $ptrType($packages["gencicrle/foo"].Entity);
^
TypeError: Cannot read properties of undefined (reading 'Entity')
at /home/aleks/tmp/049-generic-circle/049-generic-circle.js:2644:48
at Object.<anonymous> (/home/aleks/tmp/049-generic-circle/049-generic-circle.js:2654:3)
at Object.<anonymous> (/home/aleks/tmp/049-generic-circle/049-generic-circle.js:2707:4)
at Module._compile (node:internal/modules/cjs/loader:1730:14)
at Object..js (node:internal/modules/cjs/loader:1895:10)
at Module.load (node:internal/modules/cjs/loader:1465:32)
at Function._load (node:internal/modules/cjs/loader:1282:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:235:24)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:171:5)
at node:internal/main/run_main_module:36:49
Node.js v22.16.0 This is expected because there is a circular dependency in the generated code, let's take a look at it, I marked the interesting line with $packages["gencicrle/bar"] = (function() {
var $pkg = {}, $init, Bar, ptrType;
Bar = {};
Bar[0 /* gencicrle/foo.Entity */] = $newType(0, $kindStruct, "bar.Bar[gencicrle/foo.Entity]", true, "gencicrle/bar", true, function(Next_) {
this.$val = this;
if (arguments.length === 0) {
this.Next = ptrType.nil;
return;
}
this.Next = Next_;
});
ptrType = $ptrType($packages["gencicrle/foo"].Entity); // EXCEPTION HERE
$pkg.Bar = Bar;
Bar[0 /* gencicrle/foo.Entity */].init("", [{prop: "Next", name: "Next", embedded: false, exported: true, typ: ptrType, tag: ""}]);
$init = function() {
$pkg.$init = function() {};
/* */ var $f, $c = false, $s = 0, $r; if (this !== undefined && this.$blk !== undefined) { $f = this; $c = true; $s = $f.$s; $r = $f.$r; } s: while (true) { switch ($s) { case 0:
/* */ } return; } if ($f === undefined) { $f = { $blk: $init }; } $f.$s = $s; $f.$r = $r; return $f;
};
$pkg.$init = $init;
return $pkg;
})();
$packages["gencicrle/foo"] = (function() {
var $pkg = {}, $init, bar, Entity, ptrType;
bar = $packages["gencicrle/bar"];
Entity = $newType(0, $kindStruct, "foo.Entity", true, "gencicrle/foo", true, function(Ref_) {
this.$val = this;
if (arguments.length === 0) {
this.Ref = new bar.Bar[0 /* gencicrle/foo.Entity */].ptr(ptrType.nil);
return;
}
this.Ref = Ref_;
});
ptrType = $ptrType(Entity);
$pkg.Entity = Entity;
Entity.init("", [{prop: "Ref", name: "Ref", embedded: false, exported: true, typ: bar.Bar[0 /* gencicrle/foo.Entity */], tag: ""}]);
$init = function() {
$pkg.$init = function() {};
/* */ var $f, $c = false, $s = 0, $r; if (this !== undefined && this.$blk !== undefined) { $f = this; $c = true; $s = $f.$s; $r = $f.$r; } s: while (true) { switch ($s) { case 0:
$r = bar.$init(); /* */ $s = 1; case 1: if($c) { $c = false; $r = $r.$blk(); } if ($r && $r.$blk !== undefined) { break s; }
/* */ } return; } if ($f === undefined) { $f = { $blk: $init }; } $f.$s = $s; $f.$r = $r; return $f;
};
$pkg.$init = $init;
return $pkg;
})(); We are creating a composite type definition Note that if you look closely on how // $package['bar'] = ...
// Create type's object:
Bar[0 /* gencicrle/foo.Entity */] = $newType(0, $kindStruct, "bar.Bar[gencicrle/foo.Entity]", true, "gencicrle/bar", true, function(Next_) { /* omitted for brevity */ });
// Initialize extended type information:
Bar[0 /* gencicrle/foo.Entity */].init("", [{prop: "Next", name: "Next", embedded: false, exported: true, typ: ptrType, tag: ""}]);
// $package['foo'] = ...
// Create type's object:
Entity = $newType(0, $kindStruct, "foo.Entity", true, "gencicrle/foo", true, function(Ref_) { /* omitted for brevity */ });
// Initialize extended type information:
Entity.init("", [{prop: "Ref", name: "Ref", embedded: false, exported: true, typ: bar.Bar[0 /* gencicrle/foo.Entity */], tag: ""}]); Two important observations are:
So our problem would be solved if we simply delay calls to the $packages["gencicrle/bar"] = (function () {
var $pkg = {}, $init, Container, sliceType;
Container = {};
Container[0 /* gencicrle/foo.Entity */] = $newType(0, $kindStruct, "bar.Container[gencicrle/foo.Entity]", true, "gencicrle/bar", true, function (Items_) {
this.$val = this;
if (arguments.length === 0) {
this.Items = sliceType.nil;
return;
}
this.Items = Items_;
});
$pkg.Container = Container;
$init = function () {
sliceType = $sliceType($packages["gencicrle/foo"].Entity); // MOVED THIS
Container[0 /* gencicrle/foo.Entity */].init("", [{ prop: "Items", name: "Items", embedded: false, exported: true, typ: sliceType, tag: "" }]); // MOVED THIS
$pkg.$init = function () { };
/* */ var $f, $c = false, $s = 0, $r; if (this !== undefined && this.$blk !== undefined) { $f = this; $c = true; $s = $f.$s; $r = $f.$r; } s: while (true) {
switch ($s) {
case 0:
/* */
} return;
} if ($f === undefined) { $f = { $blk: $init }; } $f.$s = $s; $f.$r = $r; return $f;
};
$pkg.$init = $init;
return $pkg;
})();
$packages["gencicrle/foo"] = (function () {
var $pkg = {}, $init, bar, Entity, sliceType;
bar = $packages["gencicrle/bar"];
Entity = $newType(0, $kindStruct, "foo.Entity", true, "gencicrle/foo", true, function (Ref_) {
this.$val = this;
if (arguments.length === 0) {
this.Ref = new bar.Container[0 /* gencicrle/foo.Entity */].ptr(sliceType.nil);
return;
}
this.Ref = Ref_;
});
sliceType = $sliceType(Entity);
$pkg.Entity = Entity;
$init = function () {
Entity.init("", [{ prop: "Ref", name: "Ref", embedded: false, exported: true, typ: bar.Container[0 /* gencicrle/foo.Entity */], tag: "" }]); // MOVED THIS
$pkg.$init = function () { };
/* */ var $f, $c = false, $s = 0, $r; if (this !== undefined && this.$blk !== undefined) { $f = this; $c = true; $s = $f.$s; $r = $f.$r; } s: while (true) {
switch ($s) {
case 0:
$r = bar.$init(); /* */ $s = 1; case 1: if ($c) { $c = false; $r = $r.$blk(); } if ($r && $r.$blk !== undefined) { break s; }
/* */
} return;
} if ($f === undefined) { $f = { $blk: $init }; } $f.$s = $s; $f.$r = $r; return $f;
};
$pkg.$init = $init;
return $pkg;
})(); Full source code: 049-generic-circle.js.gz After which it runs correctly: $ node 049-generic-circle.js
<ref *1> {
'$val': [Circular *1],
Items: <ref *2> typ {
'$array': [],
'$offset': 0,
'$length': 0,
'$capacity': 0,
'$val': [Circular *2]
}
} Now, I cheated a little bit here, because I also moved Anyway, all of this is a long way of saying, that I don't feel like we need a complex ordering algorithm to determine the order the decls need to be initialized in. I think simply grouping the type object creation and type initialization should give us what we want. |
I think I've figured out what you (@nevkontakte) were thinking and think I may have a solution where the "cheat" is done programmatically. I'm breaking up the While trying to understand what you meant I learned about how JS handles variables in closures (like those for constructors) and that was the "ohhhh, I get it" moment for me. Those variables are in places where we don't have to initialize them until later since they won't be used for a while. I was trying to get every type initialized prior to it being used anywhere, including in a closure, but in our JS you don't have to do that. I'm testing my code-up of your solution and will have a new PR up soon with that work and I'll close this PR (unless I run into something that may require type ordering still, then I'll leave it open so we can discuss alternatives more). |
Here is the WIP (#1380) that I'm running tests on. You can peek at it and tell me if that's what you were thinking or wait. Either way, once I'm confident I got it working (and move over some tests from the this PR to that one), I'll let you know when it's ready for review |
When the JS packages are being setup, type instances could require type information from packages which have not been setup yet. For example,
list.List[Cat]
could be defined inside thecat
package and import thelist
package. The type instanceList[cat.Cat]
is initialized inside thelist
package. This will cause a failure becausecat.Cat
hasn't been setup yet since it requireslist
to be setup first as an import. We can't moveList[Cat]
to the cat package becauseList[T]
may access unexposed data inside thelist
package.This is the first part of two (or more) tickets. To solve the type initialization, each declaration will be given a group number. There will be$n$ groups and each group number is $g_i$ where $0 \le i < n$ . For any group, $g_i$ , the prior groups, $\{g_0 ... g_{i-1}\}$ , will have to be initialized first. All the declarations with the same group number are part of the same group. However within that group, the declarations will still need to be initialized in the current ordering where imports are done before importers and types are initialized before they are used in pointers, slices, etc.
This PR determines the grouping and order of the groups, then sets the group number for each
Decl
. This PR mainly consists of two new packages:sequencer
andgrouper
. Thesequencer
is a generalized tool for determining ordering and grouping of items depending on the items' dependencies. Thegrouper
stores information about the declarations' types so that it can use thesequencer
to group and order the declarations based on type.Following ticket will use that group number to populate a "startup map" where the key is the group number and the value is a set of functions that need to be run. Each function will initialize all the
Decl
s for a particular group for a single package. Then once all the packages have been added, the runtime will call each group of functions in order.