Specialisation of generic function seems to require type metadata as a parameter but does not use it

I'm compiling this simple code and it seems to add type metadata that it doesn't need.

import AVR

func readPin() -> UInt32 {
	digitalRead(pin: 1) ? 1 : 0
}

while true {
	let f = Float(readPin())
	print(f)
}

Where print and digitalRead are functions in my AVR library/module.

This compiles to the following LLVM IR:

%swift.type = type { i16 }
%TSf = type <{ float }>

@"$sSfN" = external global %swift.type, align 2
@__swift_reflection_version = linkonce_odr hidden constant i16 3
@_swift1_autolink_entries = private constant [6 x i8] c"-lAVR\00", section ".swift1_autolink_entries", align 2
@llvm.used = appending global [3 x i8*] [i8* bitcast (i16* @__swift_reflection_version to i8*), i8* getelementptr inbounds ([6 x i8], [6 x i8]* @_swift1_autolink_entries, i32 0, i32 0), i8* bitcast (i32 (i32, i8**)* @main to i8*)], section "llvm.metadata"
; Function Attrs: noreturn
define protected i32 @main(i32 %0, i8** nocapture readnone %1) #0 !dbg !23 {
entry:
  %2 = alloca %TSf, align 4
  %3 = bitcast %TSf* %2 to i8*, !dbg !35
  %._value = getelementptr inbounds %TSf, %TSf* %2, i16 0, i32 0, !dbg !35
  br label %4, !dbg !47

4:                                                ; preds = %4, %entry
  %5 = tail call zeroext i1 @_digitalRead(i8 zeroext 1), !dbg !48
  %. = zext i1 %5 to i32, !dbg !60
  call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %3), !dbg !35
  %6 = call swiftcc i1 @"$sSBss17FixedWidthInteger14RawSignificandRpzrlE8_convert4fromx5value_Sb5exacttqd___tSzRd__lFZSf_s6UInt32VTg5"(%TSf* noalias nocapture nonnull %2, i32 %., %swift.type* nonnull swiftself @"$sSfN"), !dbg !35
  %7 = load float, float* %._value, align 4, !dbg !35
  call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %3), !dbg !35
  call void @llvm.dbg.value(metadata float %7, metadata !61, metadata !DIExpression()), !dbg !63
  tail call swiftcc void @"$s3AVR19printNumberInternal6number10addNewlineySf_SbtF"(float %7, i1 true), !dbg !64
  br label %4, !dbg !70
}
; Function Attrs: optsize
declare zeroext i1 @_digitalRead(i8 zeroext) local_unnamed_addr #1
define linkonce_odr hidden swiftcc i1 @"$sSBss17FixedWidthInteger14RawSignificandRpzrlE8_convert4fromx5value_Sb5exacttqd___tSzRd__lFZSf_s6UInt32VTg5"(%TSf* noalias nocapture %0, i32 %1, %swift.type* swiftself %2) local_unnamed_addr #2 !dbg !71 {
entry:
  %3 = icmp eq i32 %1, 0, !dbg !76
  br i1 %3, label %4, label %5, !dbg !81, !prof !82, !misexpect !83

4:                                                ; preds = %entry
  %._value4 = getelementptr inbounds %TSf, %TSf* %0, i16 0, i32 0, !dbg !84
  store float 0.000000e+00, float* %._value4, align 4, !dbg !84
  br label %11, !dbg !81

5:                                                ; preds = %entry
  %6 = tail call i32 @llvm.ctlz.i32(i32 %1, i1 true), !dbg !89, !range !98
  %7 = trunc i32 %6 to i16, !dbg !89
  %8 = xor i16 %7, 31, !dbg !94
  %9 = tail call swiftcc i16 @"$sSf8exponentSivg"(float 0x47EFFFFFE0000000), !dbg !99
  %10 = icmp slt i16 %9, 0, !dbg !103
  br i1 %10, label %.thread, label %13, !dbg !81

11:                                               ; preds = %4, %.thread, %56, %57, %33
  %12 = phi i1 [ true, %33 ], [ %67, %57 ], [ false, %56 ], [ false, %.thread ], [ true, %4 ], !dbg !76
  ret i1 %12, !dbg !81

13:                                               ; preds = %5
  %14 = or i16 %9, %8, !dbg !81
  %15 = icmp ne i16 %14, 0, !dbg !81
  %16 = icmp slt i16 %9, %8, !dbg !103
  %or.cond = and i1 %16, %15, !dbg !81
  br i1 %or.cond, label %.thread, label %17, !dbg !81

17:                                               ; preds = %13
  %18 = icmp ugt i16 %8, 23, !dbg !81
  br i1 %18, label %19, label %33, !dbg !81

.thread:                                          ; preds = %13, %5
  %._value3 = getelementptr inbounds %TSf, %TSf* %0, i16 0, i32 0, !dbg !108
  store float 0x7FF0000000000000, float* %._value3, align 4, !dbg !108
  br label %11, !dbg !81

19:                                               ; preds = %17
  %20 = add nuw nsw i16 %8, 9, !dbg !81
  %21 = add nuw nsw i16 %8, 8, !dbg !81
  %22 = and i16 %21, 31, !dbg !112
  %23 = zext i16 %22 to i32, !dbg !112
  %24 = shl nuw i32 1, %23, !dbg !112
  %25 = and i16 %20, 31, !dbg !117
  %26 = zext i16 %25 to i32, !dbg !117
  %27 = lshr i32 %1, %26, !dbg !117
  %28 = shl i32 %24, 1, !dbg !119
  %29 = add i32 %28, -1, !dbg !123
  %30 = and i32 %29, %1, !dbg !125
  %31 = and i32 %27, 8388607, !dbg !125
  %32 = icmp ult i32 %24, %30, !dbg !127
  br i1 %32, label %47, label %43, !dbg !81

33:                                               ; preds = %17
  %34 = sub nuw nsw i16 23, %8, !dbg !81
  %35 = zext i16 %34 to i32, !dbg !129
  %36 = shl i32 %1, %35, !dbg !142
  %37 = add nuw nsw i16 %8, 127, !dbg !144
  %38 = zext i16 %37 to i32, !dbg !149
  %39 = and i32 %36, 8388607, !dbg !149
  %40 = shl nuw nsw i32 %38, 23, !dbg !149
  %41 = or i32 %39, %40, !dbg !149
  %42 = bitcast %TSf* %0 to i32*, !dbg !158
  store i32 %41, i32* %42, align 4, !dbg !158
  br label %11, !dbg !81

43:                                               ; preds = %19
  %44 = icmp eq i32 %30, %24, !dbg !76
  %45 = and i32 %27, 1, !dbg !158
  %46 = icmp ne i32 %45, 0, !dbg !76
  %or.cond9 = and i1 %46, %44, !dbg !81
  br i1 %or.cond9, label %47, label %57, !dbg !81

47:                                               ; preds = %43, %19
  %48 = add nuw nsw i32 %31, 1, !dbg !162
  %49 = icmp eq i32 %31, 8388607, !dbg !127
  br i1 %49, label %50, label %57, !dbg !81

50:                                               ; preds = %47
  %51 = add nuw nsw i16 %8, 1, !dbg !81
  %52 = tail call swiftcc i16 @"$sSf8exponentSivg"(float 0x47EFFFFFE0000000), !dbg !99
  %53 = icmp slt i16 %52, 0, !dbg !103
  %54 = icmp sle i16 %52, %8, !dbg !81
  %55 = or i1 %53, %54, !dbg !81
  br i1 %55, label %56, label %57, !dbg !81

56:                                               ; preds = %50
  %._value2 = getelementptr inbounds %TSf, %TSf* %0, i16 0, i32 0, !dbg !108
  store float 0x7FF0000000000000, float* %._value2, align 4, !dbg !108
  br label %11, !dbg !81

57:                                               ; preds = %47, %50, %43
  %58 = phi i16 [ %8, %43 ], [ %51, %50 ], [ %8, %47 ], !dbg !81
  %59 = phi i32 [ %31, %43 ], [ 8388608, %50 ], [ %48, %47 ], !dbg !81
  %60 = add nsw i16 %58, 127, !dbg !144
  %61 = and i16 %60, 255, !dbg !167
  %62 = zext i16 %61 to i32, !dbg !167
  %63 = and i32 %59, 8388607, !dbg !167
  %64 = shl nuw nsw i32 %62, 23, !dbg !167
  %65 = or i32 %64, %63, !dbg !167
  %66 = bitcast %TSf* %0 to i32*, !dbg !158
  store i32 %65, i32* %66, align 4, !dbg !158
  %67 = icmp eq i32 %30, 0, !dbg !76
  br label %11, !dbg !81
}
declare swiftcc void @"$s3AVR19printNumberInternal6number10addNewlineySf_SbtF"(float, i1) local_unnamed_addr #2
; Function Attrs: argmemonly nounwind willreturn
declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #3
; Function Attrs: argmemonly nounwind willreturn
declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #3
; Function Attrs: nounwind readnone speculatable willreturn
declare void @llvm.dbg.value(metadata, metadata, metadata) #4
; Function Attrs: nounwind readnone speculatable willreturn
declare i32 @llvm.ctlz.i32(i32, i1 immarg) #4
declare swiftcc i16 @"$sSf8exponentSivg"(float) local_unnamed_addr #2

attributes #0 = { noreturn "frame-pointer"="all" }
attributes #1 = { optsize "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #2 = { "frame-pointer"="all" }
attributes #3 = { argmemonly nounwind willreturn }
attributes #4 = { nounwind readnone speculatable willreturn }

Most of the LLVM IR is taken up by this function: @"$sSBss17FixedWidthInteger14RawSignificandRpzrlE8_convert4fromx5value_Sb5exacttqd___tSzRd__lFZSf_s6UInt32VTg5"(%TSf* noalias nocapture %0, i32 %1, %swift.type* swiftself %2) , which is a specialisation of the generic function _convert, normally defined in an extension on BinaryFloatingPoint in the file FloatingPoint.swift in the standard library...
xcrun swift-demangle sSBss17FixedWidthInteger14RawSignificandRpzrlE8_convert4fromx5value_Sb5exacttqd___tSzRd__lFZSf_s6UInt32VTg5 $sSBss17FixedWidthInteger14RawSignificandRpzrlE8_convert4fromx5value_Sb5exacttqd___tSzRd__lFZSf_s6UInt32VTg5 ---> generic specialization <Swift.Float, Swift.UInt32> of static (extension in Swift):Swift.BinaryFloatingPoint< where A.RawSignificand: Swift.FixedWidthInteger>._convert<A where A1: Swift.BinaryInteger>(from: A1) -> (value: A, exact: Swift.Bool)

What is strange and is causing me problems is the specialisation seems not to need type information or witness tables but still takes type metadata for Swift.Float as the third parameter but then never uses it in the function body!

It may be a shrug for most people as the metadata will exist in most programs. For me, it's a significant overhead. I don't have any type metadata in my programs, because they don't support reflection or debugging and adding it would be very difficult.

If I return UInt16 from the readPin function in my code, it compiles to short, simple, well optimised LLVM IR:

@__swift_reflection_version = linkonce_odr hidden constant i16 3
@_swift1_autolink_entries = private constant [6 x i8] c"-lAVR\00", section ".swift1_autolink_entries", align 2
@llvm.used = appending global [3 x i8*] [i8* bitcast (i16* @__swift_reflection_version to i8*), i8* getelementptr inbounds ([6 x i8], [6 x i8]* @_swift1_autolink_entries, i32 0, i32 0), i8* bitcast (i32 (i32, i8**)* @main to i8*)], section "llvm.metadata"
; Function Attrs: noreturn
define protected i32 @main(i32 %0, i8** nocapture readnone %1) #0 !dbg !23 {
entry:
  br label %2, !dbg !35

2:                                                ; preds = %2, %entry
  %3 = tail call zeroext i1 @_digitalRead(i8 zeroext 1), !dbg !38
  %4 = uitofp i1 %3 to float, !dbg !53
  call void @llvm.dbg.value(metadata float %4, metadata !60, metadata !DIExpression()), !dbg !62
  tail call swiftcc void @"$s3AVR19printNumberInternal6number10addNewlineySf_SbtF"(float %4, i1 true), !dbg !63
  br label %2, !dbg !69
}
; Function Attrs: optsize
declare zeroext i1 @_digitalRead(i8 zeroext) local_unnamed_addr #1
declare swiftcc void @"$s3AVR19printNumberInternal6number10addNewlineySf_SbtF"(float, i1) local_unnamed_addr #2
; Function Attrs: nounwind readnone speculatable willreturn
declare void @llvm.dbg.value(metadata, metadata, metadata) #3

I think this is probably the initialiser being called (from FloatingPointTypes.swift.gyb):

  // Fast-path for conversion when the source is representable as int,
  // falling back on the generic _convert operation otherwise.
  @inlinable // FIXME(inline-always)
  @inline(__always)
  public init<Source: BinaryInteger>(_ value: Source) {
    if value.bitWidth <= ${word_bits} {
      if Source.isSigned {
        let asInt = Int(truncatingIfNeeded: value)
        _value = Builtin.sitofp_Int${word_bits}_FPIEEE${bits}(asInt._value)
      } else {
        let asUInt = UInt(truncatingIfNeeded: value)
        _value = Builtin.uitofp_Int${word_bits}_FPIEEE${bits}(asUInt._value)
      }
    } else {
      // TODO: we can do much better than the generic _convert here for Float
      // and Double by pulling out the high-order 32/64b of the integer, ORing
      // in a sticky bit, and then using the builtin.
      self = ${Self}._convert(from: value).value
    }
  }

In my case word_bits is 16 so UInt32 is calling Float._convert(from: value).value, which is pulling in the unnecessary metadata parameter.

I suppose my question is why is this specialisation taking a metadata type parameter that it doesn't use? And is there a way to avoid this? It's making it hard to compiler for my rather compressed platform!

Terms of Service

Privacy Policy

Cookie Policy