@@ -272,6 +272,13 @@ func (p *lowerInterfacesPass) run() error {
272272 p .defineInterfaceMethodFunc (fn , itf , signature )
273273 }
274274
275+ // Define all interface type assert functions.
276+ for _ , fn := range interfaceAssertFunctions {
277+ methodsAttr := fn .GetStringAttributeAtIndex (- 1 , "tinygo-methods" )
278+ itf := p .interfaces [methodsAttr .GetStringValue ()]
279+ p .defineInterfaceAssertFunc (fn , itf )
280+ }
281+
275282 // Replace each type assert with an actual type comparison or (if the type
276283 // assert is impossible) the constant false.
277284 llvmFalse := llvm .ConstInt (p .ctx .Int1Type (), 0 , false )
@@ -318,6 +325,17 @@ func (p *lowerInterfacesPass) run() error {
318325 }
319326 sort .Strings (typeNames )
320327
328+ // Check whether runtime.typeImplementsMethodSet still has uses. Now that
329+ // interface type assertions have been lowered to type-ID comparison
330+ // chains, the only remaining callers would be from reflect
331+ // (AssignableTo/Implements). If none remain, we can strip the inline
332+ // method-set data from type descriptors to save binary size.
333+ stripMethodSets := false
334+ typeImplementsFn := p .mod .NamedFunction ("runtime.typeImplementsMethodSet" )
335+ if ! typeImplementsFn .IsNil () && ! hasUses (typeImplementsFn ) {
336+ stripMethodSets = true
337+ }
338+
321339 // Remove all method sets, which are now unnecessary and inhibit later
322340 // optimizations if they are left in place.
323341 zero := llvm .ConstInt (p .ctx .Int32Type (), 0 , false )
@@ -327,7 +345,11 @@ func (p *lowerInterfacesPass) run() error {
327345 initializer := t .typecode .Initializer ()
328346 var newInitializerFields []llvm.Value
329347 for i := 1 ; i < initializer .Type ().StructElementTypesCount (); i ++ {
330- newInitializerFields = append (newInitializerFields , p .builder .CreateExtractValue (initializer , i , "" ))
348+ field := p .builder .CreateExtractValue (initializer , i , "" )
349+ if stripMethodSets {
350+ field = p .replaceMethodSetWithEmpty (name , i - 1 , field )
351+ }
352+ newInitializerFields = append (newInitializerFields , field )
331353 }
332354 newInitializer := p .ctx .ConstStruct (newInitializerFields , false )
333355 typecodeName := t .typecode .Name ()
@@ -525,3 +547,83 @@ func (p *lowerInterfacesPass) getDIFile(file string) llvm.Metadata {
525547 }
526548 return difile
527549}
550+
551+ // defineInterfaceAssertFunc defines a $typeassert function for the given
552+ // interface. The function returns true if the concrete type (passed as a
553+ // type-ID pointer) implements the interface, using a chain of type-ID
554+ // comparisons. This avoids pulling in runtime.typeImplementsMethodSet for
555+ // programs that don't use reflect.
556+ func (p * lowerInterfacesPass ) defineInterfaceAssertFunc (fn llvm.Value , itf * interfaceInfo ) {
557+ actualType := fn .FirstParam ()
558+ actualType .SetName ("actualType" )
559+ fn .SetLinkage (llvm .InternalLinkage )
560+ fn .SetUnnamedAddr (true )
561+ AddStandardAttributes (fn , p .config )
562+
563+ entry := p .ctx .AddBasicBlock (fn , "entry" )
564+ p .builder .SetInsertPointAtEnd (entry )
565+
566+ if p .dibuilder != nil {
567+ difile := p .getDIFile ("<Go interface type assert>" )
568+ diFuncType := p .dibuilder .CreateSubroutineType (llvm.DISubroutineType {
569+ File : difile ,
570+ })
571+ difunc := p .dibuilder .CreateFunction (difile , llvm.DIFunction {
572+ Name : "(Go interface type assert)" ,
573+ File : difile ,
574+ Line : 0 ,
575+ Type : diFuncType ,
576+ LocalToUnit : true ,
577+ IsDefinition : true ,
578+ ScopeLine : 0 ,
579+ Flags : llvm .FlagPrototyped ,
580+ Optimized : true ,
581+ })
582+ fn .SetSubprogram (difunc )
583+ p .builder .SetCurrentDebugLocation (0 , 0 , difunc , llvm.Metadata {})
584+ }
585+
586+ // Build an OR chain: return (type == T1) || (type == T2) || ...
587+ llvmFalse := llvm .ConstInt (p .ctx .Int1Type (), 0 , false )
588+ result := llvmFalse
589+ for _ , typ := range itf .types {
590+ cmp := p .builder .CreateICmp (llvm .IntEQ , actualType , typ .typecodeGEP , typ .name + ".icmp" )
591+ result = p .builder .CreateOr (result , cmp , "" )
592+ }
593+ p .builder .CreateRet (result )
594+ }
595+
596+ // replaceMethodSetWithEmpty replaces a method-set field in a type descriptor
597+ // with an empty method set ({0, [0]ptr}). This is used when reflect is not
598+ // needed and method-set data can be stripped to save binary size.
599+ //
600+ // The field index is relative to the type descriptor after $methodset removal
601+ // (i.e., field 0 is the kind/meta byte). Type descriptor layouts:
602+ //
603+ // named: [kind, numMethods, ptrTo, underlying, pkgpath, methods, name]
604+ // pointer: [kind, numMethods, elem, methods]
605+ // struct: [kind, numMethods, ptrTo, pkgpath, size, numFields, fields, methods]
606+ // interface: [kind, ptrTo, methods]
607+ func (p * lowerInterfacesPass ) replaceMethodSetWithEmpty (typeName string , fieldIdx int , field llvm.Value ) llvm.Value {
608+ isMethodSetField := false
609+ switch {
610+ case strings .HasPrefix (typeName , "named:" ):
611+ isMethodSetField = (fieldIdx == 5 )
612+ case strings .HasPrefix (typeName , "pointer:" ):
613+ isMethodSetField = (fieldIdx == 3 )
614+ case strings .HasPrefix (typeName , "struct:" ):
615+ isMethodSetField = (fieldIdx == 7 )
616+ case strings .HasPrefix (typeName , "interface:" ):
617+ isMethodSetField = (fieldIdx == 2 )
618+ }
619+ if ! isMethodSetField {
620+ return field
621+ }
622+
623+ // Replace with empty method set: {length=0, methods=[0 x ptr]}
624+ emptyMethodSet := p .ctx .ConstStruct ([]llvm.Value {
625+ llvm .ConstInt (p .uintptrType , 0 , false ),
626+ llvm .ConstArray (p .ptrType , nil ),
627+ }, false )
628+ return emptyMethodSet
629+ }
0 commit comments