Just complementing the other reply, there is a small article about how you can implement a source to source AD in Julia [1]. Basically Julia has a special type of macro called generated function [2] which instead of executing during the AST lowering phase (when the compiler still didn't evaluate the symbols) it executes during the final step of compilation (when type inference already ran and you have all the exact types), and in that function you can return either the AST or Julia's SSA IR directly (which is good for AD since it closely resembles the execution graph since it avoids mutability). And you can also inspect the IR of any function call and manipulate it within the language [3], so you can recursively create the tape entirely at compile time.
[1] http://blog.rogerluo.me/2019/07/27/yassad/
[2] https://docs.julialang.org/en/v1/manual/metaprogramming/inde...
[3] https://mikeinnes.github.io/IRTools.jl/latest/#Evaluating-IR...