@@ -14,6 +14,7 @@ import (
14
14
"cmd/compile/internal/staticinit"
15
15
"cmd/compile/internal/typecheck"
16
16
"cmd/compile/internal/types"
17
+ "cmd/internal/objabi"
17
18
"cmd/internal/src"
18
19
)
19
20
@@ -731,6 +732,9 @@ func (o *orderState) stmt(n ir.Node) {
731
732
t := o .markTemp ()
732
733
o .init (n .Call )
733
734
o .call (n .Call )
735
+ if objabi .Experiment .RegabiDefer {
736
+ o .wrapGoDefer (n )
737
+ }
734
738
o .out = append (o .out , n )
735
739
o .cleanTemp (t )
736
740
@@ -1435,3 +1439,247 @@ func (o *orderState) as2ok(n *ir.AssignListStmt) {
1435
1439
o .out = append (o .out , n )
1436
1440
o .stmt (typecheck .Stmt (as ))
1437
1441
}
1442
+
1443
+ var wrapGoDefer_prgen int
1444
+
1445
+ // wrapGoDefer wraps the target of a "go" or "defer" statement with a
1446
+ // new "function with no arguments" closure. Specifically, it converts
1447
+ //
1448
+ // defer f(x, y)
1449
+ //
1450
+ // to
1451
+ //
1452
+ // x1, y1 := x, y
1453
+ // defer func() { f(x1, y1) }()
1454
+ //
1455
+ // This is primarily to enable a quicker bringup of defers under the
1456
+ // new register ABI; by doing this conversion, we can simplify the
1457
+ // code in the runtime that invokes defers on the panic path.
1458
+ func (o * orderState ) wrapGoDefer (n * ir.GoDeferStmt ) {
1459
+ call := n .Call
1460
+
1461
+ var callX ir.Node // thing being called
1462
+ var callArgs []ir.Node // call arguments
1463
+
1464
+ // A helper to recreate the call within the closure.
1465
+ var mkNewCall func (pos src.XPos , op ir.Op , fun ir.Node , args []ir.Node ) ir.Node
1466
+
1467
+ // Defer calls come in many shapes and sizes; not all of them
1468
+ // are ir.CallExpr's. Examine the type to see what we're dealing with.
1469
+ switch x := call .(type ) {
1470
+ case * ir.CallExpr :
1471
+ callX = x .X
1472
+ callArgs = x .Args
1473
+ mkNewCall = func (pos src.XPos , op ir.Op , fun ir.Node , args []ir.Node ) ir.Node {
1474
+ newcall := ir .NewCallExpr (pos , op , fun , args )
1475
+ newcall .IsDDD = x .IsDDD
1476
+ return ir .Node (newcall )
1477
+ }
1478
+ case * ir.UnaryExpr : // ex: OCLOSE
1479
+ callArgs = []ir.Node {x .X }
1480
+ mkNewCall = func (pos src.XPos , op ir.Op , fun ir.Node , args []ir.Node ) ir.Node {
1481
+ if len (args ) != 1 {
1482
+ panic ("internal error, expecting single arg to close" )
1483
+ }
1484
+ return ir .Node (ir .NewUnaryExpr (pos , op , args [0 ]))
1485
+ }
1486
+ case * ir.BinaryExpr : // ex: OCOPY
1487
+ callArgs = []ir.Node {x .X , x .Y }
1488
+ mkNewCall = func (pos src.XPos , op ir.Op , fun ir.Node , args []ir.Node ) ir.Node {
1489
+ if len (args ) != 2 {
1490
+ panic ("internal error, expecting two args" )
1491
+ }
1492
+ return ir .Node (ir .NewBinaryExpr (pos , op , args [0 ], args [1 ]))
1493
+ }
1494
+ default :
1495
+ panic ("unhandled op" )
1496
+ }
1497
+
1498
+ // No need to wrap if called func has no args. However in the case
1499
+ // of "defer func() { ... }()" we need to protect against the
1500
+ // possibility of directClosureCall rewriting things so that the
1501
+ // call does have arguments.
1502
+ if len (callArgs ) == 0 {
1503
+ if c , ok := call .(* ir.CallExpr ); ok && callX != nil && callX .Op () == ir .OCLOSURE {
1504
+ cloFunc := callX .(* ir.ClosureExpr ).Func
1505
+ cloFunc .SetClosureCalled (false )
1506
+ c .PreserveClosure = true
1507
+ }
1508
+ return
1509
+ }
1510
+
1511
+ if c , ok := call .(* ir.CallExpr ); ok {
1512
+ // To simplify things, turn f(a, b, []T{c, d, e}...) back
1513
+ // into f(a, b, c, d, e) -- when the final call is run through the
1514
+ // type checker below, it will rebuild the proper slice literal.
1515
+ undoVariadic (c )
1516
+ callX = c .X
1517
+ callArgs = c .Args
1518
+ }
1519
+
1520
+ // This is set to true if the closure we're generating escapes
1521
+ // (needs heap allocation).
1522
+ cloEscapes := func () bool {
1523
+ if n .Op () == ir .OGO {
1524
+ // For "go", assume that all closures escape (with an
1525
+ // exception for the runtime, which doesn't permit
1526
+ // heap-allocated closures).
1527
+ return base .Ctxt .Pkgpath != "runtime"
1528
+ }
1529
+ // For defer, just use whatever result escape analysis
1530
+ // has determined for the defer.
1531
+ return n .Esc () != ir .EscNever
1532
+ }()
1533
+
1534
+ // A helper for making a copy of an argument.
1535
+ mkArgCopy := func (arg ir.Node ) * ir.Name {
1536
+ argCopy := o .copyExpr (arg )
1537
+ // The value of 128 below is meant to be consistent with code
1538
+ // in escape analysis that picks byval/byaddr based on size.
1539
+ argCopy .SetByval (argCopy .Type ().Size () <= 128 || cloEscapes )
1540
+ return argCopy
1541
+ }
1542
+
1543
+ unsafeArgs := make ([]* ir.Name , len (callArgs ))
1544
+ origArgs := callArgs
1545
+
1546
+ // Copy the arguments to the function into temps.
1547
+ pos := n .Pos ()
1548
+ outerfn := ir .CurFunc
1549
+ var newNames []* ir.Name
1550
+ for i := range callArgs {
1551
+ arg := callArgs [i ]
1552
+ var argname * ir.Name
1553
+ if arg .Op () == ir .OCONVNOP && arg .Type ().IsUintptr () && arg .(* ir.ConvExpr ).X .Type ().IsUnsafePtr () {
1554
+ // No need for copy here; orderState.call() above has already inserted one.
1555
+ arg = arg .(* ir.ConvExpr ).X
1556
+ argname = arg .(* ir.Name )
1557
+ unsafeArgs [i ] = argname
1558
+ } else {
1559
+ argname = mkArgCopy (arg )
1560
+ }
1561
+ newNames = append (newNames , argname )
1562
+ }
1563
+
1564
+ // Deal with cases where the function expression (what we're
1565
+ // calling) is not a simple function symbol.
1566
+ var fnExpr * ir.Name
1567
+ var methSelectorExpr * ir.SelectorExpr
1568
+ if callX != nil {
1569
+ switch {
1570
+ case callX .Op () == ir .ODOTMETH || callX .Op () == ir .ODOTINTER :
1571
+ // Handle defer of a method call, e.g. "defer v.MyMethod(x, y)"
1572
+ n := callX .(* ir.SelectorExpr )
1573
+ n .X = mkArgCopy (n .X )
1574
+ methSelectorExpr = n
1575
+ case ! (callX .Op () == ir .ONAME && callX .(* ir.Name ).Class == ir .PFUNC ):
1576
+ // Deal with "defer returnsafunc()(x, y)" (for
1577
+ // example) by copying the callee expression.
1578
+ fnExpr = mkArgCopy (callX )
1579
+ if callX .Op () == ir .OCLOSURE {
1580
+ // For "defer func(...)", in addition to copying the
1581
+ // closure into a temp, mark it as no longer directly
1582
+ // called.
1583
+ callX .(* ir.ClosureExpr ).Func .SetClosureCalled (false )
1584
+ }
1585
+ }
1586
+ }
1587
+
1588
+ // Create a new no-argument function that we'll hand off to defer.
1589
+ var noFuncArgs []* ir.Field
1590
+ noargst := ir .NewFuncType (base .Pos , nil , noFuncArgs , nil )
1591
+ wrapGoDefer_prgen ++
1592
+ wrapname := fmt .Sprintf ("%v·dwrap·%d" , outerfn , wrapGoDefer_prgen )
1593
+ sym := types .LocalPkg .Lookup (wrapname )
1594
+ fn := typecheck .DeclFunc (sym , noargst )
1595
+ fn .SetIsHiddenClosure (true )
1596
+ fn .SetWrapper (true )
1597
+
1598
+ // helper for capturing reference to a var declared in an outer scope.
1599
+ capName := func (pos src.XPos , fn * ir.Func , n * ir.Name ) * ir.Name {
1600
+ t := n .Type ()
1601
+ cv := ir .CaptureName (pos , fn , n )
1602
+ cv .SetType (t )
1603
+ return typecheck .Expr (cv ).(* ir.Name )
1604
+ }
1605
+
1606
+ // Call args (x1, y1) need to be captured as part of the newly
1607
+ // created closure.
1608
+ newCallArgs := []ir.Node {}
1609
+ for i := range newNames {
1610
+ var arg ir.Node
1611
+ arg = capName (callArgs [i ].Pos (), fn , newNames [i ])
1612
+ if unsafeArgs [i ] != nil {
1613
+ arg = ir .NewConvExpr (arg .Pos (), origArgs [i ].Op (), origArgs [i ].Type (), arg )
1614
+ }
1615
+ newCallArgs = append (newCallArgs , arg )
1616
+ }
1617
+ // Also capture the function or method expression (if needed) into
1618
+ // the closure.
1619
+ if fnExpr != nil {
1620
+ callX = capName (callX .Pos (), fn , fnExpr )
1621
+ }
1622
+ if methSelectorExpr != nil {
1623
+ methSelectorExpr .X = capName (callX .Pos (), fn , methSelectorExpr .X .(* ir.Name ))
1624
+ }
1625
+ ir .FinishCaptureNames (pos , outerfn , fn )
1626
+
1627
+ // This flags a builtin as opposed to a regular call.
1628
+ irregular := (call .Op () != ir .OCALLFUNC &&
1629
+ call .Op () != ir .OCALLMETH &&
1630
+ call .Op () != ir .OCALLINTER )
1631
+
1632
+ // Construct new function body: f(x1, y1)
1633
+ op := ir .OCALL
1634
+ if irregular {
1635
+ op = call .Op ()
1636
+ }
1637
+ newcall := mkNewCall (call .Pos (), op , callX , newCallArgs )
1638
+
1639
+ // Type-check the result.
1640
+ if ! irregular {
1641
+ typecheck .Call (newcall .(* ir.CallExpr ))
1642
+ } else {
1643
+ typecheck .Stmt (newcall )
1644
+ }
1645
+
1646
+ // Finalize body, register function on the main decls list.
1647
+ fn .Body = []ir.Node {newcall }
1648
+ typecheck .FinishFuncBody ()
1649
+ typecheck .Func (fn )
1650
+ typecheck .Target .Decls = append (typecheck .Target .Decls , fn )
1651
+
1652
+ // Create closure expr
1653
+ clo := ir .NewClosureExpr (pos , fn )
1654
+ fn .OClosure = clo
1655
+ clo .SetType (fn .Type ())
1656
+
1657
+ // Set escape properties for closure.
1658
+ if n .Op () == ir .OGO {
1659
+ // For "go", assume that the closure is going to escape
1660
+ // (with an exception for the runtime, which doesn't
1661
+ // permit heap-allocated closures).
1662
+ if base .Ctxt .Pkgpath != "runtime" {
1663
+ clo .SetEsc (ir .EscHeap )
1664
+ }
1665
+ } else {
1666
+ // For defer, just use whatever result escape analysis
1667
+ // has determined for the defer.
1668
+ if n .Esc () == ir .EscNever {
1669
+ clo .SetTransient (true )
1670
+ clo .SetEsc (ir .EscNone )
1671
+ }
1672
+ }
1673
+
1674
+ // Create new top level call to closure over argless function.
1675
+ topcall := ir .NewCallExpr (pos , ir .OCALL , clo , []ir.Node {})
1676
+ typecheck .Call (topcall )
1677
+
1678
+ // Tag the call to insure that directClosureCall doesn't undo our work.
1679
+ topcall .PreserveClosure = true
1680
+
1681
+ fn .SetClosureCalled (false )
1682
+
1683
+ // Finally, point the defer statement at the newly generated call.
1684
+ n .Call = topcall
1685
+ }
0 commit comments