]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: use descriptors for type assertion runtime calls
authorKeith Randall <khr@golang.org>
Mon, 18 Sep 2023 17:13:18 +0000 (10:13 -0700)
committerKeith Randall <khr@golang.org>
Fri, 6 Oct 2023 17:02:26 +0000 (17:02 +0000)
Mostly a reorganization to make further changes easier.

This reorganization will make it easier to add a cache in front
of the runtime call.

Leave the old code alone for dynamic type assertions (aka generics).

Change-Id: Ia7dcb7aeb1f63baf93584ccd792e8e31510e8aea
Reviewed-on: https://go-review.googlesource.com/c/go/+/529196
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
Reviewed-by: Keith Randall <khr@google.com>
src/cmd/compile/internal/ir/expr.go
src/cmd/compile/internal/ir/symtab.go
src/cmd/compile/internal/objw/objw.go
src/cmd/compile/internal/ssagen/ssa.go
src/cmd/compile/internal/typecheck/_builtin/runtime.go
src/cmd/compile/internal/typecheck/builtin.go
src/cmd/compile/internal/walk/expr.go
src/internal/abi/switch.go
src/runtime/iface.go

index e20c342bfb950209d6dad4c869d859579f0941fa..7704a23d5fb3e34a45ee47e388cf41c45f42c3b1 100644 (file)
@@ -673,6 +673,9 @@ type TypeAssertExpr struct {
        // Runtime type information provided by walkDotType for
        // assertions from non-empty interface to concrete type.
        ITab Node `mknode:"-"` // *runtime.itab for Type implementing X's type
+
+       // An internal/abi.TypeAssert descriptor to pass to the runtime.
+       Descriptor *obj.LSym
 }
 
 func NewTypeAssertExpr(pos src.XPos, x Node, typ *types.Type) *TypeAssertExpr {
index 2c366ec7bd80c14bb26eeccc722f56366362ae39..202c4942dea86f6f947772da56b0eeb0648ea396 100644 (file)
@@ -50,6 +50,7 @@ type symsStruct struct {
        Racereadrange     *obj.LSym
        Racewrite         *obj.LSym
        Racewriterange    *obj.LSym
+       TypeAssert        *obj.LSym
        WBZero            *obj.LSym
        WBMove            *obj.LSym
        // Wasm
index 4189337b8f106ce976795773d5f6e17716d13108..ec1be325f7647b3d4eb6914da557aac5cac07765 100644 (file)
@@ -29,6 +29,14 @@ func Uintptr(s *obj.LSym, off int, v uint64) int {
        return UintN(s, off, v, types.PtrSize)
 }
 
+func Bool(s *obj.LSym, off int, v bool) int {
+       w := 0
+       if v {
+               w = 1
+       }
+       return UintN(s, off, uint64(w), 1)
+}
+
 // UintN writes an unsigned integer v of size wid bytes into s at offset off,
 // and returns the next unused offset.
 func UintN(s *obj.LSym, off int, v uint64, wid int) int {
index 56acd05fc240166c8a971b9806630326176c4c6b..a438cc77938e617ea1598713e2bc7ad06a5f9c19 100644 (file)
@@ -139,6 +139,7 @@ func InitConfig() {
        ir.Syms.Racereadrange = typecheck.LookupRuntimeFunc("racereadrange")
        ir.Syms.Racewrite = typecheck.LookupRuntimeFunc("racewrite")
        ir.Syms.Racewriterange = typecheck.LookupRuntimeFunc("racewriterange")
+       ir.Syms.TypeAssert = typecheck.LookupRuntimeFunc("typeAssert")
        ir.Syms.WBZero = typecheck.LookupRuntimeFunc("wbZero")
        ir.Syms.WBMove = typecheck.LookupRuntimeFunc("wbMove")
        ir.Syms.X86HasPOPCNT = typecheck.LookupRuntimeVar("x86HasPOPCNT")       // bool
@@ -6528,7 +6529,7 @@ func (s *state) dottype(n *ir.TypeAssertExpr, commaok bool) (res, resok *ssa.Val
        if n.ITab != nil {
                targetItab = s.expr(n.ITab)
        }
-       return s.dottype1(n.Pos(), n.X.Type(), n.Type(), iface, nil, target, targetItab, commaok)
+       return s.dottype1(n.Pos(), n.X.Type(), n.Type(), iface, nil, target, targetItab, commaok, n.Descriptor)
 }
 
 func (s *state) dynamicDottype(n *ir.DynamicTypeAssertExpr, commaok bool) (res, resok *ssa.Value) {
@@ -6546,7 +6547,7 @@ func (s *state) dynamicDottype(n *ir.DynamicTypeAssertExpr, commaok bool) (res,
        } else {
                target = s.expr(n.RType)
        }
-       return s.dottype1(n.Pos(), n.X.Type(), n.Type(), iface, source, target, targetItab, commaok)
+       return s.dottype1(n.Pos(), n.X.Type(), n.Type(), iface, source, target, targetItab, commaok, nil)
 }
 
 // dottype1 implements a x.(T) operation. iface is the argument (x), dst is the type we're asserting to (T)
@@ -6555,7 +6556,9 @@ func (s *state) dynamicDottype(n *ir.DynamicTypeAssertExpr, commaok bool) (res,
 // target is the *runtime._type of dst.
 // If src is a nonempty interface and dst is not an interface, targetItab is an itab representing (dst, src). Otherwise it is nil.
 // commaok is true if the caller wants a boolean success value. Otherwise, the generated code panics if the conversion fails.
-func (s *state) dottype1(pos src.XPos, src, dst *types.Type, iface, source, target, targetItab *ssa.Value, commaok bool) (res, resok *ssa.Value) {
+// descriptor is a compiler-allocated internal/abi.TypeAssert whose address is passed to runtime.typeAssert when
+// the target type is a compile-time-known non-empty interface. It may be nil.
+func (s *state) dottype1(pos src.XPos, src, dst *types.Type, iface, source, target, targetItab *ssa.Value, commaok bool, descriptor *obj.LSym) (res, resok *ssa.Value) {
        byteptr := s.f.Config.Types.BytePtr
        if dst.IsInterface() {
                if dst.IsEmptyInterface() {
@@ -6631,26 +6634,66 @@ func (s *state) dottype1(pos src.XPos, src, dst *types.Type, iface, source, targ
                if base.Debug.TypeAssert > 0 {
                        base.WarnfAt(pos, "type assertion not inlined")
                }
-               var fn *obj.LSym
+
+               itab := s.newValue1(ssa.OpITab, byteptr, iface)
+               data := s.newValue1(ssa.OpIData, types.Types[types.TUNSAFEPTR], iface)
+
                if commaok {
-                       fn = ir.Syms.AssertI2I2
-                       if src.IsEmptyInterface() {
-                               fn = ir.Syms.AssertE2I2
-                       }
+                       // Use a variable to hold the resulting itab. This allows us
+                       // to merge a value from the nil and non-nil branches.
+                       // (This assignment will be the nil result.)
+                       s.vars[typVar] = itab
+               }
+
+               // First, check for nil.
+               bNil := s.f.NewBlock(ssa.BlockPlain)
+               bNonNil := s.f.NewBlock(ssa.BlockPlain)
+               cond := s.newValue2(ssa.OpNeqPtr, types.Types[types.TBOOL], itab, s.constNil(byteptr))
+               b := s.endBlock()
+               b.Kind = ssa.BlockIf
+               b.SetControl(cond)
+               b.Likely = ssa.BranchLikely
+               b.AddEdgeTo(bNonNil)
+               b.AddEdgeTo(bNil)
+
+               if !commaok {
+                       // Panic if input is nil.
+                       s.startBlock(bNil)
+                       s.rtcall(ir.Syms.Panicnildottype, false, nil, target)
+               }
+
+               // Get typ, possibly by loading out of itab.
+               s.startBlock(bNonNil)
+               typ := itab
+               if !src.IsEmptyInterface() {
+                       typ = s.load(byteptr, s.newValue1I(ssa.OpOffPtr, byteptr, int64(types.PtrSize), itab))
+               }
+
+               // Call into runtime to get itab for result.
+               if descriptor != nil {
+                       d := s.newValue1A(ssa.OpAddr, byteptr, descriptor, s.sb)
+                       itab = s.rtcall(ir.Syms.TypeAssert, true, []*types.Type{byteptr}, d, typ)[0]
                } else {
-                       fn = ir.Syms.AssertI2I
-                       if src.IsEmptyInterface() {
+                       var fn *obj.LSym
+                       if commaok {
+                               fn = ir.Syms.AssertE2I2
+                       } else {
                                fn = ir.Syms.AssertE2I
                        }
+                       itab = s.rtcall(fn, true, []*types.Type{byteptr}, target, typ)[0]
                }
-               data := s.newValue1(ssa.OpIData, types.Types[types.TUNSAFEPTR], iface)
-               tab := s.newValue1(ssa.OpITab, byteptr, iface)
-               tab = s.rtcall(fn, true, []*types.Type{byteptr}, target, tab)[0]
-               var ok *ssa.Value
+               // Build result.
                if commaok {
-                       ok = s.newValue2(ssa.OpNeqPtr, types.Types[types.TBOOL], tab, s.constNil(byteptr))
+                       // Merge the nil result and the runtime call result.
+                       s.vars[typVar] = itab
+                       b := s.endBlock()
+                       b.AddEdgeTo(bNil)
+                       s.startBlock(bNil)
+                       itab = s.variable(typVar, byteptr)
+                       ok := s.newValue2(ssa.OpNeqPtr, types.Types[types.TBOOL], itab, s.constNil(byteptr))
+                       return s.newValue2(ssa.OpIMake, dst, itab, data), ok
                }
-               return s.newValue2(ssa.OpIMake, dst, tab, data), ok
+               return s.newValue2(ssa.OpIMake, dst, itab, data), nil
        }
 
        if base.Debug.TypeAssert > 0 {
index ead4a8d219db00ea654d423bb0b4a12211980d25..9f6f0665fc222c5cdf44f4d37638059ce80fcec4 100644 (file)
@@ -106,11 +106,10 @@ func convTslice(val []uint8) unsafe.Pointer
 // interface type assertions x.(T)
 func assertE2I(inter *byte, typ *byte) *byte
 func assertE2I2(inter *byte, typ *byte) *byte
-func assertI2I(inter *byte, tab *byte) *byte
-func assertI2I2(inter *byte, tab *byte) *byte
 func panicdottypeE(have, want, iface *byte)
 func panicdottypeI(have, want, iface *byte)
 func panicnildottype(want *byte)
+func typeAssert(s *byte, typ *byte) *byte
 
 // interface switches
 func interfaceSwitch(s *byte, t *byte) (int, *byte)
index d9efa128dfc63c4751688bd305c1824ebbef6d47..b141f4b0a9c2eef692036907d039ef5004f84722 100644 (file)
@@ -96,11 +96,10 @@ var runtimeDecls = [...]struct {
        {"convTslice", funcTag, 68},
        {"assertE2I", funcTag, 69},
        {"assertE2I2", funcTag, 69},
-       {"assertI2I", funcTag, 69},
-       {"assertI2I2", funcTag, 69},
        {"panicdottypeE", funcTag, 70},
        {"panicdottypeI", funcTag, 70},
        {"panicnildottype", funcTag, 71},
+       {"typeAssert", funcTag, 69},
        {"interfaceSwitch", funcTag, 72},
        {"ifaceeq", funcTag, 73},
        {"efaceeq", funcTag, 73},
index 45a6e43527d0b52263ea868529536d8661e62e10..a3caa4db36ddf6ecfbe1af25f1bddbf50b7bd2b8 100644 (file)
@@ -12,6 +12,7 @@ import (
 
        "cmd/compile/internal/base"
        "cmd/compile/internal/ir"
+       "cmd/compile/internal/objw"
        "cmd/compile/internal/reflectdata"
        "cmd/compile/internal/staticdata"
        "cmd/compile/internal/typecheck"
@@ -724,14 +725,41 @@ func walkDotType(n *ir.TypeAssertExpr, init *ir.Nodes) ir.Node {
        if !n.Type().IsInterface() && !n.X.Type().IsEmptyInterface() {
                n.ITab = reflectdata.ITabAddrAt(base.Pos, n.Type(), n.X.Type())
        }
+       if n.X.Type().IsInterface() && n.Type().IsInterface() && !n.Type().IsEmptyInterface() {
+               // Converting an interface to a non-empty interface. Needs a runtime call.
+               // Allocate an internal/abi.TypeAssert descriptor for that call.
+               lsym := types.LocalPkg.Lookup(fmt.Sprintf(".typeAssert.%d", typeAssertGen)).LinksymABI(obj.ABI0)
+               typeAssertGen++
+               off := 0
+               off = objw.SymPtr(lsym, off, reflectdata.TypeSym(n.Type()).Linksym(), 0)
+               off = objw.Bool(lsym, off, n.Op() == ir.ODOTTYPE2) // CanFail
+               off += types.PtrSize - 1
+               objw.Global(lsym, int32(off), obj.LOCAL|obj.NOPTR)
+               n.Descriptor = lsym
+       }
        return n
 }
 
+var typeAssertGen int
+
 // walkDynamicDotType walks an ODYNAMICDOTTYPE or ODYNAMICDOTTYPE2 node.
 func walkDynamicDotType(n *ir.DynamicTypeAssertExpr, init *ir.Nodes) ir.Node {
        n.X = walkExpr(n.X, init)
        n.RType = walkExpr(n.RType, init)
        n.ITab = walkExpr(n.ITab, init)
+       // Convert to non-dynamic if we can.
+       if n.RType != nil && n.RType.Op() == ir.OADDR {
+               addr := n.RType.(*ir.AddrExpr)
+               if addr.X.Op() == ir.OLINKSYMOFFSET {
+                       r := ir.NewTypeAssertExpr(n.Pos(), n.X, n.Type())
+                       if n.Op() == ir.ODYNAMICDOTTYPE2 {
+                               r.SetOp(ir.ODOTTYPE2)
+                       }
+                       r.SetType(n.Type())
+                       r.SetTypecheck(1)
+                       return walkExpr(r, init)
+               }
+       }
        return n
 }
 
index 5c1171c2f47f208fe65e5a90feb8d4150c633448..495580f9df404bef72bc7c7151ae1fb50331078f 100644 (file)
@@ -42,3 +42,8 @@ func UseInterfaceSwitchCache(goarch string) bool {
                return false
        }
 }
+
+type TypeAssert struct {
+       Inter   *InterfaceType
+       CanFail bool
+}
index 911b86cd373faf6994223f815d43f4b62dfc93d8..7a2c257b13176b9b30926d2f699b9f751772fd28 100644 (file)
@@ -417,21 +417,6 @@ func convI2I(dst *interfacetype, src *itab) *itab {
        return getitab(dst, src._type, false)
 }
 
-func assertI2I(inter *interfacetype, tab *itab) *itab {
-       if tab == nil {
-               // explicit conversions require non-nil interface value.
-               panic(&TypeAssertionError{nil, nil, &inter.Type, ""})
-       }
-       return getitab(inter, tab._type, false)
-}
-
-func assertI2I2(inter *interfacetype, tab *itab) *itab {
-       if tab == nil {
-               return nil
-       }
-       return getitab(inter, tab._type, true)
-}
-
 func assertE2I(inter *interfacetype, t *_type) *itab {
        if t == nil {
                // explicit conversions require non-nil interface value.
@@ -447,6 +432,19 @@ func assertE2I2(inter *interfacetype, t *_type) *itab {
        return getitab(inter, t, true)
 }
 
+// typeAssert builds an itab for the concrete type t and the
+// interface type s.Inter. If the conversion is not possible it
+// panics if s.CanFail is false and returns nil if s.CanFail is true.
+func typeAssert(s *abi.TypeAssert, t *_type) *itab {
+       if t == nil {
+               if s.CanFail {
+                       return nil
+               }
+               panic(&TypeAssertionError{nil, nil, &s.Inter.Type, ""})
+       }
+       return getitab(s.Inter, t, s.CanFail)
+}
+
 // interfaceSwitch compares t against the list of cases in s.
 // If t matches case i, interfaceSwitch returns the case index i and
 // an itab for the pair <t, s.Cases[i]>.