cmd/compile: create/use noder2 transform functions for more node types

Pull out the transformation part of the typechecking functions for:
 - assignment statements
 - return statements
 - send statements
 - select statements
 - type conversions
 - normal function/method calls
 - index operations

The transform functions are like the original typechecking functions,
but with all code removed related to:
  - Detecting compile-time errors (already done by types2)
  - Setting the actual type of existing nodes (already done based on
    info from types2)
  - Dealing with untyped constants

Moved all the transformation functions to a separate file, transform.go.

Continuing with the same pattern, we delay transforming a node if it has
any type params in its args, marking it with a typecheck flag of 3, and
do the actual transformation during stenciling.

Assignment statements are tricky, since their transformation must be
delayed if any of the left or right-hands-sides are delayed.

Still to do are:
 - selector expressions (OXDOT)
 - composite literal expressions (OCOMPLIT)
 - builtin function calls

Change-Id: Ie608cadbbc69b40db0067a5536cf707dd974aacc
Reviewed-on: https://go-review.googlesource.com/c/go/+/304049
Run-TryBot: Dan Scales <danscales@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Trust: Dan Scales <danscales@google.com>
Trust: Robert Griesemer <gri@golang.org>
Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
Dan Scales 2021-03-23 10:19:11 -07:00
parent 29ed12d4c7
commit e7e0995cba
7 changed files with 650 additions and 157 deletions

View file

@ -0,0 +1,523 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains transformation functions on nodes, which are the
// transformations that the typecheck package does that are distinct from the
// typechecking functionality. These transform functions are pared-down copies of
// the original typechecking functions, with all code removed that is related to:
//
// - Detecting compile-time errors (already done by types2)
// - Setting the actual type of existing nodes (already done based on
// type info from types2)
// - Dealing with untyped constants (which types2 has already resolved)
package noder
import (
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"cmd/compile/internal/typecheck"
"cmd/compile/internal/types"
"go/constant"
)
// Transformation functions for expressions
// transformAdd transforms an addition operation (currently just addition of
// strings). Corresponds to the "binary operators" case in typecheck.typecheck1.
func transformAdd(n *ir.BinaryExpr) ir.Node {
l := n.X
if l.Type().IsString() {
var add *ir.AddStringExpr
if l.Op() == ir.OADDSTR {
add = l.(*ir.AddStringExpr)
add.SetPos(n.Pos())
} else {
add = ir.NewAddStringExpr(n.Pos(), []ir.Node{l})
}
r := n.Y
if r.Op() == ir.OADDSTR {
r := r.(*ir.AddStringExpr)
add.List.Append(r.List.Take()...)
} else {
add.List.Append(r)
}
add.SetType(l.Type())
return add
}
return n
}
// Corresponds to typecheck.stringtoruneslit.
func stringtoruneslit(n *ir.ConvExpr) ir.Node {
if n.X.Op() != ir.OLITERAL || n.X.Val().Kind() != constant.String {
base.Fatalf("stringtoarraylit %v", n)
}
var l []ir.Node
i := 0
for _, r := range ir.StringVal(n.X) {
l = append(l, ir.NewKeyExpr(base.Pos, ir.NewInt(int64(i)), ir.NewInt(int64(r))))
i++
}
nn := ir.NewCompLitExpr(base.Pos, ir.OCOMPLIT, ir.TypeNode(n.Type()), nil)
nn.List = l
// Need to transform the OCOMPLIT.
// TODO(danscales): update this when we have written transformCompLit()
return typecheck.Expr(nn)
}
// transformConv transforms an OCONV node as needed, based on the types involved,
// etc. Corresponds to typecheck.tcConv.
func transformConv(n *ir.ConvExpr) ir.Node {
t := n.X.Type()
op, _ := typecheck.Convertop(n.X.Op() == ir.OLITERAL, t, n.Type())
assert(op != ir.OXXX)
n.SetOp(op)
switch n.Op() {
case ir.OCONVNOP:
if t.Kind() == n.Type().Kind() {
switch t.Kind() {
case types.TFLOAT32, types.TFLOAT64, types.TCOMPLEX64, types.TCOMPLEX128:
// Floating point casts imply rounding and
// so the conversion must be kept.
n.SetOp(ir.OCONV)
}
}
// Do not convert to []byte literal. See CL 125796.
// Generated code and compiler memory footprint is better without it.
case ir.OSTR2BYTES:
// ok
case ir.OSTR2RUNES:
if n.X.Op() == ir.OLITERAL {
return stringtoruneslit(n)
}
}
return n
}
// transformConvCall transforms a conversion call. Corresponds to the OTYPE part of
// typecheck.tcCall.
func transformConvCall(n *ir.CallExpr) ir.Node {
arg := n.Args[0]
n1 := ir.NewConvExpr(n.Pos(), ir.OCONV, nil, arg)
n1.SetType(n.X.Type())
return transformConv(n1)
}
// transformCall transforms a normal function/method call. Corresponds to last half
// (non-conversion, non-builtin part) of typecheck.tcCall.
func transformCall(n *ir.CallExpr) {
transformArgs(n)
l := n.X
t := l.Type()
switch l.Op() {
case ir.ODOTINTER:
n.SetOp(ir.OCALLINTER)
case ir.ODOTMETH:
l := l.(*ir.SelectorExpr)
n.SetOp(ir.OCALLMETH)
tp := t.Recv().Type
if l.X == nil || !types.Identical(l.X.Type(), tp) {
base.Fatalf("method receiver")
}
default:
n.SetOp(ir.OCALLFUNC)
}
typecheckaste(ir.OCALL, n.X, n.IsDDD, t.Params(), n.Args)
if t.NumResults() == 0 {
return
}
if t.NumResults() == 1 {
n.SetType(l.Type().Results().Field(0).Type)
if n.Op() == ir.OCALLFUNC && n.X.Op() == ir.ONAME {
if sym := n.X.(*ir.Name).Sym(); types.IsRuntimePkg(sym.Pkg) && sym.Name == "getg" {
// Emit code for runtime.getg() directly instead of calling function.
// Most such rewrites (for example the similar one for math.Sqrt) should be done in walk,
// so that the ordering pass can make sure to preserve the semantics of the original code
// (in particular, the exact time of the function call) by introducing temporaries.
// In this case, we know getg() always returns the same result within a given function
// and we want to avoid the temporaries, so we do the rewrite earlier than is typical.
n.SetOp(ir.OGETG)
}
}
return
}
}
// transformCompare transforms a compare operation (currently just equals/not
// equals). Corresponds to the "comparison operators" case in
// typecheck.typecheck1, including tcArith.
func transformCompare(n *ir.BinaryExpr) {
if (n.Op() == ir.OEQ || n.Op() == ir.ONE) && !types.Identical(n.X.Type(), n.Y.Type()) {
// Comparison is okay as long as one side is assignable to the
// other. The only allowed case where the conversion is not CONVNOP is
// "concrete == interface". In that case, check comparability of
// the concrete type. The conversion allocates, so only do it if
// the concrete type is huge.
l, r := n.X, n.Y
lt, rt := l.Type(), r.Type()
converted := false
if rt.Kind() != types.TBLANK {
aop, _ := typecheck.Assignop(lt, rt)
if aop != ir.OXXX {
types.CalcSize(lt)
if rt.IsInterface() == lt.IsInterface() || lt.Width >= 1<<16 {
l = ir.NewConvExpr(base.Pos, aop, rt, l)
l.SetTypecheck(1)
}
converted = true
}
}
if !converted && lt.Kind() != types.TBLANK {
aop, _ := typecheck.Assignop(rt, lt)
if aop != ir.OXXX {
types.CalcSize(rt)
if rt.IsInterface() == lt.IsInterface() || rt.Width >= 1<<16 {
r = ir.NewConvExpr(base.Pos, aop, lt, r)
r.SetTypecheck(1)
}
}
}
n.X, n.Y = l, r
}
}
// Corresponds to typecheck.implicitstar.
func implicitstar(n ir.Node) ir.Node {
// insert implicit * if needed for fixed array
t := n.Type()
if !t.IsPtr() {
return n
}
t = t.Elem()
if !t.IsArray() {
return n
}
star := ir.NewStarExpr(base.Pos, n)
star.SetImplicit(true)
return typed(t, star)
}
// transformIndex transforms an index operation. Corresponds to typecheck.tcIndex.
func transformIndex(n *ir.IndexExpr) {
n.X = implicitstar(n.X)
l := n.X
t := l.Type()
if t.Kind() == types.TMAP {
n.Index = typecheck.AssignConv(n.Index, t.Key(), "map index")
n.SetOp(ir.OINDEXMAP)
// Set type to just the map value, not (value, bool). This is
// different from types2, but fits the later stages of the
// compiler better.
n.SetType(t.Elem())
n.Assigned = false
}
}
// transformSlice transforms a slice operation. Corresponds to typecheck.tcSlice.
func transformSlice(n *ir.SliceExpr) {
l := n.X
if l.Type().IsArray() {
addr := typecheck.NodAddr(n.X)
addr.SetImplicit(true)
typed(types.NewPtr(n.X.Type()), addr)
n.X = addr
l = addr
}
t := l.Type()
if t.IsString() {
n.SetOp(ir.OSLICESTR)
} else if t.IsPtr() && t.Elem().IsArray() {
if n.Op().IsSlice3() {
n.SetOp(ir.OSLICE3ARR)
} else {
n.SetOp(ir.OSLICEARR)
}
}
}
// Transformation functions for statements
// Corresponds to typecheck.checkassign.
func transformCheckAssign(stmt ir.Node, n ir.Node) {
if n.Op() == ir.OINDEXMAP {
n := n.(*ir.IndexExpr)
n.Assigned = true
return
}
}
// Corresponds to typecheck.assign.
func transformAssign(stmt ir.Node, lhs, rhs []ir.Node) {
checkLHS := func(i int, typ *types.Type) {
transformCheckAssign(stmt, lhs[i])
}
cr := len(rhs)
if len(rhs) == 1 {
if rtyp := rhs[0].Type(); rtyp != nil && rtyp.IsFuncArgStruct() {
cr = rtyp.NumFields()
}
}
// x, ok = y
assignOK:
for len(lhs) == 2 && cr == 1 {
stmt := stmt.(*ir.AssignListStmt)
r := rhs[0]
switch r.Op() {
case ir.OINDEXMAP:
stmt.SetOp(ir.OAS2MAPR)
case ir.ORECV:
stmt.SetOp(ir.OAS2RECV)
case ir.ODOTTYPE:
r := r.(*ir.TypeAssertExpr)
stmt.SetOp(ir.OAS2DOTTYPE)
r.SetOp(ir.ODOTTYPE2)
default:
break assignOK
}
checkLHS(0, r.Type())
checkLHS(1, types.UntypedBool)
return
}
if len(lhs) != cr {
for i := range lhs {
checkLHS(i, nil)
}
return
}
// x,y,z = f()
if cr > len(rhs) {
stmt := stmt.(*ir.AssignListStmt)
stmt.SetOp(ir.OAS2FUNC)
r := rhs[0].(*ir.CallExpr)
r.Use = ir.CallUseList
rtyp := r.Type()
for i := range lhs {
checkLHS(i, rtyp.Field(i).Type)
}
return
}
for i, r := range rhs {
checkLHS(i, r.Type())
if lhs[i].Type() != nil {
rhs[i] = assignconvfn(r, lhs[i].Type())
}
}
}
// Corresponds to typecheck.typecheckargs.
func transformArgs(n ir.InitNode) {
var list []ir.Node
switch n := n.(type) {
default:
base.Fatalf("typecheckargs %+v", n.Op())
case *ir.CallExpr:
list = n.Args
if n.IsDDD {
return
}
case *ir.ReturnStmt:
list = n.Results
}
if len(list) != 1 {
return
}
t := list[0].Type()
if t == nil || !t.IsFuncArgStruct() {
return
}
// Rewrite f(g()) into t1, t2, ... = g(); f(t1, t2, ...).
// Save n as n.Orig for fmt.go.
if ir.Orig(n) == n {
n.(ir.OrigNode).SetOrig(ir.SepCopy(n))
}
as := ir.NewAssignListStmt(base.Pos, ir.OAS2, nil, nil)
as.Rhs.Append(list...)
// If we're outside of function context, then this call will
// be executed during the generated init function. However,
// init.go hasn't yet created it. Instead, associate the
// temporary variables with InitTodoFunc for now, and init.go
// will reassociate them later when it's appropriate.
static := ir.CurFunc == nil
if static {
ir.CurFunc = typecheck.InitTodoFunc
}
list = nil
for _, f := range t.FieldSlice() {
t := typecheck.Temp(f.Type)
as.PtrInit().Append(ir.NewDecl(base.Pos, ir.ODCL, t))
as.Lhs.Append(t)
list = append(list, t)
}
if static {
ir.CurFunc = nil
}
switch n := n.(type) {
case *ir.CallExpr:
n.Args = list
case *ir.ReturnStmt:
n.Results = list
}
transformAssign(as, as.Lhs, as.Rhs)
as.SetTypecheck(1)
n.PtrInit().Append(as)
}
// assignconvfn converts node n for assignment to type t. Corresponds to
// typecheck.assignconvfn.
func assignconvfn(n ir.Node, t *types.Type) ir.Node {
if t.Kind() == types.TBLANK {
return n
}
if types.Identical(n.Type(), t) {
return n
}
op, _ := typecheck.Assignop(n.Type(), t)
r := ir.NewConvExpr(base.Pos, op, t, n)
r.SetTypecheck(1)
r.SetImplicit(true)
return r
}
// Corresponds to typecheck.typecheckaste.
func typecheckaste(op ir.Op, call ir.Node, isddd bool, tstruct *types.Type, nl ir.Nodes) {
var t *types.Type
var i int
lno := base.Pos
defer func() { base.Pos = lno }()
var n ir.Node
if len(nl) == 1 {
n = nl[0]
}
i = 0
for _, tl := range tstruct.Fields().Slice() {
t = tl.Type
if tl.IsDDD() {
if isddd {
n = nl[i]
ir.SetPos(n)
if n.Type() != nil {
nl[i] = assignconvfn(n, t)
}
return
}
// TODO(mdempsky): Make into ... call with implicit slice.
for ; i < len(nl); i++ {
n = nl[i]
ir.SetPos(n)
if n.Type() != nil {
nl[i] = assignconvfn(n, t.Elem())
}
}
return
}
n = nl[i]
ir.SetPos(n)
if n.Type() != nil {
nl[i] = assignconvfn(n, t)
}
i++
}
}
// transformSend transforms a send statement, converting the value to appropriate
// type for the channel, as needed. Corresponds of typecheck.tcSend.
func transformSend(n *ir.SendStmt) {
n.Value = assignconvfn(n.Value, n.Chan.Type().Elem())
}
// transformReturn transforms a return node, by doing the needed assignments and
// any necessary conversions. Corresponds to typecheck.tcReturn()
func transformReturn(rs *ir.ReturnStmt) {
transformArgs(rs)
nl := rs.Results
if ir.HasNamedResults(ir.CurFunc) && len(nl) == 0 {
return
}
typecheckaste(ir.ORETURN, nil, false, ir.CurFunc.Type().Results(), nl)
}
// transformSelect transforms a select node, creating an assignment list as needed
// for each case. Corresponds to typecheck.tcSelect().
func transformSelect(sel *ir.SelectStmt) {
for _, ncase := range sel.Cases {
if ncase.Comm != nil {
n := ncase.Comm
oselrecv2 := func(dst, recv ir.Node, def bool) {
n := ir.NewAssignListStmt(n.Pos(), ir.OSELRECV2, []ir.Node{dst, ir.BlankNode}, []ir.Node{recv})
n.Def = def
n.SetTypecheck(1)
ncase.Comm = n
}
switch n.Op() {
case ir.OAS:
// convert x = <-c into x, _ = <-c
// remove implicit conversions; the eventual assignment
// will reintroduce them.
n := n.(*ir.AssignStmt)
if r := n.Y; r.Op() == ir.OCONVNOP || r.Op() == ir.OCONVIFACE {
r := r.(*ir.ConvExpr)
if r.Implicit() {
n.Y = r.X
}
}
oselrecv2(n.X, n.Y, n.Def)
case ir.OAS2RECV:
n := n.(*ir.AssignListStmt)
n.SetOp(ir.OSELRECV2)
case ir.ORECV:
// convert <-c into _, _ = <-c
n := n.(*ir.UnaryExpr)
oselrecv2(ir.BlankNode, n, false)
case ir.OSEND:
break
}
}
}
}
// transformAsOp transforms an AssignOp statement. Corresponds to OASOP case in
// typecheck1.
func transformAsOp(n *ir.AssignOpStmt) {
transformCheckAssign(n, n.X)
}