Just An Application

October 29, 2014

So Swift Then: Mangled Function Names And Function Types — Function Types Go Recursive

Now we have a model of how function types are encoded lets see if we can break it.

What happens if we use it on a function which returns a function.

Starting with the function

    func bass() -> ()
    {
    }

Now we have a function to return like so

    func cod() -> () -> ()
    {
        return bass
    }

Compiling cod gives us the symbol

    __TF4xper3codFT_FT_T_

giving us

    FT_FT_T_

as the function type.

Now for the model.

Starting by creating some useful objects.

    let encoder     = Encoder()
    let builder     = FunctionTypeBuilder()
    let ftVoidVoid  = builder.build()

then

    let ft6000 = builder.returnType(ftVoidVoid).build()

    println(ft6000.encode(encoder))

prints

    FT_FT_T_

We’re off to flying start.

This is a function which returns a function which returns a function

    func dab() -> () -> () -> ()
    {
        return cod
    }

and compiling it gives us the symbol

    __TF4xper3dabFT_FT_FT_T_

giving us

    FT_FT_FT_T_

as the function type.

This

    let ft6001 = builder.returnType(ft6000).build()
    
    println(ft6001.encode(encoder))

prints

    FT_FT_FT_T_

so that’s alright.

A function that takes an Int and a function as arguments

    func eel(x: Int, f: (Int) -> (Int)) -> Int
    {
        return f(x)
    }

Compiling it gives us the symbol

    __TF4xper3eelFTSiFSiSi_Si

giving us

    FTSiFSiSi_Si

as the function type.

    let ftIntInt = builder.intParam().intReturnType().build()
    let ft6002   = builder.intParam().param(ftIntInt).intReturnType().build()
    
    println(ft6002.encode(encoder))

prints

    FTSiFSiSi_Si

A function that takes a function that takes two Ints and returns a pair or Ints

    func flounder(f: (Int, Int) -> (Int, Int))
    {
    }

Compiling it gives us the symbol

    __TF4xper8flounderFFTSiSi_TSiSi_T_

giving us

    FFTSiSi_TSiSi_T_

as the function type.

    let intType  = BuiltinType.IntType
    let fnType   = builder.tupleTypeParam(intType, intType).tupleReturnType(intType, intType).build()
    let ft6003   = builder.param(fnType).build()
    
    println(ft6003.encode(encoder))

prints

    FFTSiSi_TSiSi_T_

which of course it would !

A function that takes a function which returns a function as its argument and returns the result of calling that function

    func goby(f: () -> () -> ()) -> () -> ()
    {
        return f()
    }

Pick the parentheses out of that.

The compiler having picked the parentheses out comes up with the symbol

    __TF4xper4gobyFFT_FT_T_FT_T_

which gives us the function type

    FFT_FT_T_FT_T_

and we’ll just have to trust that it knows what its doing.

    let ft6004   = builder.param(ft6000).returnType(ftVoidVoid).build()
    
    println(ft6004.encode(encoder))

prints

    FFT_FT_T_FT_T_

so if the compiler knows what its doing so does the model, and possibly vice versa.

And that’s enough of that without a dedicated test harness that can count ‘F’s and ‘T’s and stuff.

It certainly looks as though the model’s behaviour matches that of the compiler when it comes to function types as arguments and return types.


Copyright (c) 2014 By Simon Lewis. All Rights Reserved.

Unauthorized use and/or duplication of this material without express and written permission from this blog’s author and owner Simon Lewis is strictly prohibited.

Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and specific direction to the original content.

Advertisements

October 28, 2014

So Swift Then: More Fun With Mangled Names Continued

Function Type Encoding: A Model In Swift

Type Encoding

Encodeable

We start by defining the Encodeable protocol

    protocol Encodeable
    {
        func encode(encoder:Encoder) -> String
    }

Type

A Type must be encodeable. That’s it at the moment.

    protocol Type: Encodeable
    {
    }

Builtin Types

Built in types are represented by the enum BuiltinType

We will assume that the Encoder is responsible for knowing how built-in types are actually encoded.

    enum BuiltinType: Type
    {
        case ArrayType
        case BoolType
        case DoublgType
        case IntType
        case OptionalType
        case StringType
        case UintType
    
        //
    
        func encode(encoder:Encoder) -> String
        {
            return encoder.encode(self)
        }
    }

Generic Types

Generic types are represented by sub-classes of GenericType.

A generic type encodes itself.

    class GenericType: Type
    {
        init(baseType:Type, parameterTypes:Type...)
        {
            self.baseType       = baseType
            self.parameterTypes = parameterTypes
        }
    
        //
    
        func encode(encoder:Encoder) -> String
        {
            var encoding = "G"
    
            encoding += baseType.encode(encoder)
            for t in parameterTypes
            {
                encoding += t.encode(encoder)
            }
            encoding += "_"
            return encoding
        }
    
        //
    
        private let baseType        : Type
        private let parameterTypes  : [Type]
    }

Array Types

Array types are represented by instances of the class ArrayType

    final class ArrayType: GenericType
    {
        init(elementType:Type)
        {
            super.init(baseType:BuiltinType.ArrayType, parameterTypes:elementType)
        }
    }

Optional Types

Optional types are represented by instances of the class OptionalType

    final class OptionalType: GenericType
    {
        init(type:Type)
        {
            super.init(baseType:BuiltinType.OptionalType, parameterTypes:type)
        }
    }

Tuple Types

The Empty Tuple Type

The type of the empty tuple

    ()

is represented by an instance of the class EmptyTupleType

    final class EmptyTupleType: TupleType
    {
        func encode(encoder:Encoder) -> String
        {
            return "T_"
        }
    }

    typealias VoidType = EmptyTupleType

Single Element Tuple Types

The type of a tuple with a single unnamed element is represented by an instance of the class SingleElementTupleType

    final class SingleElementTupleType: TupleType
    {
        init(elementType:Type)
        {
            self.elementType = elementType
        }
        
        //

        func encode(encoder:Encoder) -> String
        {
            return elementType.encode(encoder)
        }

        //

        private let elementType: Type
    }

It encodes itself by returning the encoding of its element type.

Multi Element Tuple Types

The type of any tuple which has more than one element is represented by an instance of the class MultiElementTupleType

    final class MultiElementTupleType: TupleType
    {
        init(first:TupleElementType, second:TupleElementType, rest:[TupleElementType])
        {
            var elementTypes = [first, second]
    
            elementTypes += rest
            self.elementTypes = elementTypes
        }
    
        //
    
        func encode(encoder:Encoder) -> String
        {
            var encoding = "T"
    
            for et in elementTypes
            {
                encoding += e.encode(encoder)
            }
            encoding += "_"
            return encoding
        }
    
        //
    
        private let elementTypes: [TupleElementType]
    }

The type of a tuple element is represented by an instance of the enum TupleElementType

    enum TupleElementType: Encodeable
    {
        case NameAndType(String, Type)
        case TypeOnly(Type)
    
        func encode(encoder:Encoder) -> String
        {
            switch self
            {
                case let .NameAndType(name, type):
    
                    var encoding = ""
    
                    encoding += encoder.encode(name)
                    encoding += type.encode(encoder)
                    return encoding
    
                case let .TypeOnly(type):
    
                    return type.encode(encoder)
            }
        }
    }
    

Function Type Encoding

The encoding of a function type is the encoding of its parameters immediately followed by the encoding of its return type.

A function’s parameters are encoded as though they comprise a tuple type.

To do this for certain function parameters we need two additional ‘synthetic’ tuple types.

Synthetic Tuple Types

Single Named Element Tuple Types

The compiler refuses to acknowledge the existence of single named element tuple types.

We need to encode a parameter with an external name as though it were one so we define the class SingleNamedElementTupleType

    final class SingleNamedElementTupleType: TupleType
    {
        init(name:String, type:Type)
        {
            self.name = name
            self.type = type
        }
    
        //
    
        func encode(encoder:Encoder) -> String
        {
            var encoding = "T"
    
            encoding += encoder.encode(name)
            encoding += type.encode(encoder)
            encoding += "_"
            return encoding
        }
    
        //
    
        private let name:   String
        private let type:   Type
    }

Varadic Tuple Types

To represent a function’s parameters as a tuple type when that function has a varadic parameter we need a ‘varadic tuple’ type.

A ‘varadic tuple’ type is represented by an instance of the class

    final class VaradicTupleType: TupleType
    {
        init(var elementTypes:[TupleElementType])
        {
            assert(elementTypes.count != 0)
            
            var last : TupleElementType

            switch elementTypes.removeLast()
            {
                case let .NameAndType(name, type):

                    last = TupleElementType.NameAndType(
                                                name,
                                                ArrayType(
                                                    elementType:type)))

                case let .TypeOnly(type):

                    last = TupleElementType.TypeOnly(
                                                ArrayType(
                                                    elementType:type)))
            }
            elementTypes.append(last)
            self.elementTypes = elementTypes
        }

        //

        func encode(encoder:Encoder) -> String
        {
            var encoding = "t"

            for et in elementTypes
            {
                encoding += et.encode(encoder)
            }
            encoding += "_"
            return encoding
        }

        //

        private let elementTypes : [TupleElementType]
    }

Parameter Tuple Element Types

Vanilla

A vanilla parameter of type T is represented by an instance of

    TupleElementType.TypeOnly

For example

    i : Int

is represented by

    TupleElementType.TypeOnly(BuiltinType.IntType)

External Names

A parameter with an external name is represented by an instance of

    TupleElementType.NameAndType

For example

    e i : Int

is represented by

    TupleElementType.NameAndType("e", "BuiltinType.IntType)

inout

The type of an inout parameter is represented by an instance of the class ReferenceType.

    final class ReferenceType: Type
    {
        init(type:Type)
        {
            self.type = type
        }
    
        //
    
        func encode(encoder:Encoder) -> String
        {
            return "R" + type.encode(encoder)
        }
    
        //
    
        private let type: Type
    }

If it does not have an ‘external name’ the parameter is represented by an instance of

    TupleElementType.TypeOnly

or by an instance of

    TupleElementType.NameAndType

otherwise.

Varadic

If a function has a varadic parameter then its parameters are represented by an instance of the class VaradicTupleType.

The parameter itself is represented by an instance of ArrayType.

Function Types

A function type is represented by an instance of the class FunctionType.

   final class FunctionType: Type
    {
        init(parameters:TupleType, returnType:TupleType)
        {
            self.parameters = parameters
            self.returnType = returnType
        }
    
        //
    
        func encode(encoder:Encoder) -> String
        {
            var encoding = "F"
    
            encoding += parameters.encode(encoder)
            encoding += returnType.encode(encoder)
            return encoding
        }
    
        //
    
        private let parameters: TupleType
        private let returnType: TupleType
    }

Other Types

Of the other types we have seen class, enum and struct types can all be represented by sub-classes of NamedType.

    class NamedType: Type
    {
        init(prefix:String, name:Name)
        {
            self.prefix = prefix
            self.name   = name
        }
    
        func encode(encoder:Encoder) -> String
        {
            return encoder.encode(prefix:prefix, name:name)
        }
    
        private let prefix: String
        private let name:   Name
    }

The actual encoding of the names is done by the Encoder. This makes it possible for the Encoder to replace the name of the type with a substitution pattern
when appropriate.

The type names are represented by instances of the enum Name.

    enum Name
    {
        case Local([String])
        case External([String])
        case Swift([String])
    }

The representation of the protocol type is left as an exercise for the reader.

Some Examples

Starting with the obvious one

    let encoder = Encoder()
    let builder = FunctionTypeBuilder()
    
    let ft0001 = builder.build()
    
    println(ft0001.encode(encoder))

prints

    FT_T_

So far so good.

Some return types.

An integer

    let ft0002 = builder.returnType(BuiltinType.IntType).build()
    
    println(ft0002.encode(encoder))

prints

    FT_Si

An array of integers

    let ft0003 = builder.returnType(ArrayType(elementType:BuiltinType.IntType)).build()
    
    println(ft0003.encode(encoder))

prints

    FT_GSaSi_

An optional integer

    let ft0004 = builder.optionalReturnType(BuiltinType.IntType).build()

    println(ft0004.encode(encoder))

prints

    FT_GSqSi_

A single unnamed element tuple

    let ft0005 = builder.tupleReturnType(BuiltinType.IntType).build()
    
    println(ft0005.encode(encoder))

prints

    FT_Si

A multiple element tuple

    let ft0006 = builder.tupleReturnType(BuiltinType.IntType, BuiltinType.IntType).build()
    
    println(ft0006.encode(encoder))

prints

    FT_TSiSi_

A named element tuple

    let ft0007 = builder.namedTupleReturnType((name:"x", type:BuiltinType.IntType), rest:(name:"y", type:BuiltinType.IntType)).build()
    
    println(ft0007.encode(encoder))

prints

    FT_T1xSi1ySi_

Some parameters

An integer parameter

    let ft1000 = builder.param(BuiltinType.IntType).build()

    println(ft1000.encode(encoder))

prints

    FSiT_

An integer parameter with an external name

    let ft1001 = builder.param(externalName:"e", type:BuiltinType.IntType).build()

    println(ft1001.encode(encoder))

prints

    FT1eSi_T_

Two integer parameters

    let ft1002 = builder.param(BuiltinType.IntType).param(BuiltinType.IntType).build()

    println(ft1002.encode(encoder))

prints

    FTSiSi_T_

An inout integer parameter

    let ft1003 = builder.inoutParam(BuiltinType.IntType).build()

    println(ft1003.encode(encoder))

prints

    FRSiT_

An integer parameter and an inout integer parameter

    let ft1004 = builder.param(BuiltinType.IntType).inoutParam(BuiltinType.IntType).build()

    println(ft1004.encode(encoder))

prints

    FTSiRSi_T_

A single unnamed element tuple

    let ft1005 = builder.tupleTypeParam(BuiltinType.IntType).build()

    println(ft1005.encode(encoder))

prints

    FSiT_

A multiple unnamed element tuple

    let ft1006 = builder.tupleTypeParam(BuiltinType.IntType, BuiltinType.IntType).build()

    println(ft1006.encode(encoder))

prints

    FTSiSi_T_

Conclusions

The model is based on two simple assumptions

  1. a single unnamed element tuple type is encoded as the type of its element, and

  2. a function’s parameters are encoded as though they comprised a tuple type

The resulting behaviour seems to be accurate up to and including generating the same encodings for ostensibly ‘different’ sets of function parameters.


Copyright (c) 2014 By Simon Lewis. All Rights Reserved.

Unauthorized use and/or duplication of this material without express and written permission from this blog’s author and owner Simon Lewis is strictly prohibited.

Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and specific direction to the original content.

October 27, 2014

So Swift Then: More Fun With Mangled Names

What follows is an attempt to determine

  1. whether the function name mangling machine (FNMM) has something against single unnamed element tuple types
    only when they appear in vanilla parameters or whether it just objects to them on principle wherever they appear

  2. whether it has any idiosyncracies with respect to any of the other possible tuple types.

There are effectively four kinds of tuple.

  • the empty tuple

  • unnamed element tuples, i.e., tuples where all the elements are unnamed

  • named element tuples, i.e., tuples where all the elements are named

  • mixed element tuples, i.e., tuples which are a mixture of named and unnamed elements

Unnamed and named tuples can further be divided into single and multiple element cases.

Tuple types can appear in the declarations of return types and of parameter types and as components of other type declarations.

1.0 Return Types

1.1 Void

We already know that the FNMM encodes the empty tuple type as

    T_

whenever it is used as a return type.

1.2 Unnamed Element Tuples

1.2.1 Single Element

Compiling

    func cod() -> (Int)
    {
        return (0)
    }

gives us the symbol

     __TF4xper3codFT_Si

so the FNMM also flattens a single element tuple type when it is used as a return type.

This means that there is a collision between the mangled names of functions which return

    T

and those which return

    (T)

and have identical parameters.

In practice it turns out that the compiler considers them to be the same function when it can see both of them at the same time

    $ swiftc -module-name xper -emit-library functions03.swift
    functions03.swift:8:6: error: invalid redeclaration of 'cod()'
    func cod() -> (Int)
         ^
    functions03.swift:3:6: note: 'cod()' previously declared here
    func cod() -> Int
         ^

Then there are optionals.

Compiling

    func cod() -> (Int)?
    {
        return (0)
    }

gives us the symbol

     __TF4xper3codFT_GSqSi_

The FNMM has flattened the tuple type again.

Compiling

    func cod() -> (Int)!
    {
        return (0)
    }

gives us the symbol

     __TF4xper3codFT_GSQSi_

At least the FNMM is consistent, but is it recursive ?

Compiling

    func cod() -> (((Int)))
    {
        return (((0)))
    }

gives us the symbol

     __TF4xper3codFT_Si

so the FNMM is indeed recursive.

Compiling

    func cod() -> [(Int)]
    {
        return []
    }

gives us the symbol

     __TF4xper3codFT_GSaSi_

so the FNMM is not easily fooled either.

1.2.2 Multi Element

Compiling

    func cod() -> (Int, String, Int)
    {
        return (1, "", 5)
    }

gives us the symbol

     __TF4xper3codFT_TSiSSSi_

The FNMM does not treat a multiple unnamed element tuple type specially when it is used as a return type.

1.3 Named Tuples

1.4 Single Element

Compiling

    func cod() -> (i: Int)
    {
        return (i: 0)
    }

doesnt.

Instead this happens

    $ swiftc -module-name xper -emit-library  functions03.swift
    functions03.swift:40:16: error: cannot create a single-element tuple with an element label
    func cod() -> (i: Int)
                   ^~~

so the FNMM never gets the chance to encode the return type.

The compiler error message is a tad misleading.

You CAN create a single named element tuple, for example, this will compile

    func cod() -> (Int)
    {
        let t = (i: 0)

        return t
    }

you just cannot explictly specify its type, so this version will not compile

    func cod() -> (Int)
    {
        let t : (i: Int) = (i: 0)
    
        return t
    }

and the reason for that is probably because that isn’t its type.

Note the return type of the function and the fact that this compiles

    func cod() -> (Int)
    {
        let t : (Int) = (i: 0)
    
        return t
    }

1.4 Multi Element

Compiling

    func cod() -> (i: Int, j: Int)
    {
        return (i: 0, j: 0)
    }

gives us the symbol

     __TF4xper3codFT_T1iSi1jSi_

The FNMM does not treat a multiple named element tuple type specially when it is used as a return type.

Of course, compiling

    func cod() -> (i: (Int), j: (Int))
    {
        return (i: 0, j: 0)
    }

also gives us the symbol

     __TF4xper3codFT_T1iSi1jSi_

Luckily this is not a problem as the compiler, as in the case above, considers them to be one and the same function.

    $ swiftc  -module-name xper -emit-library somefuncs.swift
    somefuncs.swift:7:6: error: invalid redeclaration of 'cod()'
    func cod() -> (i: (Int), j: (Int))
         ^
    somefuncs.swift:1:6: note: 'cod()' previously declared here
    func cod() -> (i: Int, j: Int)
         ^

1.5 Mixed Element Tuples

Compiling

    func cod() -> (s String, String)
    {
        return (s: "", "")
    }

gives us the symbol

     __TF4xper3codFT_T1sSSSS_

The FNMM does not treat a mixed element tuple type specially when it is used as a return type.

2.0 Parameters

2.1 Vanilla

2.1.1 Void

Compiling

    func cod(t:())
    {
    }

gives us the symbol

     __TF4xper3codFT_T_

A function that takes a Void argument is the same as function that takes no arguments at all.

Digression

Of course, a function that takes more than one Void argument is not the same as function that takes no arguments at all.

Compiling

    func cod(v0:(), v1:(), v2:())
    {
    }

gives us the symbol

    __TF4xper3codFTT_T_T__T_

Discuss.

End of digression

2.1.2 Unnamed Element Tuples

2.1.2.1 Single Element

We already know the answer to this one.

The FNMM flattens a single unnamed element tuple type when it appears as the type of a vanilla parameter.

2.1.2.2 Multi Element

Compiling

    func cod(e t:(Int, Int))
    {
    }

gives us the symbol

    __TF4xper3codFTSiSi_T_

The FNMM does not treat a multiple unnamed element tuple type specially when it is used as the type of a vanilla parameter.

2.1.3 Named Tuples

2.1.3.1 Single Element

As in the return type case above, not supported by the compiler.

2.1.3.2 Multi Element

Compiling

    func cod(e t:(x:Int, y:Int))
    {
    }

gives us the symbol

    __TF4xper3codFT1xSi1ySi_T_

The FNMM does not treat a multiple named element tuple type specially when it is used as the type of a vanilla parameter.

2.1.4 Mixed Element Tuples

Compiling

    func cod(t:(x:Int, String, y:Int))
    {
    }

gives us the symbol

    __TF4xper3codFT1xSiSS1ySi_T_

The FNMM does not treat a mixed element tuple type specially when it is used as the type of a vanilla parameter.

2.2 External Names

2.2.1 Void

Compiling

    func cod(e t:())
    {
    }

gives us the symbol

     __TF4xper3codFT1eT__T_

How do you call it ?

Like this, obviously

    cod(t:())

2.2.2 Unnamed Element Tuples

2.2.2.1 Single Element

Compiling

    func cod(e t:(Int))
    {
    }

gives us the symbol

     __TF4xper3codFT1eSi_T_

The FNMM has flattened the single unnamed element tuple type as usual.

2.2.2.2 Multi Element

Compiling

    func cod(e t:(Int, Int))
    {
    }

gives us the symbol

     __TF4xper3codFT1eTSiSi__T_

The FNMM does not treat a multiple unnamed element tuple types specially when it is used as the type of a vanilla parameter with an external name.

2.2.3 Named Element Tuples

2.2.3.1 Single Element

Not supported by the compiler.

2.2.3.2 Multi Element

Compiling

    func cod(e t:(i: Int, j:Int))
    {
    }

gives us the symbol

     __TF4xper3codFT1eT1iSi1jSi__T_

The FNMM does not treat a multiple named element tuple type specially when it is used as the type of a vanilla parameter with an external name.

2.2.4 Mixed Element Tuples

Compiling

    func cod(e t:(x:Int, String, y:Int))
    {
    }

gives us the symbol

   __TF4xper3codFT1eT1xSiSS1ySi__T_

The FNMM does not treat a mixed element tuple type specially when it is used as the type of a vanilla parameter with an external name.

2.3 inout

2.3.1 Void

Compiling

    func cod(inout t:())
    {
    }

gives us the symbol

     __TF4xper3codFRT_T_

Yes you can call it.

You need a mutable empty tuple of course.

    $ swift
    Welcome to Swift!  Type :help for assistance.
      1> func cod(inout t:()) { println(t) }
      2> func dab()
      3. {
      4.     var empty : () = ()
      5.
      6.     cod(&empty)
      7. }
      8> dab()
    ()
      9>
      

You can have an external name as well if you want.

Compiling

    func cod(inout e t:())
    {
    }

gives us the symbol

     __TF4xper3codFT1eRT__T_

2.3.2 Unnamed Element Tuples

2.3.2.1 Single Element

Compiling

    func cod(inout t:(Int))
    {
    }

gives us the symbol

     __TF4xper3codFRSiT_

and compiling

    func cod(inout e t:(Int))
    {
    }

gives us the symbol

     __TF4xper3codFT1eRSi_T_

In both cases the FNMM has flattened the single unnamed element tuple type as usual.

2.3.2.2 Multi Element

Compiling

    func cod(inout t:(Int, Int))
    {
    }

gives us the symbol

     __TF4xper3codFRTSiSi_T_

and compiling

    func cod(inout e t:(Int, Int))
    {
    }

gives us the symbol

     __TF4xper3codFT1eRTSiSi__T_

The FNMM does not treat a multiple named element tuple type specially when it is used as the type of an inout parameter with or without an external parameter.

2.3.3 Named Element Tuples

2.3.3.1 Single Element

Not supported by the compiler.

2.3.3.2 Multi Element

Compiling

    func cod(inout t:(x:Int, y:Int))
    {
    }

gives us the symbol

     __TF4xper3codFRT1xSi1ySi_T_

and compiling

    func cod(inoute t:(x:Int, y:Int))
    {
    }

gives us the symbol

     __TF4xper3codFT1eRT1xSi1ySi__T_

The FNMM does not treat a multiple named element tuple type specially when it is used as the type of an inout parameter with or without an external name.

2.3.4 Mixed Element Tuples

Compiling

    func cod(inout t:(x:Int, String, y:Int))
    {
    }

gives us the symbol

    __TF4xper3codFRT1xSiSS1ySi_T_

and compiling

    func cod(inout e t:(x:Int, String, y:Int))
    {
    }

gives us the symbol

    __TF4xper3codFT1eRT1xSiSS1ySi__T_

The FNMM does not treat a mixed element tuple type specially when it is used as the type of an inout parameter with or without an external name.

2.4 varadic

2.4.1 Void

Compiling

    func cod(t:()...)
    {
    }

gives us the symbol

     __TF4xper3codFtGSaT___T_

and you can call it too.

You end up with an array of empty tuples.

Compiling

    func cod(voids t:()...)
    {
    }

gives us the symbol

     __TF4xper3codFt5voidsGSaT___T_

Needless to say you can call that one as well.

2.4.2 Unnamed Element Tuples

2.4.2.1 Single Element

Compiling

    func cod(t:(Int)...)
    {
    }

gives us the symbol

     __TF4xper3codFtGSaSi__T_

and compiling

    func cod(ints t:(Int)...)
    {
    }

gives us the symbol

     __TF4xper3codFt4intsGSaSi__T_

In both cases the FNMM has flattened the single element tuple type as usual.

2.4.2.2 Multi Element

Compiling

    func cod(t:(Int, Int)...)
    {
    }

gives us the symbol

     __TF4xper3codFtGSaTSiSi___T_

and compiling

    func cod(e t:(Int, Int)...)
    {
    }

gives us the symbol

     __TF4xper3codFt1eGSaTSiSi___T_

The FNMM does not treat a multiple unnamed element tuple type specially when it is used as the type of a varadic parameter with or without an external name.

2.4.3 Named Element Tuples

2.4.3.1 Single Element

Not supported by the compiler.

2.4.3.2 Multi Element

Compiling

    func cod(t:(i:Int, j:Int)...)
    {
    }

gives us the symbol

     __TF4xper3codFtGSaT1iSi1jSi___T_

and compiling

    func cod(e t:(i:Int, j:Int)...)
    {
    }

gives us the symbol

     __TF4xper3codFt1eGSaT1iSi1jSi___T_

The FNMM does not treat a multiple named element tuple type specially when it is used as the type of a varadic parameter with or without an external name.

2.4.4 Mixed Element Tuples

Compiling

    func cod(t:(x:Int, String, y:Int)...)
    {
    }

gives us the symbol

     __TF4xper3codFtGSaT1xSiSS1ySi___T_

and compiling

    func cod(e t:(x:Int, String, y:Int)...)
    {
    }

gives us the symbol

     __TF4xper3codFt1eGSaT1xSiSS1xSi___T_

The FNMM does not treat a mixed element tuple type specially when it is used as the type of a varadic parameter with or without an external name.

3.0 Conclusion

The FNMM simply cannot abide single unnamed element tuple types. Either that or they don’t exist.

Postscript

While ploughing through that lot it occurred to me that the FNMM’s dislike of single unnamed element tuple types is at the bottom of the way in which it encodes a function’s parameters.

Unfortunately the explanation is too long to fit into the margin of this post.


Copyright (c) 2014 By Simon Lewis. All Rights Reserved.

Unauthorized use and/or duplication of this material without express and written permission from this blog’s author and owner Simon Lewis is strictly prohibited.

Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and specific direction to the original content.

October 24, 2014

So Swift Then: Fun With Mangled Names Continued

Continuing where we left off in last week’s thrilling installment, that is, with the encoding of function parameters in mangled function names.

While the return type of a function is necessarily a type and nothing but a type, Swift function parameters come in a variety of different flavours.

Some of these flavours affact how a function is called and what it can be called with.

Mangled function names are potentially used by the linker so the names must include an encoding of the relevant information about the function parameters which affect how the function can be called.

A basic vanilla function parameter is of the form

    'name' ":" 'type'

for example

    func cod(b: Bool)

In the case of a vanilla parameter only the type is significant so we would expect that the parameter would appear in a mangled function name as the encoding of its type.

A vanilla parameter can be augmented with an ‘external name’ which is declared before the ‘name’, for example

    func cod(e b: Bool)

The ‘external name’ must appear in any call to the function, for example

    cod(e: true)

In this case, since the external name is significant, we would expect both it and the type to appear in a mangled function name is some way.

By default function parameters are immutable. A function parameter can be made mutable using the

    var

keywood. for example

    func cod(var b: Bool)

The fact that a parameter of a function is mutable is not apparent to a caller of that function, consequently we would not expect the encoding of a mutable parameter to be any different from that of an immutable one.

A function parameter can be made mutable such that changes to it are visible to the caller using the

    inout

keywood. for example

    func cod(inout b: Bool)

A call to this function would look like this

    cod(&flag)

and flag must be mutable.

In this case we would expect the encoding to comprise the encoding of the parameter type plus something to indicate that it is an inout parameter.

A vanilla parameter can have a default value added, like so

    func cod(b: Bool = true)

A parameter with a default value must have an ‘external name’. If it does not have an explicit one, the ‘name’ is also used as the ‘external name’.

A call to the function above with a value would look like this

    cod(b:true)

and without one, so the default value is used, like this

    cod()

While the presence of a default value does affect how the function can be called, the effect is to define a function with two different entry points.

It is easier for the compiler to handle this by generating what looks like two different functions than for the linker to edit compiled code at the call site.

Hence, in this case we would only expect the ‘external name’ and the parameter type to appear in the parameter encoding.

A Swift function can be defined to take a variable number of parameters lke so

    func cod(flags: Bool...)

In the body of the function

   flags

has the type

   [Bool]

One candidate for the encoding of this kind of parameter would be the encoding of the appropriate array type.

Vanilla Parameters

Compiling

    func cod(b: Bool)
    {
    }

gives us the symbol

    __TF4xper3codFSbT_

Compiling

    func cod(b: Bool, c: Bool)
    {
    }

gives us the symbol

    __TF4xper3codFTSbSb_T_

Compiling

    func cod(b: Bool, c: Bool, d: Bool)
    {
    }

gives us the symbol

    __TF4xper3codFTSbSbSb_T_

On the basis of these three examples so far we can infer the following rules for the encoding of the parameters of a function with N vanilla parameters.

If N == 0 the parameters are represented by the encoding of the empty tuple type

   ()

If N == 1 the parameters are represented by the encoding of

    T

where T is the type of the single parameter.

If N > 1 then the parameters are represented by the encoding of the tuple type

   (T[0], ..., T[N-1])

where T[i] is the type of the i'th parameter.

As it stands the N == 1 case is a bit odd. Why is it a special case ? What if the single parameter has a tuple type ?

Compiling this

    func cod(b: (Bool))
    {
    }

gives us the symbol

    __TF4xper3codFSbT_

NOT

    __TF4xper3codFTSb_T_

which is surprising but it does mean that the rule for N == 1 holds.

Compiling this

    func cod(b: (Bool,Bool))
    {
    }

gives us the symbol

    __TF4xper3codFTSbSb_T_

which is even more surprising.

Two functions with different numbers of parameters end up with the same mangled name.

That cannot be right.

How can you have both of the functions in the same library ?

What happens if you compile both of them in the same file ?

    $ swiftc -module-name xper -emit-library funcs.swift
    Basic Block in function '_TF4xper3codFTSbSb_T_' does not have terminator!
    label %entry1
    LLVM ERROR: Broken function found, compilation aborted!

That's not good.

What about putting them in different files ?

    $ swiftc -module-name xper -emit-library func01.swift func02.swift
    duplicate symbol __TF4xper3codFTSbSb_T_ in:
        [..]/func01-b2b04f.o
        [..]/func02-5b9bf6.o
    ld: 1 duplicate symbol for architecture x86_64
    <unknown>:0: error: linker command failed with exit code 1 (use -v to see invocation)

That's not good either.

OK, them's the rules and they are broken.

Sidles away nonchalantly, hands in pockets, hoping no one is going to ask him to pay for the damage.

Parameter Type Substitution Syntax

There is one slight twist with the encoding of function parameter types even in the all vanilla parameters case.

Compiling

    func cod(c1: Character, c2: Character)
    {
    }

gives us the symbol

    __TF4xper3codFTOSs9CharacterS0__T_

rather than

    __TF4xper3codFTOSs9CharacterOSs9CharacterST_

If we read

    S0_

as substitute the 0th parameter type then it makes sense.

External Names

Adding an external name to our first example

    func cod(e b: Bool)
    {
    }

and compiling gives us the symbol

    __TF4xper3codFT1eSb_T_

We now have the function's parameters represented by

    T1eSb_

which looks like the encoding of the named tuple type

    (e:Bool)

Adding external names to our second example

    func cod(e b: Bool, f c: Bool)
    {
    }

and compiling gives us the symbol

    __TF4xper3codFT1eSb1fSb_T_

and we now have the function's parameters represented by an encoding of the named tuple type

    (e:Bool, f:Bool)

Adding external names to the first two parameters of our third example

    func cod(e b: Bool, f c: Bool, d: Bool)
    {
    }

and compiling gives us the symbol

    __TF4xper3codFT1eSb1fSbSb_T_

and we now have the function's parameters represented by an encoding of the hybrid named/unnamed tuple type

    (e:Bool, f:Bool, Bool)

as you might expect.

Mutable Parameters

Modifying our first example once more

    func cod(var b: Bool)
    {
    }

and compiling gives us the symbol

    __TF4xper3codFSbT_

as expected the presence of the var keyword has no effect on the encoding of the paramater.

inout Parameters

Compiling

    func cod(inout b: Bool)
    {
    }

gives us the symbol

    __TF4xper3codFRSbT_

and

    RSb

for the encoding of the parameter with an

    R

for 'Reference' or 'Recondite' or something.

An inout parameter can have an 'external name'.

Compiling

    func cod(inoute b: Bool)
    {
    }

gives us the symbol

    __TF4xper3codFT1eRSb_T_

and

    T1eRSb_

for the encoding of the parameter.

Default Values

Compiling

    func cod(b: Bool = true)
    {
    }

gives us the symbol

    __TF4xper3codFT1bSb_T_

and a second symbol

    __TIF4xper3codFT1bSb_T_A_

The first symbol is the same as the symbol for the function

    func cod(b b: Bool)
    {
    }

and would enable the linker to identify the entry point for calls to the function made with a value.

The second symbol presumably enables the linker to identify the entry point for the calls to the function made without a value.

Adding another parameter

    func cod(s:String, b: Bool = true)
    {
    }

and compiling gives us the two symbols

    __TF4xper3codFTSS1bSb_T_

and

    __TIF4xper3codFTSS1bSb_T_A0_

because there are still only two ways to call the function, for example

    cod("")

and

    cod("", b:false)

Compiling a function with two parameters both with default values

    func cod(i:Int = 2, b: Bool = true)
    {
    }

and compiling gives us three symbols

    __TF4xper3codFT1iSi1bSb_T_
    __TIF4xper3codFT1iSi1bSb_T_A0_
    __TIF4xper3codFT1iSi1bSb_T_A_

as there are three ways to call the function.

In all these examples it looks as though it is the 'A' suffix on the 'secondary' symbols which identifies the parameter value or values which are being defaulted.

Varadic Parameters

Compiling

    func cod(flags: Bool...)
    {
    }

gives us the symbol

    __TF4xper3codFtGSaSb__T_

and

   tGSaSb__

for the encoding of the parameter.

We have

   GSaSb_

for

  [Bool]

but 't' rather than 'T' for the tuple.

Adding another parameter

    func  cod(s: String, flags:  Bool...)
    {
    }

and compiling gives us the symbol

    __TF4xper3codFtSSGSaSb__T_

A varadic parameter can have an 'external name'

    func  cod(flags f:  Bool...)
    {
    }

Compiling this gives us the symbol

    __TF4xper3codFt5flagsGSaSb__T_

The 'external name' is now present as we would expect.

In all these examples the 't' remains resolutely lower-case so it looks as though it is connected with the presence of the varadic parameter.

Not The Summary

This is another post that is now way too long so its time to call a halt.

Coming up next time, what has the function name mangling machine got against single element type tuples ?


Copyright (c) 2014 By Simon Lewis. All Rights Reserved.

Unauthorized use and/or duplication of this material without express and written permission from this blog's author and owner Simon Lewis is strictly prohibited.

Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and specific direction to the original content.

Blog at WordPress.com.

%d bloggers like this: