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

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

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

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

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

Пример кода:

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
}

Это кажется правильным?

22
задан Erkki Lindpere 4 January 2012 в 00:11
поделиться