Skip to content

Commit 4d24395

Browse files
committed
Assign identity to all function literals and use them as funcRefs.
The main change is that we assign explicit names to all function objects that correspond to Go functions (named and literals). Function name is declared as `var f = function nameHere() { ... }` and is visible inside the function scope only. Doing so serves two purposes: - It is an identifier which we can use when saving state of a blocked function to know which function to call upon resumption. - It shows up in the stack trace, which helps distinguish similarly-named functions. For methods, we include the receiver type in the identifier to make A.String and B.String easily distinguishable. The main trick is that we synthesize names for the function literals, which are anonymous as far as go/types is concerned. The upstream Go compiler does something very similar. Lest but not least, with this change the generic-related code path in the funcContext.translateFunctionBody becomes more self-contained, which will become handy in the upcoming commits.
1 parent 1729b59 commit 4d24395

File tree

5 files changed

+134
-40
lines changed

5 files changed

+134
-40
lines changed

compiler/expressions.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression {
177177
}
178178

179179
case *ast.FuncLit:
180-
fun := fc.nestedFunctionContext(fc.pkgCtx.FuncLitInfos[e], exprType.(*types.Signature)).translateFunctionBody(e.Type, nil, e.Body, "")
180+
fun := fc.literalFuncContext(e).translateFunctionBody(e.Type, nil, e.Body)
181181
if len(fc.pkgCtx.escapingVars) != 0 {
182182
names := make([]string, 0, len(fc.pkgCtx.escapingVars))
183183
for obj := range fc.pkgCtx.escapingVars {

compiler/functions.go

Lines changed: 58 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,21 @@ import (
1919

2020
// newFunctionContext creates a new nested context for a function corresponding
2121
// to the provided info.
22-
func (fc *funcContext) nestedFunctionContext(info *analysis.FuncInfo, sig *types.Signature) *funcContext {
22+
func (fc *funcContext) nestedFunctionContext(info *analysis.FuncInfo, o *types.Func) *funcContext {
2323
if info == nil {
2424
panic(errors.New("missing *analysis.FuncInfo"))
2525
}
26-
if sig == nil {
27-
panic(errors.New("missing *types.Signature"))
26+
if o == nil {
27+
panic(errors.New("missing *types.Func"))
2828
}
2929

3030
c := &funcContext{
3131
FuncInfo: info,
32+
funcObject: o,
3233
pkgCtx: fc.pkgCtx,
3334
genericCtx: fc.genericCtx,
3435
parent: fc,
35-
sigTypes: &signatureTypes{Sig: sig},
36+
sigTypes: &signatureTypes{Sig: o.Type().(*types.Signature)},
3637
allVars: make(map[string]int, len(fc.allVars)),
3738
localVars: []string{},
3839
flowDatas: map[*types.Label]*flowData{nil: {}},
@@ -44,6 +45,42 @@ func (fc *funcContext) nestedFunctionContext(info *analysis.FuncInfo, sig *types
4445
c.allVars[k] = v
4546
}
4647

48+
// Synthesize an identifier by which the function may reference itself. Since
49+
// it appears in the stack trace, it's useful to include the receiver type in
50+
// it.
51+
funcRef := o.Name()
52+
if typeName := c.sigTypes.RecvTypeName(); typeName != "" {
53+
funcRef = typeName + midDot + funcRef
54+
}
55+
c.funcRef = c.newVariable(funcRef, varPackage)
56+
57+
// If the function has type parameters, create a new generic context for it.
58+
if c.sigTypes.IsGeneric() {
59+
c.genericCtx = &genericCtx{}
60+
}
61+
62+
return c
63+
}
64+
65+
// namedFuncContext creates a new funcContext for a named Go function
66+
// (standalone or method).
67+
func (fc *funcContext) namedFuncContext(fun *ast.FuncDecl) *funcContext {
68+
o := fc.pkgCtx.Defs[fun.Name].(*types.Func)
69+
info := fc.pkgCtx.FuncDeclInfos[o]
70+
c := fc.nestedFunctionContext(info, o)
71+
72+
return c
73+
}
74+
75+
// literalFuncContext creates a new funcContext for a function literal. Since
76+
// go/types doesn't generate *types.Func objects for function literals, we
77+
// generate a synthetic one for it.
78+
func (fc *funcContext) literalFuncContext(fun *ast.FuncLit) *funcContext {
79+
info := fc.pkgCtx.FuncLitInfos[fun]
80+
sig := fc.pkgCtx.TypeOf(fun).(*types.Signature)
81+
o := types.NewFunc(fun.Pos(), fc.pkgCtx.Pkg, fc.newLitFuncName(), sig)
82+
83+
c := fc.nestedFunctionContext(info, o)
4784
return c
4885
}
4986

@@ -68,9 +105,6 @@ func (fc *funcContext) translateTopLevelFunction(fun *ast.FuncDecl) []byte {
68105
// package context. Exported functions are also assigned to the `$pkg` object.
69106
func (fc *funcContext) translateStandaloneFunction(fun *ast.FuncDecl) []byte {
70107
o := fc.pkgCtx.Defs[fun.Name].(*types.Func)
71-
info := fc.pkgCtx.FuncDeclInfos[o]
72-
sig := o.Type().(*types.Signature)
73-
74108
if fun.Recv != nil {
75109
panic(fmt.Errorf("expected standalone function, got method: %s", o))
76110
}
@@ -79,7 +113,7 @@ func (fc *funcContext) translateStandaloneFunction(fun *ast.FuncDecl) []byte {
79113
if fun.Body == nil {
80114
return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fc.unimplementedFunction(o)))
81115
}
82-
body := fc.nestedFunctionContext(info, sig).translateFunctionBody(fun.Type, nil, fun.Body, lvalue)
116+
body := fc.namedFuncContext(fun).translateFunctionBody(fun.Type, nil, fun.Body)
83117

84118
code := &bytes.Buffer{}
85119
fmt.Fprintf(code, "\t%s = %s;\n", lvalue, body)
@@ -99,14 +133,11 @@ func (fc *funcContext) translateMethod(fun *ast.FuncDecl) []byte {
99133
}
100134

101135
o := fc.pkgCtx.Defs[fun.Name].(*types.Func)
102-
info := fc.pkgCtx.FuncDeclInfos[o]
103-
104-
sig := o.Type().(*types.Signature)
105136
var recv *ast.Ident
106137
if fun.Recv.List[0].Names != nil {
107138
recv = fun.Recv.List[0].Names[0]
108139
}
109-
nestedFC := fc.nestedFunctionContext(info, sig)
140+
nestedFC := fc.namedFuncContext(fun)
110141

111142
// primaryFunction generates a JS function equivalent of the current Go function
112143
// and assigns it to the JS expression defined by lvalue.
@@ -115,11 +146,11 @@ func (fc *funcContext) translateMethod(fun *ast.FuncDecl) []byte {
115146
return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fc.unimplementedFunction(o)))
116147
}
117148

118-
funDef := nestedFC.translateFunctionBody(fun.Type, recv, fun.Body, lvalue)
149+
funDef := nestedFC.translateFunctionBody(fun.Type, recv, fun.Body)
119150
return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, funDef))
120151
}
121152

122-
recvType := sig.Recv().Type()
153+
recvType := nestedFC.sigTypes.Sig.Recv().Type()
123154
ptr, isPointer := recvType.(*types.Pointer)
124155
namedRecvType, _ := recvType.(*types.Named)
125156
if isPointer {
@@ -131,7 +162,7 @@ func (fc *funcContext) translateMethod(fun *ast.FuncDecl) []byte {
131162
// Objects the method should be assigned to.
132163
prototypeVar := fmt.Sprintf("%s.prototype.%s", typeName, funName)
133164
ptrPrototypeVar := fmt.Sprintf("$ptrType(%s).prototype.%s", typeName, funName)
134-
isGeneric := signatureTypes{Sig: sig}.IsGeneric()
165+
isGeneric := nestedFC.sigTypes.IsGeneric()
135166
if isGeneric {
136167
// Generic method factories are assigned to the generic type factory
137168
// properties, to be invoked at type construction time rather than method
@@ -194,23 +225,7 @@ func (fc *funcContext) unimplementedFunction(o *types.Func) string {
194225
return fmt.Sprintf("function() {\n\t\t$throwRuntimeError(\"native function not implemented: %s\");\n\t}", o.FullName())
195226
}
196227

197-
func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt, funcRef string) string {
198-
functionName := "" // Function object name, i.e. identifier after the "function" keyword.
199-
if funcRef == "" {
200-
// Assign a name for the anonymous function.
201-
funcRef = "$b"
202-
functionName = " $b"
203-
}
204-
205-
// For regular functions instance is directly in the function variable name.
206-
instanceVar := funcRef
207-
if fc.sigTypes.IsGeneric() {
208-
fc.genericCtx = &genericCtx{}
209-
// For generic function, funcRef refers to the generic factory function,
210-
// allocate a separate variable for a function instance.
211-
instanceVar = fc.newVariable("instance", varGenericFactory)
212-
}
213-
228+
func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt) string {
214229
prevEV := fc.pkgCtx.escapingVars
215230

216231
params := fc.funcParamVars(typ)
@@ -272,10 +287,13 @@ func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident,
272287
// $c indicates that a function is being resumed after a blocking call when set to true.
273288
// $f is an object used to save and restore function context for blocking calls.
274289
localVars = append(localVars, "$r")
290+
// funcRef identifies the function object itself, so it doesn't need to be saved
291+
// or restored.
292+
localVars = removeMatching(localVars, fc.funcRef)
275293
// If a blocking function is being resumed, initialize local variables from the saved context.
276294
localVarDefs = fmt.Sprintf("var {%s, $c} = $restore(this, {%s});\n", strings.Join(localVars, ", "), strings.Join(params, ", "))
277295
// If the function gets blocked, save local variables for future.
278-
saveContext := fmt.Sprintf("var $f = {$blk: %s, $c: true, $r, %s};", instanceVar, strings.Join(fc.localVars, ", "))
296+
saveContext := fmt.Sprintf("var $f = {$blk: %s, $c: true, %s};", fc.funcRef, strings.Join(localVars, ", "))
279297

280298
suffix = " " + saveContext + "return $f;" + suffix
281299
} else if len(fc.localVars) > 0 {
@@ -324,9 +342,13 @@ func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident,
324342
fc.pkgCtx.escapingVars = prevEV
325343

326344
if !fc.sigTypes.IsGeneric() {
327-
return fmt.Sprintf("function%s(%s) {\n%s%s}", functionName, strings.Join(params, ", "), bodyOutput, fc.Indentation(0))
345+
return fmt.Sprintf("function %s(%s) {\n%s%s}", fc.funcRef, strings.Join(params, ", "), bodyOutput, fc.Indentation(0))
328346
}
329347

348+
// For generic function, funcRef refers to the generic factory function,
349+
// allocate a separate variable for a function instance.
350+
instanceVar := fc.newVariable("instance", varGenericFactory)
351+
330352
// Generic functions are generated as factories to allow passing type parameters
331353
// from the call site.
332354
// TODO(nevkontakte): Cache function instances for a given combination of type
@@ -341,9 +363,9 @@ func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident,
341363
}
342364

343365
code := &strings.Builder{}
344-
fmt.Fprintf(code, "function%s(%s){\n", functionName, strings.Join(typeParams, ", "))
366+
fmt.Fprintf(code, "function(%s){\n", strings.Join(typeParams, ", "))
345367
fmt.Fprintf(code, "%s", typesInit.String())
346-
fmt.Fprintf(code, "%sconst %s = function(%s) {\n", fc.Indentation(1), instanceVar, strings.Join(params, ", "))
368+
fmt.Fprintf(code, "%sconst %s = function %s(%s) {\n", fc.Indentation(1), instanceVar, fc.funcRef, strings.Join(params, ", "))
347369
fmt.Fprintf(code, "%s", bodyOutput)
348370
fmt.Fprintf(code, "%s};\n", fc.Indentation(1))
349371
// TODO(nevkontakte): Method list entries for generic type methods should be

compiler/natives/src/reflect/reflect.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1734,21 +1734,22 @@ func methodNameSkip() string {
17341734
}
17351735
// Function name extracted from the call stack can be different from vanilla
17361736
// Go. Here we try to fix stuff like "Object.$packages.reflect.Q.ptr.SetIterKey"
1737-
// into "Value.SetIterKey".
1737+
// or plain "SetIterKey" into "Value.SetIterKey".
17381738
// This workaround may become obsolete after https://github.com/gopherjs/gopherjs/issues/1085
17391739
// is resolved.
17401740
name := f.Name()
17411741
idx := len(name) - 1
17421742
for idx > 0 {
17431743
if name[idx] == '.' {
1744+
idx++
17441745
break
17451746
}
17461747
idx--
17471748
}
17481749
if idx < 0 {
17491750
return name
17501751
}
1751-
return "Value" + name[idx:]
1752+
return "Value." + name[idx:]
17521753
}
17531754

17541755
func verifyNotInHeapPtr(p uintptr) bool {

compiler/package.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ func (sel *fakeSelection) Type() types.Type { return sel.typ }
9494
// JavaScript code (as defined for `var` declarations).
9595
type funcContext struct {
9696
*analysis.FuncInfo
97+
// Function object this context corresponds to, or nil if the context is
98+
// top-level or doesn't correspond to a function. For function literals, this
99+
// is a synthetic object that assigns a unique identity to the function.
100+
funcObject *types.Func
101+
// JavaScript identifier assigned to the function object (the word after the
102+
// "function" keyword in the generated code). This identifier can be used
103+
// within the function scope to reference the function object. It will also
104+
// appear in the stack trace.
105+
funcRef string
97106
// Surrounding package context.
98107
pkgCtx *pkgContext
99108
// Surrounding generic function context. nil if non-generic code.
@@ -137,6 +146,8 @@ type funcContext struct {
137146
posAvailable bool
138147
// Current position in the Go source code.
139148
pos token.Pos
149+
// Number of function literals encountered within the current function context.
150+
funcLitCounter int
140151
}
141152

142153
// newRootCtx creates a new package-level instance of a functionContext object.

compiler/utils.go

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ import (
2222
"github.com/gopherjs/gopherjs/compiler/typesutil"
2323
)
2424

25+
// We use this character as a separator in synthetic identifiers instead of a
26+
// regular dot. This character is safe for use in JS identifiers and helps to
27+
// visually separate components of the name when it appears in a stack trace.
28+
const midDot = "·"
29+
2530
// IsRoot returns true for the package-level context.
2631
func (fc *funcContext) IsRoot() bool {
2732
return fc.parent == nil
@@ -410,6 +415,25 @@ func (fc *funcContext) newIdentFor(obj types.Object) *ast.Ident {
410415
return ident
411416
}
412417

418+
// newLitFuncName generates a new synthetic name for a function literal.
419+
func (fc *funcContext) newLitFuncName() string {
420+
fc.funcLitCounter++
421+
name := &strings.Builder{}
422+
423+
// If function literal is defined inside another function, qualify its
424+
// synthetic name with the outer function to make it easier to identify.
425+
if fc.funcObject != nil {
426+
if recvType := fc.sigTypes.RecvTypeName(); recvType != "" {
427+
name.WriteString(recvType)
428+
name.WriteString(midDot)
429+
}
430+
name.WriteString(fc.funcObject.Name())
431+
name.WriteString(midDot)
432+
}
433+
fmt.Fprintf(name, "func%d", fc.funcLitCounter)
434+
return name.String()
435+
}
436+
413437
// typeParamVars returns a list of JS variable names representing type given
414438
// parameters.
415439
func (fc *funcContext) typeParamVars(params *types.TypeParamList) []string {
@@ -502,6 +526,7 @@ func (fc *funcContext) methodName(fun *types.Func) string {
502526
}
503527
return name
504528
}
529+
505530
func (fc *funcContext) varPtrName(o *types.Var) string {
506531
if getVarLevel(o) == varPackage && o.Exported() {
507532
return fc.pkgVar(o.Pkg()) + "." + o.Name() + "$ptr"
@@ -903,7 +928,15 @@ func rangeCheck(pattern string, constantIndex, array bool) string {
903928
}
904929

905930
func encodeIdent(name string) string {
906-
return strings.Replace(url.QueryEscape(name), "%", "$", -1)
931+
// Quick-and-dirty way to make any string safe for use as an identifier in JS.
932+
name = url.QueryEscape(name)
933+
// We use unicode middle dot as a visual separator in synthetic identifiers.
934+
// It is safe for use in a JS identifier, so we un-encode it for readability.
935+
name = strings.ReplaceAll(name, "%C2%B7", midDot)
936+
// QueryEscape uses '%' before hex-codes of escaped characters, which is not
937+
// allowed in a JS identifier, use '$' instead.
938+
name = strings.ReplaceAll(name, "%", "$")
939+
return name
907940
}
908941

909942
// formatJSStructTagVal returns JavaScript code for accessing an object's property
@@ -999,6 +1032,23 @@ func (st signatureTypes) IsGeneric() bool {
9991032
return st.Sig.TypeParams().Len() > 0 || st.Sig.RecvTypeParams().Len() > 0
10001033
}
10011034

1035+
// RecvTypeName returns receiver type name for a method signature. For pointer
1036+
// receivers the named type is unwrapped from the pointer type. For non-methods
1037+
// an empty string is returned.
1038+
func (st signatureTypes) RecvTypeName() string {
1039+
recv := st.Sig.Recv()
1040+
if recv == nil {
1041+
return ""
1042+
}
1043+
1044+
typ := recv.Type()
1045+
if ptrType, ok := typ.(*types.Pointer); ok {
1046+
typ = ptrType.Elem()
1047+
}
1048+
1049+
return typ.(*types.Named).Obj().Name()
1050+
}
1051+
10021052
// ErrorAt annotates an error with a position in the source code.
10031053
func ErrorAt(err error, fset *token.FileSet, pos token.Pos) error {
10041054
return fmt.Errorf("%s: %w", fset.Position(pos), err)
@@ -1057,3 +1107,13 @@ func bailingOut(err interface{}) (*FatalError, bool) {
10571107
fe, ok := err.(*FatalError)
10581108
return fe, ok
10591109
}
1110+
1111+
func removeMatching[T comparable](haystack []T, needle T) []T {
1112+
var result []T
1113+
for _, el := range haystack {
1114+
if el != needle {
1115+
result = append(result, el)
1116+
}
1117+
}
1118+
return result
1119+
}

0 commit comments

Comments
 (0)