@@ -7,10 +7,13 @@ package ssa
7
7
import (
8
8
"cmd/compile/internal/abi"
9
9
"cmd/compile/internal/ir"
10
+ "cmd/compile/internal/types"
10
11
"cmd/internal/dwarf"
11
12
"cmd/internal/obj"
13
+ "cmd/internal/src"
12
14
"encoding/hex"
13
15
"fmt"
16
+ "internal/buildcfg"
14
17
"math/bits"
15
18
"sort"
16
19
"strings"
@@ -335,6 +338,216 @@ func (s *debugState) stateString(state stateAtPC) string {
335
338
return strings .Join (strs , "" )
336
339
}
337
340
341
+ // slotCanonicalizer is a table used to lookup and canonicalize
342
+ // LocalSlot's in a type insensitive way (e.g. taking into account the
343
+ // base name, offset, and width of the slot, but ignoring the slot
344
+ // type).
345
+ type slotCanonicalizer struct {
346
+ slmap map [slotKey ]SlKeyIdx
347
+ slkeys []LocalSlot
348
+ }
349
+
350
+ func newSlotCanonicalizer () * slotCanonicalizer {
351
+ return & slotCanonicalizer {
352
+ slmap : make (map [slotKey ]SlKeyIdx ),
353
+ slkeys : []LocalSlot {LocalSlot {N : nil }},
354
+ }
355
+ }
356
+
357
+ type SlKeyIdx uint32
358
+
359
+ const noSlot = SlKeyIdx (0 )
360
+
361
+ // slotKey is a type-insensitive encapsulation of a LocalSlot; it
362
+ // is used to key a map within slotCanonicalizer.
363
+ type slotKey struct {
364
+ name * ir.Name
365
+ offset int64
366
+ width int64
367
+ splitOf SlKeyIdx // idx in slkeys slice in slotCanonicalizer
368
+ splitOffset int64
369
+ }
370
+
371
+ // lookup looks up a LocalSlot in the slot canonicalizer "sc", returning
372
+ // a canonical index for the slot, and adding it to the table if need
373
+ // be. Return value is the canonical slot index, and a boolean indicating
374
+ // whether the slot was found in the table already (TRUE => found).
375
+ func (sc * slotCanonicalizer ) lookup (ls LocalSlot ) (SlKeyIdx , bool ) {
376
+ split := noSlot
377
+ if ls .SplitOf != nil {
378
+ split , _ = sc .lookup (* ls .SplitOf )
379
+ }
380
+ k := slotKey {
381
+ name : ls .N , offset : ls .Off , width : ls .Type .Width ,
382
+ splitOf : split , splitOffset : ls .SplitOffset ,
383
+ }
384
+ if idx , ok := sc .slmap [k ]; ok {
385
+ return idx , true
386
+ }
387
+ rv := SlKeyIdx (len (sc .slkeys ))
388
+ sc .slkeys = append (sc .slkeys , ls )
389
+ sc .slmap [k ] = rv
390
+ return rv , false
391
+ }
392
+
393
+ func (sc * slotCanonicalizer ) canonSlot (idx SlKeyIdx ) LocalSlot {
394
+ return sc .slkeys [idx ]
395
+ }
396
+
397
+ // PopulateABIInRegArgOps examines the entry block of the function
398
+ // and looks for incoming parameters that have missing or partial
399
+ // OpArg{Int,Float}Reg values, inserting additional values in
400
+ // cases where they are missing. Example:
401
+ //
402
+ // func foo(s string, used int, notused int) int {
403
+ // return len(s) + used
404
+ // }
405
+ //
406
+ // In the function above, the incoming parameter "used" is fully live,
407
+ // "notused" is not live, and "s" is partially live (only the length
408
+ // field of the string is used). At the point where debug value
409
+ // analysis runs, we might expect to see an entry block with:
410
+ //
411
+ // b1:
412
+ // v4 = ArgIntReg <uintptr> {s+8} [0] : BX
413
+ // v5 = ArgIntReg <int> {used} [0] : CX
414
+ //
415
+ // While this is an accurate picture of the live incoming params,
416
+ // we also want to have debug locations for non-live params (or
417
+ // their non-live pieces), e.g. something like
418
+ //
419
+ // b1:
420
+ // v9 = ArgIntReg <*uint8> {s+0} [0] : AX
421
+ // v4 = ArgIntReg <uintptr> {s+8} [0] : BX
422
+ // v5 = ArgIntReg <int> {used} [0] : CX
423
+ // v10 = ArgIntReg <int> {unused} [0] : DI
424
+ //
425
+ // This function examines the live OpArg{Int,Float}Reg values and
426
+ // synthesizes new (dead) values for the non-live params or the
427
+ // non-live pieces of partially live params.
428
+ //
429
+ func PopulateABIInRegArgOps (f * Func ) {
430
+ pri := f .ABISelf .ABIAnalyzeFuncType (f .Type .FuncType ())
431
+
432
+ // When manufacturing new slots that correspond to splits of
433
+ // composite parameters, we want to avoid creating a new sub-slot
434
+ // that differs from some existing sub-slot only by type, since
435
+ // the debug location analysis will treat that slot as a separate
436
+ // entity. To achieve this, create a lookup table of existing
437
+ // slots that is type-insenstitive.
438
+ sc := newSlotCanonicalizer ()
439
+ for _ , sl := range f .Names {
440
+ sc .lookup (* sl )
441
+ }
442
+
443
+ // Add slot -> value entry to f.NamedValues if not already present.
444
+ addToNV := func (v * Value , sl LocalSlot ) {
445
+ values , ok := f .NamedValues [sl ]
446
+ if ! ok {
447
+ // Haven't seen this slot yet.
448
+ sla := f .localSlotAddr (sl )
449
+ f .Names = append (f .Names , sla )
450
+ } else {
451
+ for _ , ev := range values {
452
+ if v == ev {
453
+ return
454
+ }
455
+ }
456
+ }
457
+ values = append (values , v )
458
+ f .NamedValues [sl ] = values
459
+ }
460
+
461
+ newValues := []* Value {}
462
+
463
+ abiRegIndexToRegister := func (reg abi.RegIndex ) int8 {
464
+ i := f .ABISelf .FloatIndexFor (reg )
465
+ if i >= 0 { // float PR
466
+ return f .Config .floatParamRegs [i ]
467
+ } else {
468
+ return f .Config .intParamRegs [reg ]
469
+ }
470
+ }
471
+
472
+ // Helper to construct a new OpArg{Float,Int}Reg op value.
473
+ var pos src.XPos
474
+ if len (f .Entry .Values ) != 0 {
475
+ pos = f .Entry .Values [0 ].Pos
476
+ }
477
+ synthesizeOpIntFloatArg := func (n * ir.Name , t * types.Type , reg abi.RegIndex , sl LocalSlot ) * Value {
478
+ aux := & AuxNameOffset {n , sl .Off }
479
+ op , auxInt := ArgOpAndRegisterFor (reg , f .ABISelf )
480
+ v := f .newValueNoBlock (op , t , pos )
481
+ v .AuxInt = auxInt
482
+ v .Aux = aux
483
+ v .Args = nil
484
+ v .Block = f .Entry
485
+ newValues = append (newValues , v )
486
+ addToNV (v , sl )
487
+ f .setHome (v , & f .Config .registers [abiRegIndexToRegister (reg )])
488
+ return v
489
+ }
490
+
491
+ // Make a pass through the entry block looking for
492
+ // OpArg{Int,Float}Reg ops. Record the slots they use in a table
493
+ // ("sc"). We use a type-insensitive lookup for the slot table,
494
+ // since the type we get from the ABI analyzer won't always match
495
+ // what the compiler uses when creating OpArg{Int,Float}Reg ops.
496
+ for _ , v := range f .Entry .Values {
497
+ if v .Op == OpArgIntReg || v .Op == OpArgFloatReg {
498
+ aux := v .Aux .(* AuxNameOffset )
499
+ sl := LocalSlot {N : aux .Name , Type : v .Type , Off : aux .Offset }
500
+ // install slot in lookup table
501
+ idx , _ := sc .lookup (sl )
502
+ // add to f.NamedValues if not already present
503
+ addToNV (v , sc .canonSlot (idx ))
504
+ } else if v .Op .IsCall () {
505
+ // if we hit a call, we've gone too far.
506
+ break
507
+ }
508
+ }
509
+
510
+ // Now make a pass through the ABI in-params, looking for params
511
+ // or pieces of params that we didn't encounter in the loop above.
512
+ for _ , inp := range pri .InParams () {
513
+ if ! isNamedRegParam (inp ) {
514
+ continue
515
+ }
516
+ n := inp .Name .(* ir.Name )
517
+
518
+ // Param is spread across one or more registers. Walk through
519
+ // each piece to see whether we've seen an arg reg op for it.
520
+ types , offsets := inp .RegisterTypesAndOffsets ()
521
+ for k , t := range types {
522
+ // Note: this recipe for creating a LocalSlot is designed
523
+ // to be compatible with the one used in expand_calls.go
524
+ // as opposed to decompose.go. The expand calls code just
525
+ // takes the base name and creates an offset into it,
526
+ // without using the SplitOf/SplitOffset fields. The code
527
+ // in decompose.go does the opposite -- it creates a
528
+ // LocalSlot object with "Off" set to zero, but with
529
+ // SplitOf pointing to a parent slot, and SplitOffset
530
+ // holding the offset into the parent object.
531
+ pieceSlot := LocalSlot {N : n , Type : t , Off : offsets [k ]}
532
+
533
+ // Look up this piece to see if we've seen a reg op
534
+ // for it. If not, create one.
535
+ _ , found := sc .lookup (pieceSlot )
536
+ if ! found {
537
+ // This slot doesn't appear in the map, meaning it
538
+ // corresponds to an in-param that is not live, or
539
+ // a portion of an in-param that is not live/used.
540
+ // Add a new dummy OpArg{Int,Float}Reg for it.
541
+ synthesizeOpIntFloatArg (n , t , inp .Registers [k ],
542
+ pieceSlot )
543
+ }
544
+ }
545
+ }
546
+
547
+ // Insert the new values into the head of the block.
548
+ f .Entry .Values = append (newValues , f .Entry .Values ... )
549
+ }
550
+
338
551
// BuildFuncDebug returns debug information for f.
339
552
// f must be fully processed, so that each Value is where it will be when
340
553
// machine code is emitted.
@@ -349,6 +562,10 @@ func BuildFuncDebug(ctxt *obj.Link, f *Func, loggingEnabled bool, stackOffset fu
349
562
state .stackOffset = stackOffset
350
563
state .ctxt = ctxt
351
564
565
+ if buildcfg .Experiment .RegabiArgs {
566
+ PopulateABIInRegArgOps (f )
567
+ }
568
+
352
569
if state .loggingEnabled {
353
570
state .logf ("Generating location lists for function %q\n " , f .Name )
354
571
}
0 commit comments