@@ -90,18 +90,19 @@ type interfaceInfo struct {
9090// pass has been implemented as an object type because of its complexity, but
9191// should be seen as a regular function call (see LowerInterfaces).
9292type lowerInterfacesPass struct {
93- mod llvm.Module
94- config * compileopts.Config
95- builder llvm.Builder
96- dibuilder * llvm.DIBuilder
97- difiles map [string ]llvm.Metadata
98- ctx llvm.Context
99- uintptrType llvm.Type
100- targetData llvm.TargetData
101- ptrType llvm.Type
102- types map [string ]* typeInfo
103- signatures map [string ]* signatureInfo
104- interfaces map [string ]* interfaceInfo
93+ mod llvm.Module
94+ config * compileopts.Config
95+ builder llvm.Builder
96+ dibuilder * llvm.DIBuilder
97+ difiles map [string ]llvm.Metadata
98+ ctx llvm.Context
99+ uintptrType llvm.Type
100+ targetData llvm.TargetData
101+ ptrType llvm.Type
102+ types map [string ]* typeInfo
103+ signatures map [string ]* signatureInfo
104+ interfaces map [string ]* interfaceInfo
105+ keepMethodNames bool // true when reflection methods can inspect method names
105106}
106107
107108// LowerInterfaces lowers all intermediate interface calls and globals that are
@@ -342,9 +343,50 @@ func (p *lowerInterfacesPass) run() error {
342343 stripMethodSets = true
343344 }
344345
345- // TODO: Restore method-set pruning once reflective method calls can be
346- // distinguished from calls made inside the reflect implementation.
347- // For now, preserve complete method sets whenever reflection needs them.
346+ keepAllMethods := p .usesReflectMethods ()
347+ if keepAllMethods {
348+ stripMethodSets = false // Method sets are needed.
349+ }
350+
351+ p .keepMethodNames = keepAllMethods
352+
353+ // Collect all method signatures that appear in any interface type
354+ // descriptor. When reflect is imported and method sets are kept,
355+ // concrete type method sets are pruned: individual methods not in any
356+ // interface are removed, and types that can't fully satisfy at least
357+ // one interface have their method sets emptied entirely.
358+ //
359+ // When keepAllMethods is true, pruning is disabled and all methods are
360+ // kept.
361+ //
362+ // When method sets are stripped entirely (reflect not imported),
363+ // methodFilter is nil and filterMethodSet replaces with empty.
364+ var methodFilter map [string ]struct {}
365+ var ifaceMethodSets []map [string ]struct {}
366+ if ! stripMethodSets && ! keepAllMethods {
367+ methodFilter = make (map [string ]struct {})
368+ for _ , name := range typeNames {
369+ if ! strings .HasPrefix (name , "interface:" ) {
370+ continue
371+ }
372+ t := p .types [name ]
373+ initializer := t .typecode .Initializer ()
374+ ifaceSet := make (map [string ]struct {})
375+ for i := 0 ; i < initializer .Type ().StructElementTypesCount (); i ++ {
376+ field := p .builder .CreateExtractValue (initializer , i , "" )
377+ for _ , sig := range p .extractMethodSigs (field ) {
378+ methodFilter [sig ] = struct {}{}
379+ ifaceSet [sig ] = struct {}{}
380+ }
381+ }
382+ if len (ifaceSet ) > 0 {
383+ ifaceMethodSets = append (ifaceMethodSets , ifaceSet )
384+ }
385+ }
386+ }
387+
388+ // Remove all method sets, which are now unnecessary and inhibit later
389+ // optimizations if they are left in place.
348390 zero := llvm .ConstInt (p .ctx .Int32Type (), 0 , false )
349391 for _ , name := range typeNames {
350392 t := p .types [name ]
@@ -370,8 +412,8 @@ func (p *lowerInterfacesPass) run() error {
370412 numMethodFieldIdx := - 1 // index into newInitializerFields
371413 for i := 1 ; i < numFields ; i ++ {
372414 field := p .builder .CreateExtractValue (initializer , i , "" )
373- if stripMethodSets {
374- field = p .filterMethodSet (field , nil , nil )
415+ if ! keepAllMethods {
416+ field = p .filterMethodSet (field , methodFilter , ifaceMethodSets )
375417 }
376418 // Track where the numMethod field lands in the new slice.
377419 if i == 2 && numMethodsIsI16 {
@@ -420,12 +462,45 @@ func (p *lowerInterfacesPass) run() error {
420462 t .typecode .EraseFromParentAsGlobal ()
421463 newGlobal .SetName (typecodeName )
422464 t .typecode = newGlobal
465+ } else if ! keepAllMethods {
466+ // Types without an external method set (e.g., interface types)
467+ // may still have inline method sets with name pointers that
468+ // should be nulled out when reflection cannot inspect methods.
469+ initializer := t .typecode .Initializer ()
470+ if initializer .Type ().TypeKind () != llvm .StructTypeKind {
471+ continue
472+ }
473+ numFields := initializer .Type ().StructElementTypesCount ()
474+ changed := false
475+ var fields []llvm.Value
476+ for i := 0 ; i < numFields ; i ++ {
477+ field := p .builder .CreateExtractValue (initializer , i , "" )
478+ filtered := p .filterMethodSet (field , methodFilter , ifaceMethodSets )
479+ if filtered .C != field .C {
480+ changed = true
481+ }
482+ fields = append (fields , filtered )
483+ }
484+ if changed {
485+ newInitializer := p .ctx .ConstStruct (fields , false )
486+ t .typecode .SetInitializer (newInitializer )
487+ }
423488 }
424489 }
425490
426491 return nil
427492}
428493
494+ func (p * lowerInterfacesPass ) usesReflectMethods () bool {
495+ for fn := p .mod .FirstFunction (); ! fn .IsNil (); fn = llvm .NextFunction (fn ) {
496+ attr := fn .GetStringAttributeAtIndex (- 1 , "tinygo-reflect-method" )
497+ if ! attr .IsNil () && (fn .Linkage () != llvm .InternalLinkage || hasUses (fn )) {
498+ return true
499+ }
500+ }
501+ return false
502+ }
503+
429504// addTypeMethods reads the method set of the given type info struct. It
430505// retrieves the signatures and the references to the method functions
431506// themselves for later type<->interface matching.
@@ -757,14 +832,24 @@ func (p *lowerInterfacesPass) filterMethodSet(field llvm.Value, keepSigs map[str
757832 }
758833
759834 // Prune: keep only method entries whose signature appears in keepSigs.
835+ // When reflection cannot inspect methods, null out name pointers so LLVM
836+ // can eliminate the name string globals.
760837 var kept []llvm.Value
761838 for _ , e := range entries {
762839 if _ , ok := keepSigs [e .name ]; ok {
763- kept = append (kept , e .pair )
840+ if p .keepMethodNames {
841+ kept = append (kept , e .pair )
842+ } else {
843+ sig := p .builder .CreateExtractValue (e .pair , 0 , "" )
844+ kept = append (kept , p .ctx .ConstStruct ([]llvm.Value {
845+ sig ,
846+ llvm .ConstNull (p .ptrType ),
847+ }, false ))
848+ }
764849 }
765850 }
766851
767- if len (kept ) == numMethods {
852+ if len (kept ) == numMethods && p . keepMethodNames {
768853 return field
769854 }
770855
0 commit comments