Частичное применение в LLVM
Я пытаюсь создать функцию "добавить", которая может быть применена к одному аргументу, а затем к другому. Я не могу понять, как представить это с помощью LLVM IR, поскольку я не понимаю, как вызвать функцию с одним значением, затем сохранить значение где-нибудь в памяти и вернуть другую функцию, которая применяется к этому значению. Мне понадобится какой-то механизм закрытия в LLVM.
Я искал реализации этого в C, чтобы я мог просматривать испускаемый LLVM через clang, но решения, которые я нашел, были чрезвычайно сложными, поэтому я подумал, что могу просто исследовать LLVM напрямую.
Это была бы неиспользованная версия
define i8 @add(i8 %a, i8 %b) {
entry:
%res = add i8 %a, %b
ret i8 %res
}
И как-то я хотел бы для add(1)
вернуть i8 (i8)
тип. Я полагаю, мне придется как-то разделить функцию.
пс. Я смотрю на это, потому что я работаю над компилятором для небольшого функционального языка, поэтому я ищу любой совет, касающийся реализации частичного применения / каррирования в дизайне компилятора в целом.
Обновление: теперь у меня работает следующий код, но он довольно сложный, и я не думаю, что его будет легко сгенерировать автоматически.
declare i32 @printf(i8* noalias nocapture, ...)
define { i8, i8 (i8, i8) * } @add1(i8 %a) {
; allocate the struct containing the supplied argument
; and a function ptr to the actual function
%nextPtr = alloca { i8, i8 (i8, i8) * }
store { i8, i8 (i8, i8) * } { i8 undef, i8 (i8, i8) * @add2 }, { i8, i8 (i8, i8) * } * %nextPtr
%next0 = load { i8, i8 (i8, i8) * } * %nextPtr
; insert the supplied arg into the struct
%next1 = insertvalue { i8, i8 (i8, i8) * } %next0, i8 %a, 0
ret { i8, i8 (i8, i8) * } %next1
}
define i8 @add2(i8 %a, i8 %b) {
%res = add i8 %a, %b
ret i8 %res
}
define i8 @main() {
; call add(35) resulting in 'fn' of type {35, &add2}
%res1 = call { i8, i8 (i8, i8) * } @add1(i8 35)
; get the arg of the first call, ie element 0 of the resulting struct
%arg = extractvalue { i8, i8 (i8, i8) * } %res1, 0
; similarily get the function ptr
%fn = extractvalue { i8, i8 (i8, i8) * } %res1, 1
; apply the argument to the function
%res = call i8 %fn(i8 %arg, i8 30)
; print result
%ptr = alloca i8
store i8 %res, i8* %ptr
call i32 (i8*, ...)* @printf(i8* %ptr)
ret i8 0
}
1 ответ
Я написал этот пример кода C, чтобы имитировать то, что мой компилятор функционального языка поддерживает для "частичного применения" (лямбда-исчисление). Я не излучаю код 'C', а вместо этого напрямую излучаю в LLVM-IR. Вы можете видеть перспективу LLVM-IR, просто испуская из исходного источника:
clang -S -emit-llvm partial.c
Компилятор запускается для генерации частичного механизма в LLVM-IR, когда он проходит через узел AST, отражая заключенное в скобки (дать или взять несколько дополнительных деталей) выражение.
Надеюсь это поможет.