Подтвердить что ты не робот

Как эффективно осуществлять закрытие в LLVM IR?

Я начал добавлять закрытие (lambdas) на мой язык, который использует LLVM в качестве бэкэнд. Я реализовал их для простых случаев, когда они всегда могут быть встроены, т.е. Код для определения закрытия сам по себе не нужно генерировать, так как он является встроенным, где используется.

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

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

Я подумал о одном возможном решении, используя LLVM trampoline intrinsics, который "исключает" один параметр из функции, возвращая указатель на функцию батута, которая принимает один меньший параметр. В этом случае, если функция, сгенерированная для замыкания, заменила среду ссылок как первый параметр, я мог бы ее вырезать и вернуть функцию, которая принимает ровно столько же параметров, сколько фактически объявляет закрытие. Звучит ли это? Эффективное? Есть ли лучшие решения?

Пример кода:

def applyFunctionTo(value: Int, f: (Int) -> Int) = f(value)

def main() = {
  val m := 4;
  val n := 5;
  val lambda := { (x: Int) => x + m + n };
  applyFunctionTo(3, lambda)
}

Теперь давайте представим, что это не будет привязано к def main() = 3 + 4 + 5 и что applyFunctionTo может быть скомпилирован отдельно, и мы не можем изменить сайт вызова там. С батутом я представляю, что сгенерированный код был бы чем-то вроде этого (выраженный в псевдокоде, * означает указатель):

def main$lambda$1(env: {m: Int, n: Int}*, x: Int) = x + env.m + env.n
def main() = {
  m = 4
  n = 5
  env* = allocate-space-for {Int, Int}
  env = {m, n}
  tramp* = create-trampoline-for(main$lambda$1*, env*)
  return applyFunctionTo(3, tramp*)
  // release memory for env and trampoline if the lambda didn't escape
}

Правильно ли это?

4b9b3361

Ответ 1

Звуки выполнимы и эффективны.

Альтернативным способом, который не требует трамплинов, является определение типа закрытия как пары указателей на функцию и указателя на среду, т.е. указателя стека. В C-вызове дополнительные аргументы игнорируются, поэтому, если вы предоставляете среду в качестве последнего аргумента, вы можете использовать (function_ptr, null) в качестве обратного вызова для регулярной функции.

Ответ 2

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

Создатель замыкания отвечает за настройку переменных TLS и "сохранение" состояния, которое у них было (разрешить рекурсивный вызов).

Затем пользователь вызывает функцию нормально, она выполняется и использует среду.

После вызова создатель замыкания "восстанавливает" исходные значения в переменные TLS.