From 3934a9d5827f22fb7625685276153cf507750383 Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Tue, 12 Dec 2023 09:00:06 +1000 Subject: [PATCH 1/9] Add a generic test case to work with --- compiler/coroutine_test.go | 6 ++++++ compiler/testdata/coroutine.go | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/compiler/coroutine_test.go b/compiler/coroutine_test.go index 6282738..7835ced 100644 --- a/compiler/coroutine_test.go +++ b/compiler/coroutine_test.go @@ -214,6 +214,12 @@ func TestCoroutineYield(t *testing.T) { yields: []int{11}, result: 42, }, + + { + name: "identity generic", + coro: func() { IdentityGeneric[int](11) }, + yields: []int{11}, + }, } // This emulates the installation of function type information by the diff --git a/compiler/testdata/coroutine.go b/compiler/testdata/coroutine.go index f3e53e2..3678de2 100644 --- a/compiler/testdata/coroutine.go +++ b/compiler/testdata/coroutine.go @@ -557,3 +557,7 @@ func ReturnNamedValue() (out int) { out = 42 return } + +func IdentityGeneric[T any](n T) { + coroutine.Yield[T, any](n) +} From fb1b16bd1d6945d96054583c41657809a0783dfc Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Tue, 12 Dec 2023 12:19:34 +1000 Subject: [PATCH 2/9] Track generic instantiations --- compiler/compile.go | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/compiler/compile.go b/compiler/compile.go index f7194e8..abd821f 100644 --- a/compiler/compile.go +++ b/compiler/compile.go @@ -47,6 +47,8 @@ func Compile(path string, options ...Option) error { type Option func(*compiler) type compiler struct { + prog *ssa.Program + generics map[*ssa.Function][]*ssa.Function coroutinePkg *packages.Package fset *token.FileSet @@ -112,13 +114,26 @@ func (c *compiler) compile(path string) error { } log.Printf("building SSA program") - prog, _ := ssautil.AllPackages(pkgs, ssa.InstantiateGenerics|ssa.GlobalDebug) - prog.Build() + c.prog, _ = ssautil.AllPackages(pkgs, ssa.InstantiateGenerics|ssa.GlobalDebug) + c.prog.Build() log.Printf("building call graph") - cg := vta.CallGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog)) + cg := vta.CallGraph(ssautil.AllFunctions(c.prog), cha.CallGraph(c.prog)) + + log.Printf("collecting generic instances") + c.generics = map[*ssa.Function][]*ssa.Function{} + for fn := range ssautil.AllFunctions(c.prog) { + if fn.Signature.TypeParams() != nil { + if _, ok := c.generics[fn]; !ok { + c.generics[fn] = nil + } + } + if origin := fn.Origin(); origin != nil { + c.generics[origin] = append(c.generics[origin], fn) + } + } - log.Printf("finding generic yield instantiations") + log.Printf("finding yield points") packages.Visit(pkgs, func(p *packages.Package) bool { if p.PkgPath == coroutinePackage { c.coroutinePkg = p @@ -129,10 +144,10 @@ func (c *compiler) compile(path string) error { log.Printf("%s not imported by the module. Nothing to do", coroutinePackage) return nil } - yieldFunc := prog.FuncValue(c.coroutinePkg.Types.Scope().Lookup("Yield").(*types.Func)) + yieldFunc := c.prog.FuncValue(c.coroutinePkg.Types.Scope().Lookup("Yield").(*types.Func)) yieldInstances := functionColors{} - for fn := range ssautil.AllFunctions(prog) { - if fn.Origin() == yieldFunc { + if fns, ok := c.generics[yieldFunc]; ok { + for _, fn := range fns { yieldInstances[fn] = fn.Signature } } @@ -149,11 +164,17 @@ func (c *compiler) compile(path string) error { }, nil) colorsByPkg := map[*packages.Package]functionColors{} for fn, color := range colors { - if fn.Pkg == nil { + pkg := fn.Pkg + if pkg == nil { + if origin := fn.Origin(); origin != nil { + pkg = origin.Pkg + } + } + if pkg == nil { return fmt.Errorf("unsupported yield function %s (Pkg is nil)", fn) } - p := pkgsByTypes[fn.Pkg.Pkg] + p := pkgsByTypes[pkg.Pkg] pkgColors := colorsByPkg[p] if pkgColors == nil { pkgColors = functionColors{} @@ -306,7 +327,7 @@ func (c *compiler) compilePackage(p *packages.Package, colors functionColors) er } } - generateFunctypes(p, gen, colorsByFunc) + c.generateFunctypes(p, gen, colorsByFunc) // Find all the required imports for this file. gen = addImports(p, gen) From 3000e20ca5f3d6fb45b0078f999c32baef78a85d Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Tue, 12 Dec 2023 12:19:58 +1000 Subject: [PATCH 3/9] Don't register type information for generic functions yet --- compiler/function.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/compiler/function.go b/compiler/function.go index 65acdc9..662f008 100644 --- a/compiler/function.go +++ b/compiler/function.go @@ -5,6 +5,7 @@ import ( "go/ast" "go/token" "go/types" + "log" "maps" "slices" "strconv" @@ -207,15 +208,22 @@ func functionPath(p *packages.Package, f *ast.FuncDecl) string { return packagePath(p) + "." + f.Name.Name } -func generateFunctypes(p *packages.Package, f *ast.File, colors map[ast.Node]*types.Signature) { +func (c *compiler) generateFunctypes(p *packages.Package, f *ast.File, colors map[ast.Node]*types.Signature) { functypes := map[string]functype{} for _, decl := range f.Decls { switch d := decl.(type) { case *ast.FuncDecl: - scope := &funcscope{vars: map[string]*funcvar{}} - name := functionPath(p, d) - collectFunctypes(p, name, d, scope, colors, functypes) + obj := p.TypesInfo.ObjectOf(d.Name).(*types.Func) + fn := c.prog.FuncValue(obj) + if fn.TypeParams() != nil { + log.Printf("warning: cannot register runtime type information for generic function %s", d.Name) + continue + } else { + scope := &funcscope{vars: map[string]*funcvar{}} + name := functionPath(p, d) + collectFunctypes(p, name, d, scope, colors, functypes) + } } } From ccb24b211c8fd63f096fa5cc48f07a7dd9b2bf43 Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Tue, 12 Dec 2023 12:20:16 +1000 Subject: [PATCH 4/9] Fail if type information isn't known at runtime --- types/types.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/types/types.go b/types/types.go index 2ddc28c..443537f 100644 --- a/types/types.go +++ b/types/types.go @@ -399,6 +399,9 @@ func (m *funcmap) RegisterAddr(addr unsafe.Pointer) (id funcid, closureType refl if f == nil { panic(fmt.Sprintf("function not found at address %v", addr)) } + if f.Type == nil { + panic(fmt.Sprintf("type information not registered for function %s (%p)", f.Name, addr)) + } var closureTypeID typeid if f.Closure != nil { From c66bc73961abd60f1faf5f3341cf7b346493a097 Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Tue, 12 Dec 2023 12:20:36 +1000 Subject: [PATCH 5/9] Support converting types with type parameters to expressions --- compiler/types.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compiler/types.go b/compiler/types.go index 4e4ec5f..eae8442 100644 --- a/compiler/types.go +++ b/compiler/types.go @@ -92,6 +92,12 @@ func typeExpr(p *packages.Package, typ types.Type) ast.Expr { c.Dir = ast.RECV } return c + + case *types.TypeParam: + obj := t.Obj() + ident := ast.NewIdent(obj.Name()) + p.TypesInfo.Defs[ident] = obj + return ident } panic(fmt.Sprintf("not implemented: %T", typ)) } From beedb21e626cf9a77c0958b43977d860c8c1583c Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Tue, 12 Dec 2023 12:22:25 +1000 Subject: [PATCH 6/9] Fix tests --- compiler/testdata/coroutine_durable.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/testdata/coroutine_durable.go b/compiler/testdata/coroutine_durable.go index 5203438..d9b71d3 100644 --- a/compiler/testdata/coroutine_durable.go +++ b/compiler/testdata/coroutine_durable.go @@ -3236,6 +3236,9 @@ func ReturnNamedValue() (_fn0 int) { } panic("unreachable") } + +//go:noinline +func IdentityGeneric[T any](n T) { coroutine.Yield[T, any](n) } func init() { _types.RegisterFunc[func(n int)]("github.com/stealthrocket/coroutine/compiler/testdata.Double") _types.RegisterFunc[func(_fn0 int)]("github.com/stealthrocket/coroutine/compiler/testdata.EvenSquareGenerator") From 262d4ecf3e7f4de0a623b9211b786da35e46f743 Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Tue, 12 Dec 2023 12:25:04 +1000 Subject: [PATCH 7/9] Add TODO for generics support --- compiler/function.go | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/function.go b/compiler/function.go index 662f008..0e5df50 100644 --- a/compiler/function.go +++ b/compiler/function.go @@ -217,6 +217,7 @@ func (c *compiler) generateFunctypes(p *packages.Package, f *ast.File, colors ma obj := p.TypesInfo.ObjectOf(d.Name).(*types.Func) fn := c.prog.FuncValue(obj) if fn.TypeParams() != nil { + // TODO: support generics. Generate type func/closure type information for each instance from: instances := c.generics[fn] log.Printf("warning: cannot register runtime type information for generic function %s", d.Name) continue } else { From 46c3191a6ab890eb7a2961ac4b4a762b124357ed Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Tue, 12 Dec 2023 12:42:55 +1000 Subject: [PATCH 8/9] Add another basic test --- compiler/coroutine_test.go | 12 ++++++++++++ compiler/testdata/coroutine.go | 16 ++++++++++++++++ compiler/testdata/coroutine_durable.go | 15 +++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/compiler/coroutine_test.go b/compiler/coroutine_test.go index 7835ced..d6e92c8 100644 --- a/compiler/coroutine_test.go +++ b/compiler/coroutine_test.go @@ -220,6 +220,18 @@ func TestCoroutineYield(t *testing.T) { coro: func() { IdentityGeneric[int](11) }, yields: []int{11}, }, + + { + name: "identity generic (2)", + coro: func() { IdentityGenericInt(11) }, + yields: []int{11}, + }, + + { + name: "identity generic (3)", + coro: func() { IdentityGenericStructInt(11) }, + yields: []int{11}, + }, } // This emulates the installation of function type information by the diff --git a/compiler/testdata/coroutine.go b/compiler/testdata/coroutine.go index 3678de2..92d7218 100644 --- a/compiler/testdata/coroutine.go +++ b/compiler/testdata/coroutine.go @@ -561,3 +561,19 @@ func ReturnNamedValue() (out int) { func IdentityGeneric[T any](n T) { coroutine.Yield[T, any](n) } + +type IdentityGenericStruct[T any] struct { + n T +} + +func (i *IdentityGenericStruct[T]) Run() { + coroutine.Yield[T, any](i.n) +} + +func IdentityGenericInt(n int) { + IdentityGeneric[int](n) +} + +func IdentityGenericStructInt(n int) { + (&IdentityGenericStruct[int]{n: n}).Run() +} diff --git a/compiler/testdata/coroutine_durable.go b/compiler/testdata/coroutine_durable.go index d9b71d3..f13ce47 100644 --- a/compiler/testdata/coroutine_durable.go +++ b/compiler/testdata/coroutine_durable.go @@ -3239,12 +3239,27 @@ func ReturnNamedValue() (_fn0 int) { //go:noinline func IdentityGeneric[T any](n T) { coroutine.Yield[T, any](n) } + +type IdentityGenericStruct[T any] struct { + n T +} + +//go:noinline +func (i *IdentityGenericStruct[T]) Run() { coroutine.Yield[T, any](i.n) } + +//go:noinline +func IdentityGenericInt(n int) { IdentityGeneric[int](n) } + +//go:noinline +func IdentityGenericStructInt(n int) { (&IdentityGenericStruct[int]{n: n}).Run() } func init() { _types.RegisterFunc[func(n int)]("github.com/stealthrocket/coroutine/compiler/testdata.Double") _types.RegisterFunc[func(_fn0 int)]("github.com/stealthrocket/coroutine/compiler/testdata.EvenSquareGenerator") _types.RegisterFunc[func(_fn0 int)]("github.com/stealthrocket/coroutine/compiler/testdata.FizzBuzzIfGenerator") _types.RegisterFunc[func(_fn0 int)]("github.com/stealthrocket/coroutine/compiler/testdata.FizzBuzzSwitchGenerator") _types.RegisterFunc[func(n int)]("github.com/stealthrocket/coroutine/compiler/testdata.Identity") + _types.RegisterFunc[func(n int)]("github.com/stealthrocket/coroutine/compiler/testdata.IdentityGenericInt") + _types.RegisterFunc[func(n int)]("github.com/stealthrocket/coroutine/compiler/testdata.IdentityGenericStructInt") _types.RegisterFunc[func(_ int)]("github.com/stealthrocket/coroutine/compiler/testdata.LoopBreakAndContinue") _types.RegisterFunc[func(_fn1 int)]("github.com/stealthrocket/coroutine/compiler/testdata.MethodGenerator") _types.RegisterFunc[func(_fn0 int) (_ int)]("github.com/stealthrocket/coroutine/compiler/testdata.NestedLoops") From 069cac4384f5358d39479efde04589a19227c90f Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Tue, 12 Dec 2023 12:46:52 +1000 Subject: [PATCH 9/9] Better warning about the generic function that's skipped --- compiler/function.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/function.go b/compiler/function.go index 0e5df50..e468bd0 100644 --- a/compiler/function.go +++ b/compiler/function.go @@ -218,7 +218,7 @@ func (c *compiler) generateFunctypes(p *packages.Package, f *ast.File, colors ma fn := c.prog.FuncValue(obj) if fn.TypeParams() != nil { // TODO: support generics. Generate type func/closure type information for each instance from: instances := c.generics[fn] - log.Printf("warning: cannot register runtime type information for generic function %s", d.Name) + log.Printf("warning: cannot register runtime type information for generic function %s", fn) continue } else { scope := &funcscope{vars: map[string]*funcvar{}}