From b7ce0cfe4adb3d337c753590204c7358b650b72d Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Wed, 9 Oct 2024 15:01:25 -0600 Subject: [PATCH] Trying to use InastanceMap --- compiler/decls.go | 6 +- compiler/functions.go | 14 ++--- compiler/internal/analysis/info.go | 59 +++++++++++++------- compiler/internal/analysis/info_test.go | 74 +++++++++++++++++++++---- 4 files changed, 111 insertions(+), 42 deletions(-) diff --git a/compiler/decls.go b/compiler/decls.go index 0faea056a..019c7bc7e 100644 --- a/compiler/decls.go +++ b/compiler/decls.go @@ -319,7 +319,7 @@ func (fc *funcContext) newFuncDecl(fun *ast.FuncDecl, inst typeparams.Instance) o := fc.pkgCtx.Defs[fun.Name].(*types.Func) d := &Decl{ FullName: o.FullName(), - Blocking: fc.pkgCtx.IsBlocking(o), + Blocking: fc.pkgCtx.IsBlocking(inst), LinkingName: symbol.New(o), } @@ -355,7 +355,7 @@ func (fc *funcContext) newFuncDecl(fun *ast.FuncDecl, inst typeparams.Instance) func (fc *funcContext) callInitFunc(init *types.Func) ast.Stmt { id := fc.newIdentFor(init) call := &ast.CallExpr{Fun: id} - if fc.pkgCtx.IsBlocking(init) { + if fc.pkgCtx.IsBlocking(typeparams.Instance{Object: init}) { fc.Blocking[call] = true } return &ast.ExprStmt{X: call} @@ -379,7 +379,7 @@ func (fc *funcContext) callMainFunc(main *types.Func) ast.Stmt { }, }, } - if fc.pkgCtx.IsBlocking(main) { + if fc.pkgCtx.IsBlocking(typeparams.Instance{Object: main}) { fc.Blocking[call] = true fc.Flattened[ifStmt] = true } diff --git a/compiler/functions.go b/compiler/functions.go index 1641174b4..657bf9926 100644 --- a/compiler/functions.go +++ b/compiler/functions.go @@ -72,7 +72,7 @@ func (fc *funcContext) nestedFunctionContext(info *analysis.FuncInfo, inst typep // namedFuncContext creates a new funcContext for a named Go function // (standalone or method). func (fc *funcContext) namedFuncContext(inst typeparams.Instance) *funcContext { - info := fc.pkgCtx.FuncDeclInfos[inst.Object.(*types.Func)] + info := fc.pkgCtx.FuncInfo(inst) c := fc.nestedFunctionContext(info, inst) return c @@ -82,7 +82,7 @@ func (fc *funcContext) namedFuncContext(inst typeparams.Instance) *funcContext { // go/types doesn't generate *types.Func objects for function literals, we // generate a synthetic one for it. func (fc *funcContext) literalFuncContext(fun *ast.FuncLit) *funcContext { - info := fc.pkgCtx.FuncLitInfos[fun] + info := fc.pkgCtx.FuncLitInfo(fun) sig := fc.pkgCtx.TypeOf(fun).(*types.Signature) o := types.NewFunc(fun.Pos(), fc.pkgCtx.Pkg, fc.newLitFuncName(), sig) inst := typeparams.Instance{Object: o} @@ -237,7 +237,7 @@ func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident, } bodyOutput := string(fc.CatchOutput(1, func() { - if len(fc.Blocking) != 0 { + if fc.HasBlocking() { fc.pkgCtx.Scopes[body] = fc.pkgCtx.Scopes[typ] fc.handleEscapingVars(body) } @@ -283,14 +283,14 @@ func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident, if fc.HasDefer { fc.localVars = append(fc.localVars, "$deferred") suffix = " }" + suffix - if len(fc.Blocking) != 0 { + if fc.HasBlocking() { suffix = " }" + suffix } } localVarDefs := "" // Function-local var declaration at the top. - if len(fc.Blocking) != 0 { + if fc.HasBlocking() { localVars := append([]string{}, fc.localVars...) // There are several special variables involved in handling blocking functions: // $r is sometimes used as a temporary variable to store blocking call result. @@ -314,7 +314,7 @@ func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident, if fc.HasDefer { prefix = prefix + " var $err = null; try {" deferSuffix := " } catch(err) { $err = err;" - if len(fc.Blocking) != 0 { + if fc.HasBlocking() { deferSuffix += " $s = -1;" } if fc.resultNames == nil && fc.sig.HasResults() { @@ -324,7 +324,7 @@ func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident, if fc.resultNames != nil { deferSuffix += fmt.Sprintf(" if (!$curGoroutine.asleep) { return %s; }", fc.translateResults(fc.resultNames)) } - if len(fc.Blocking) != 0 { + if fc.HasBlocking() { deferSuffix += " if($curGoroutine.asleep) {" } suffix = deferSuffix + suffix diff --git a/compiler/internal/analysis/info.go b/compiler/internal/analysis/info.go index 537845745..38226493e 100644 --- a/compiler/internal/analysis/info.go +++ b/compiler/internal/analysis/info.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/gopherjs/gopherjs/compiler/astutil" + "github.com/gopherjs/gopherjs/compiler/internal/typeparams" "github.com/gopherjs/gopherjs/compiler/typesutil" ) @@ -51,8 +52,8 @@ type Info struct { *types.Info Pkg *types.Package HasPointer map[*types.Var]bool - FuncDeclInfos map[*types.Func]*FuncInfo - FuncLitInfos map[*ast.FuncLit]*FuncInfo + funcInstInfos *typeparams.InstanceMap[*FuncInfo] + funcLitInfos map[*ast.FuncLit]*FuncInfo InitFuncInfo *FuncInfo // Context for package variable initialization. isImportedBlocking func(*types.Func) bool // For functions from other packages. @@ -65,7 +66,7 @@ func (info *Info) newFuncInfo(n ast.Node) *FuncInfo { Flattened: make(map[ast.Node]bool), Blocking: make(map[ast.Node]bool), GotoLabel: make(map[*types.Label]bool), - localNamedCallees: make(map[*types.Func][]astPath), + localInstCallees: new(typeparams.InstanceMap[[]astPath]), literalFuncCallees: make(map[*ast.FuncLit][]astPath), } @@ -83,10 +84,11 @@ func (info *Info) newFuncInfo(n ast.Node) *FuncInfo { } fn := info.Defs[n.Name].(*types.Func) - info.FuncDeclInfos[fn] = funcInfo + inst := typeparams.Instance{Object: fn} + info.funcInstInfos.Set(inst, funcInfo) case *ast.FuncLit: - info.FuncLitInfos[n] = funcInfo + info.funcLitInfos[n] = funcInfo } // And add it to the list of all functions. @@ -95,12 +97,23 @@ func (info *Info) newFuncInfo(n ast.Node) *FuncInfo { return funcInfo } +func (info *Info) FuncInfo(inst typeparams.Instance) *FuncInfo { + if funInfo := info.funcInstInfos.Get(inst); funInfo != nil { + return funInfo + } + panic(fmt.Errorf(`Info did not have function declaration or instance for %v`, inst)) +} + // IsBlocking returns true if the function may contain blocking calls or operations. -func (info *Info) IsBlocking(fun *types.Func) bool { - if funInfo := info.FuncDeclInfos[fun]; funInfo != nil { - return len(funInfo.Blocking) > 0 +func (info *Info) IsBlocking(inst typeparams.Instance) bool { + return info.FuncInfo(inst).HasBlocking() +} + +func (info *Info) FuncLitInfo(fun *ast.FuncLit) *FuncInfo { + if funInfo := info.funcLitInfos[fun]; funInfo != nil { + return funInfo } - panic(fmt.Errorf(`info did not have function declaration for %s`, fun.FullName())) + panic(fmt.Errorf(`Info did not have function literal for %v`, fun)) } // VarsWithInitializers returns a set of package-level variables that have @@ -121,8 +134,8 @@ func AnalyzePkg(files []*ast.File, fileSet *token.FileSet, typesInfo *types.Info Pkg: typesPkg, HasPointer: make(map[*types.Var]bool), isImportedBlocking: isBlocking, - FuncDeclInfos: make(map[*types.Func]*FuncInfo), - FuncLitInfos: make(map[*ast.FuncLit]*FuncInfo), + funcInstInfos: new(typeparams.InstanceMap[*FuncInfo]), + funcLitInfos: make(map[*ast.FuncLit]*FuncInfo), } info.InitFuncInfo = info.newFuncInfo(nil) @@ -155,19 +168,19 @@ func AnalyzePkg(files []*ast.File, fileSet *token.FileSet, typesInfo *types.Info done := true for _, caller := range info.allInfos { // Check calls to named functions and function-typed variables. - for callee, callSites := range caller.localNamedCallees { - if info.IsBlocking(callee) { + for callee, callSites := range caller.localInstCallees { + if info.funcInstInfos[callee].HasBlocking() { for _, callSite := range callSites { caller.markBlocking(callSite) } - delete(caller.localNamedCallees, callee) + delete(caller.localInstCallees, callee) done = false } } // Check direct calls to function literals. for callee, callSites := range caller.literalFuncCallees { - if len(info.FuncLitInfos[callee].Blocking) > 0 { + if info.funcLitInfos[callee].HasBlocking() { for _, callSite := range callSites { caller.markBlocking(callSite) } @@ -214,7 +227,7 @@ type FuncInfo struct { returnStmts []astPath // List of other named functions from the current package this function calls. // If any of them are blocking, this function will become blocking too. - localNamedCallees map[*types.Func][]astPath + localInstCallees *typeparams.InstanceMap[[]astPath] // List of function literals directly called from this function (for example: // `func() { /* do stuff */ }()`). This is distinct from function literals // assigned to named variables (for example: `doStuff := func() {}; @@ -226,6 +239,14 @@ type FuncInfo struct { visitorStack astPath } +// HasBlocking indicates if this function may block goroutine execution. +// +// For example, a channel operation in a function or a call to another +// possibly blocking function may block the function. +func (fi *FuncInfo) HasBlocking() bool { + return len(fi.Blocking) != 0 +} + func (fi *FuncInfo) Visit(node ast.Node) ast.Visitor { if node == nil { if len(fi.visitorStack) != 0 { @@ -338,7 +359,6 @@ func (fi *FuncInfo) visitCallExpr(n *ast.CallExpr) ast.Visitor { switch f := astutil.RemoveParens(n.Fun).(type) { case *ast.Ident: fi.callToNamedFunc(fi.pkgInfo.Uses[f]) - case *ast.SelectorExpr: if sel := fi.pkgInfo.Selections[f]; sel != nil && typesutil.IsJsObject(sel.Recv()) { // js.Object methods are known to be non-blocking, but we still must @@ -346,7 +366,6 @@ func (fi *FuncInfo) visitCallExpr(n *ast.CallExpr) ast.Visitor { } else { fi.callToNamedFunc(fi.pkgInfo.Uses[f.Sel]) } - case *ast.FuncLit: // Collect info about the function literal itself. ast.Walk(fi, n.Fun) @@ -358,7 +377,6 @@ func (fi *FuncInfo) visitCallExpr(n *ast.CallExpr) ast.Visitor { // Register literal function call site in case it is identified as blocking. fi.literalFuncCallees[f] = append(fi.literalFuncCallees[f], fi.visitorStack.copy()) return nil // No need to walk under this CallExpr, we already did it manually. - case *ast.IndexExpr: // Collect info about the instantiated type or function, or index expression. if astutil.IsTypeExpr(f, fi.pkgInfo.Info) { @@ -386,7 +404,6 @@ func (fi *FuncInfo) visitCallExpr(n *ast.CallExpr) ast.Visitor { // or not, we have to be conservative and assume that function might be blocking. fi.markBlocking(fi.visitorStack) } - case *ast.IndexListExpr: // Collect info about the instantiated type or function. if astutil.IsTypeExpr(f, fi.pkgInfo.Info) { @@ -401,7 +418,6 @@ func (fi *FuncInfo) visitCallExpr(n *ast.CallExpr) ast.Visitor { fmt.Printf(" >> %[1]d) %[2]T %#[2]v\n", i, index) } } - default: if astutil.IsTypeExpr(f, fi.pkgInfo.Info) { // This is a type conversion, not a call. Type assertion itself is not @@ -428,6 +444,7 @@ func (fi *FuncInfo) callToNamedFunc(callee types.Object) { } } if o.Pkg() != fi.pkgInfo.Pkg { + // TODO: make isImportedBlocking take an instance type. if fi.pkgInfo.isImportedBlocking(o) { fi.markBlocking(fi.visitorStack) } diff --git a/compiler/internal/analysis/info_test.go b/compiler/internal/analysis/info_test.go index 42a9d1620..a93f91e0d 100644 --- a/compiler/internal/analysis/info_test.go +++ b/compiler/internal/analysis/info_test.go @@ -1,6 +1,7 @@ package analysis import ( + "fmt" "go/ast" "go/types" "testing" @@ -597,29 +598,33 @@ func TestBlocking_InstantiationBlocking(t *testing.T) { } func blockingViaExplicit() { - FooBaz[BazBlocker](BazBlocker{c: make(chan bool)}) + FooBaz[BazBlocker](BazBlocker{c: make(chan bool)}) // line 21 } func notBlockingViaExplicit() { - FooBaz[BazNotBlocker](BazNotBlocker{}) + FooBaz[BazNotBlocker](BazNotBlocker{}) // line 25 } func blockingViaImplicit() { - FooBaz(BazBlocker{c: make(chan bool)}) + FooBaz(BazBlocker{c: make(chan bool)}) // line 29 } func notBlockingViaImplicit() { - FooBaz(BazNotBlocker{}) + FooBaz(BazNotBlocker{}) // line 33 }`) bt.assertBlocking(`FooBaz`) // generic instantiation is blocking bt.assertBlocking(`BazBlocker.Baz`) bt.assertBlocking(`blockingViaExplicit`) bt.assertBlocking(`blockingViaImplicit`) + bt.assertBlockingInst(`FooBaz`, 21) + bt.assertBlockingInst(`FooBaz`, 25) // should be the same instance as 21 bt.assertNotBlocking(`BazNotBlocker.Baz`) bt.assertNotBlocking(`notBlockingViaExplicit`) bt.assertNotBlocking(`notBlockingViaImplicit`) + bt.assertNotBlockingInst(`FooBaz`, 29) + bt.assertNotBlockingInst(`FooBaz`, 33) // should be the same instance as 33 } type blockingTest struct { @@ -647,13 +652,13 @@ func newBlockingTest(t *testing.T, src string) *blockingTest { func (bt *blockingTest) assertBlocking(funcName string) { if !bt.isTypesFuncBlocking(funcName) { - bt.f.T.Errorf(`Got: %q is not blocking. Want: %q is blocking.`, funcName, funcName) + bt.f.T.Errorf(`Got %q as not blocking but expected it to be blocking.`, funcName) } } func (bt *blockingTest) assertNotBlocking(funcName string) { if bt.isTypesFuncBlocking(funcName) { - bt.f.T.Errorf(`Got: %q is blocking. Want: %q is not blocking.`, funcName, funcName) + bt.f.T.Errorf(`Got %q as blocking but expected it to be blocking.`, funcName) } } @@ -680,18 +685,18 @@ func (bt *blockingTest) isTypesFuncBlocking(funcName string) bool { if !ok { bt.f.T.Fatalf(`No type information is found for %v.`, decl.Name) } - return bt.pkgInfo.IsBlocking(blockingType.(*types.Func)) + return bt.pkgInfo.funcDeclInfos[blockingType.(*types.Func)].HasBlocking() } func (bt *blockingTest) assertBlockingLit(lineNo int) { if !bt.isFuncLitBlocking(lineNo) { - bt.f.T.Errorf(`Got: FuncLit at line %d is not blocking. Want: FuncLit at line %d is blocking.`, lineNo, lineNo) + bt.f.T.Errorf(`Got FuncLit at line %d as not blocking but expected it to be blocking.`, lineNo) } } func (bt *blockingTest) assertNotBlockingLit(lineNo int) { if bt.isFuncLitBlocking(lineNo) { - bt.f.T.Errorf(`Got: FuncLit at line %d is blocking. Want: FuncLit at line %d is not blocking.`, lineNo, lineNo) + bt.f.T.Errorf(`Got FuncLit at line %d as blocking but expected it to be not blocking.`, lineNo) } } @@ -710,9 +715,56 @@ func (bt *blockingTest) isFuncLitBlocking(lineNo int) bool { if fnLit == nil { bt.f.T.Fatalf(`FuncLit found on line %d not found in the AST.`, lineNo) } - info, ok := bt.pkgInfo.FuncLitInfos[fnLit] + info, ok := bt.pkgInfo.funcLitInfos[fnLit] if !ok { bt.f.T.Fatalf(`No type information is found for FuncLit at line %d.`, lineNo) } - return len(info.Blocking) > 0 + return info.HasBlocking() +} + +func (bt *blockingTest) assertBlockingInst(funcName string, lineNo int) { + if !bt.isFuncInstBlocking(funcName, lineNo) { + bt.f.T.Errorf(`Got function instance of %s at line %d as not blocking but expected it to be blocking.`, funcName, lineNo) + } +} + +func (bt *blockingTest) assertNotBlockingInst(funcName string, lineNo int) { + if bt.isFuncInstBlocking(funcName, lineNo) { + bt.f.T.Errorf(`Got function instance of %s at line %d as blocking but expected it to be not blocking.`, funcName, lineNo) + } +} + +func (bt *blockingTest) isFuncInstBlocking(funcName string, lineNo int) bool { + var fnId *ast.Ident + ast.Inspect(bt.file, func(n ast.Node) bool { + if id, ok := n.(*ast.Ident); ok { + if id.Name == funcName && bt.f.FileSet.Position(id.NamePos).Line == lineNo { + fnId = id + return false + } + } + return fnId == nil + }) + + if fnId == nil { + bt.f.T.Errorf(`Function instance of %s at line %d was not found in AST.`, funcName, lineNo) + return false + } + + fmt.Println("--------------------") + fmt.Printf("name: %s, lineNo: %d, fnId: %v\n", funcName, lineNo, fnId) + fmt.Printf("uses: %v\n", bt.pkgInfo.Uses[fnId]) + fmt.Printf("defs: %v\n", bt.pkgInfo.Defs[fnId]) + inst := bt.pkgInfo.Instances[fnId] + fmt.Printf("inst: %v\n", inst) + sig := inst.Type.(*types.Signature) + fmt.Printf("sig: %v\n", sig) + for i := 0; i < inst.TypeArgs.Len(); i++ { + fmt.Printf("tp(%d) %v\n", i, inst.TypeArgs.At(i)) + } + + fun := types.NewFunc(fnId.Pos(), bt.pkgInfo.Pkg, funcName, sig) + fmt.Printf("fun: %v\n", fun) + + return true }