Обтекатель объектных объектов Swift в apple / swift

После прочтения:

  • https://github.com/rodionovd/SWRoute/wiki/Function-hooking-in-Swift
  • https://github.com/rodionovd/SWRoute/blob/master/SWRoute/rd_get_func_impl.c

Я понял, что указатель функции Swift обернут swift_func_wrapper и swift_func_object (согласно статье в 2014 году).

Я думаю, это все еще работает в Swift 3, но я не мог найти, какой файл в https://github.com/apple/swift лучше всего описывает эти структуры.

Может кто-нибудь мне помочь?

Я считаю, что эти детали в основном являются частью реализации IRGen Swift – я не думаю, что вы найдете дружественные структуры в источнике, показывая вам полную структуру различных значений функции Swift. Поэтому, если вы хотите сделать это, я бы рекомендовал изучить IR, испускаемый компилятором.

Вы можете сделать это, выполнив команду:

 xcrun swiftc -emit-ir main.swift | xcrun swift-demangle > main.irgen 

который будет излучать ИК (с демарнированными символами) для -Новая assembly. Здесь вы можете найти документацию для LLVM IR .

Ниже приводится несколько интересных материалов, которые я смог изучить, пройдя через IR самостоятельно в сборке Swift 3.1. Обратите внимание, что все это может быть изменено в будущих версиях Swift (по крайней мере до тех пор, пока Swift не стабилен ABI). Само собой разумеется, что приведенные ниже примеры кода предназначены только для демонстрационных целей; и никогда не должны использоваться в фактическом коде производства.


Толстые значения функции

На самом базовом уровне значения функций в Swift – это простые вещи – они определены в IR как:

 %swift.function = type { i8*, %swift.refcounted* } 

который является необработанным указателем функции i8* , а также указателем на его контекст %swift.refcounted* , где %swift.refcounted определяется как:

 %swift.refcounted = type { %swift.type*, i32, i32 } 

которая представляет собой структуру простого объекта с подсчетом ссылок, содержащего указатель на метаданные объекта вместе с двумя 32-битными значениями.

Эти два 32-битных значения используются для подсчета ссылок объекта. Вместе они могут либо представлять (как из Swift 4):

  • Сильный и незанятый счетчик ссылок объекта + некоторые флаги, включая вопрос о том, использует ли объект собственный подсчет ссылок Swift (в отличие от подсчета ссылок Obj-C) и имеет ли объект боковую таблицу.

или

  • Указатель на боковую таблицу, содержащий выше, плюс слабый счетчик ссылок объекта (при формировании слабой ссылки на объект, если он еще не имеет боковой таблицы, будет создан).

Для дальнейшего чтения о внутренних подсчетах ссылок Swift Майк Эш имеет отличную запись в блоге по этому вопросу .

Контекст функции обычно добавляет дополнительные значения в конец этой %swift.refcounted структуры. Эти значения являются динамическими вещами, которые требуется функции при вызове (например, любыми зафиксированными значениями или любыми параметрами, с которыми она была частично применена). В довольно многих случаях значениям функций не нужен контекст, поэтому указатель на контекст будет просто nil .

Когда функция вызывается, Swift просто переходит в контекст как последний параметр. Если функция не имеет параметра контекста, появляется соглашение о вызове, позволяющее безопасно передавать его.

Сохранение указателя функции вместе с указателем контекста называется толстым значением функции,
и как Swift обычно сохраняет значения функций известного типа (в отличие от значения тонкой функции, которое является только указателем функции).

Таким образом, это объясняет, почему MemoryLayout<(Int) -> Int>.size возвращает 16 байт – потому что он состоит из двух указателей (каждое из которых представляет собой слово длиной, т. MemoryLayout<(Int) -> Int>.size 8 байт на 64-битной платформе).

Когда значения толстых функций передаются в функциональные параметры (где эти параметры имеют не общий тип), Swift, похоже, передает исходный указатель и контекст функции как отдельные параметры.


Захват значений

Когда замыкание фиксирует значение, это значение будет помещено в поле, выделенное кучей (хотя само значение может получить повышение стека в случае неэкранирующего закрытия – см. Следующий раздел). Этот ящик будет доступен для функции через объект контекста ( соответствующий IR ).

Для закрытия, которое просто фиксирует одно значение, Swift просто делает сам блок контекстом функции (нет необходимости в дополнительной косвенности). Таким образом, у вас будет значение функции, которое выглядит как ThickFunction> из следующих структур:

 // The structure of a %swift.function. struct ThickFunction { // the raw function pointer var ptr: UnsafeRawPointer // the context of the function value – can be nil to indicate // that the function has no context. var context: UnsafePointer? } // The structure of a %swift.refcounted. struct RefCounted { // pointer to the metadata of the object var type: UnsafeRawPointer // the reference counting bits. var refCountingA: UInt32 var refCountingB: UInt32 } // The structure of a %swift.refcounted, with a value tacked onto the end. // This is what captured values get wrapped in (on the heap). struct Box { var ref: RefCounted var value: T } 

Фактически, мы можем убедиться в этом сами, выполнив следующее:

 // this wrapper is necessary so that the function doesn't get put through a reabstraction // thunk when getting typed as a generic type T (such as with .initialize(to:)) struct VoidVoidFunction { var f: () -> Void } func makeClosure() -> () -> Void { var i = 5 return { i += 2 } } let f = VoidVoidFunction(f: makeClosure()) let ptr = UnsafeMutablePointer.allocate(capacity: 1) ptr.initialize(to: f) let ctx = ptr.withMemoryRebound(to: ThickFunction>.self, capacity: 1) { $0.pointee.context! // force unwrap as we know the function has a context object. } print(ctx.pointee) // Box(ref: // RefCounted(type: 0x00000001002b86d0, refCountingA: 2, refCountingB: 2), // value: 5 // ) ff() // call the closure – increment the captured value. print(ctx.pointee) // Box(ref: // RefCounted(type: 0x00000001002b86d0, refCountingA: 2, refCountingB: 2), // value: 7 // ) ptr.deinitialize() ptr.deallocate(capacity: 1) 

Мы видим, что, вызывая функцию между печатью значения объекта контекста, мы можем наблюдать изменение значения захваченной переменной i .

Для множественных захваченных значений нам нужно дополнительное косвенное обращение, так как ящики не могут быть сохранены непосредственно в контексте данной функции и могут быть захвачены другими закрытиями. Это делается путем добавления указателей в поля до конца %swift.refcounted .

Например:

 struct TwoCaptureContext { // reference counting header var ref: RefCounted // pointers to boxes with captured values... var first: UnsafePointer> var second: UnsafePointer> } func makeClosure() -> () -> Void { var i = 5 var j = "foo" return { i += 2; j += "b" } } let f = VoidVoidFunction(f: makeClosure()) let ptr = UnsafeMutablePointer.allocate(capacity: 1) ptr.initialize(to: f) let ctx = ptr.withMemoryRebound(to: ThickFunction>.self, capacity: 1) { $0.pointee.context!.pointee } print(ctx.first.pointee.value, ctx.second.pointee.value) // 5 foo ff() // call the closure – mutate the captured values. print(ctx.first.pointee.value, ctx.second.pointee.value) // 7 foob ptr.deinitialize() ptr.deallocate(capacity: 1) 

Передача функций в параметры родового типа

Вы заметите, что в предыдущих примерах мы использовали оболочку VoidVoidFunction для наших значений функций. Это связано с тем, что в противном случае при передаче в параметр родового типа (например, UnsafeMutablePointer initialize(to:) ), Swift будет выставлять значение функции через некоторые реконструирующие thunks, чтобы унифицировать свое вызывающее соглашение на одно, где аргументы и возврат передается по ссылке, а не по значению ( соответствующий IR ).

Но теперь наше значение функции имеет указатель на thunk, а не фактическую функцию, которую мы хотим вызвать. Итак, как thunk знает, какую функцию вызывать? Ответ прост – Swift ставит функцию, которую мы хотим, чтобы вызов вызывал в самом контексте , что будет выглядеть следующим образом:

 // the context object for a reabstraction thunk – contains an actual function to call. struct ReabstractionThunkContext { // the standard reference counting header var ref: RefCounted // the thick function value for the thunk to call var function: ThickFunction } 

Первый тон, который мы проходим, имеет 3 параметра:

  1. Указатель на то, где нужно сохранить возвращаемое значение
  2. Указатель того, где расположены аргументы функции.
  3. Объект контекста, который содержит фактическое толстое значение функции для вызова (например, показанное выше)

Этот первый thunk просто извлекает значение функции из контекста, а затем вызывает второй thunk с 4 параметрами:

  1. Указатель на то, где нужно сохранить возвращаемое значение
  2. Указатель того, где расположены аргументы функции.
  3. Необработанный указатель функции для вызова
  4. Указатель на контекст функции для вызова

Этот thunk теперь извлекает аргументы (если есть) из указателя аргументов, затем вызывает данный указатель функции с этими аргументами вместе со своим контекстом. Затем он сохраняет возвращаемое значение (если оно есть) по адресу указателя возврата.

Как и в предыдущих примерах, мы можем проверить это так:

 func makeClosure() -> () -> Void { var i = 5 return { i += 2 } } func printSingleCapturedValue(t: T) { let ptr = UnsafeMutablePointer.allocate(capacity: 1) ptr.initialize(to: t) let ctx = ptr.withMemoryRebound(to: ThickFunction>>.self, capacity: 1) { // get the context from the thunk function value, which we can // then get the actual function value from, and therefore the actual // context object. $0.pointee.context!.pointee.function.context! } // print out captured value in the context object print(ctx.pointee.value) ptr.deinitialize() ptr.deallocate(capacity: 1) } let closure = makeClosure() printSingleCapturedValue(t: closure) // 5 closure() printSingleCapturedValue(t: closure) // 7 

Выход из-за отсутствия захвата

Когда компилятор может определить, что захват данной локальной переменной не ускользнет от времени жизни функции, в которой он объявлен, он может оптимизировать, продвигая значение этой переменной из блока, выделенного кучей, в стек (это гарантировано оптимизация и происходит в четном – одно). Затем объекту контекста функции нужно только сохранить указатель на заданное зафиксированное значение в стеке, так как он гарантированно не понадобится после выхода функции.

Это может быть сделано поэтому, когда замыкание (ов), фиксирующее переменную, известно, что оно не выходит за время жизни функции.

Как правило, закрывающееся закрытие представляет собой:

  • Сохраняется в нелокальной переменной (включая возврат из функции).
  • Захвачено другим закрывающимся закрытием.
  • @escaping как аргумент функции, где этот параметр либо помечен как @escaping , либо не имеет типа функции (обратите внимание, что это включает составные типы, такие как необязательные типы функций).

Итак, следующие примеры, когда захват данной переменной можно считать не утерять время жизни функции:

 // the parameter is non-escaping, as is of function type and is not marked @escaping. func nonEscaping(_ f: () -> Void) { f() } func bar() -> String { var str = "" // c doesn't escape the lifetime of bar(). let c = { str += "c called; " } c(); // immediately-evaluated closure obviously doesn't escape. { str += "immediately-evaluated closure called; " }() // closure passed to non-escaping function parameter, so doesn't escape. nonEscaping { str += "closure passed to non-escaping parameter called." } return str } 

В этом примере, поскольку str только когда-либо фиксируется закрытием, которое, как известно, не позволяет избежать жизни функции bar() , компилятор может оптимизировать, сохраняя значение str в стеке, при этом объекты контекста сохраняют только указатель на это ( соответствующий IR ).

Таким образом, объекты контекста для каждого из закрытий 1 будут выглядеть как Box> , с указателями на строковое значение в стеке. Хотя, к сожалению, в стиле Шредингера попытка заметить это путем выделения и повторного связывания указателя (например, прежде) вызывает компилятор, чтобы обработать данное закрытие как escaping – поэтому мы снова смотрим на Box для контекста.

Чтобы справиться с несоответствием между объектами контекста, которые содержат указатели (ов) для захваченных значений, а не удерживать значения в своих собственных ячейках, выделенных кучей, – Swift создает специализированные реализации замыканий, которые принимают указатели на захваченные значения в качестве аргументов.

Затем для каждого замыкания создается thunk, который просто берет в заданный объект контекста, извлекает указатель (ов) к захваченным значениям из него и передает это на специализированную реализацию закрытия. Теперь мы можем просто указать указатель на этот кусок вместе с нашим контекстным объектом как на значение толстой функции.

Для нескольких захваченных значений, которые не исчезают, дополнительные указатели просто добавляются в конец поля, т. Е.

 struct TwoNonEscapingCaptureContext { // reference counting header var ref: RefCounted // pointers to captured values (on the stack)... var first: UnsafePointer var second: UnsafePointer } 

Эта оптимизация продвижения захваченных значений из кучи в стек может быть особенно полезна в этом случае, поскольку нам больше не нужно выделять отдельные поля для каждого значения, как это было раньше.

Кроме того, стоит отметить, что многие случаи с захватом закрытия без экранирования могут быть оптимизированы гораздо более агрессивно в -O с помощью inlining, что может привести к тому, что объекты контекста будут полностью оптимизированы.


1. Немедленно оцененные замыкания фактически не используют объект контекста, указатель (и) на захваченные значения просто передаются непосредственно ему при вызове.

  • Словарь в Swift с Mutable Array как значение работает очень медленно? Как оптимизировать или построить правильно?
  • Xcode 8 Beta 3 Используйте устаревшую версию Swift
  • Как управлять расстоянием между строками в UILabel
  • Swift: Как использовать sizeof?
  • Как преобразовать шестнадцатеричный номер в корзину в Swift?
  • metadata.downloadURL () больше не распознается?
  • Сохранение значений в завершенииHandlers - Swift
  • Несколько функций с тем же именем
  • Как вы разворачиваете опции Swift?
  • Почему дополнительная константа автоматически не имеет значения по умолчанию nil
  • Swift 3 для цикла с приращением
  • Давайте будем гением компьютера.