Generating serialized SIL for inlinable & transparent functions in REPL expressions

I am experimenting with a patch that changes the SwiftExpressionParser to mimic the behavior of Merge Modules Pass in relation to the Swift Evolution proposal cross module inlining and serialization.

Specifically, if a function is marked as @transparent or @inlinable, the compiler serializes the SIL generated for the function and the merge modules pass has access to the SIL IR generated for such functions in imported modules. I would like to replicate that behavior by making the SIL IR generated for inlinable functions in one REPL line to be available when parsing subsequent REPL lines.

We also need this in the context of Automatic Differentiation and Graph Program Extraction that we are implementing as part of the Swift for TensorFlow project. Both of these components benefit from having access to the SIL IR generated for certain functions in imported modules.

I have created a proof-of-concept patch to get this working. Basically, it serializes the SIL module for each REPL line into a .swiftmodule in a temporary directory for the session and imports them later. It is admittedly hacky and not robust, but I am looking for feedback on whether this is the right direction to pursue.

@dcci @Slava_Pestov @Frederic_Riss if you know of anyone I should talk to regarding this, please let me now.

3 Likes

Instead of serializing and deserializing the SIL between REPL inputs can you keep the same SILModule around in memory?

1 Like

Thanks for suggestion, @Slava_Pestov.

I did consider keep around the SILModule in memory for each REPL input. However, the SILModule::LinkFunction ultimately calls SerializedSilLoader::lookupSILFunction, which only goes over deserialized SIL sections. That is why I went down this route. (It might very well be that I am missing some context. Please let me know in that case.)

Or, are you suggesting just keeping around one single SILModule that you keep accumulating into (just as it is begin done for the IRGen'd LLVM module)? This might not be trivial as we need to change the IRGen & execution parts as well.

Yeah.

Also I notice that default arguments already work in the REPL. I can define a function func foo(x: Int = 0) {} and then call it as foo() and it works. This suggests we can already call SIL-only functions, so I'm confused about what exactly is still broken.

1 Like

The LLVM bitcode is correct as far as I understand even for SIL functions. That is why you are able to call foo() and it works.

However, there are cases where one needs access to the SIL function in an imported module when compiling a swift module.

Auto-differentiation is an example. Suppose we define a function @inlinable foo() {....} in one REPL line and in another line we try to get the gradient as let gradfoo = #gradient(foo), it does not work as we don't have a SIL function to create the gradient function for foo.

By serializing inlinable functions, subsequent REPL lines have access to the SIL function if needed.

Right, but you can only call symbols that have public linkage in LLVM, right? Default argument generators are not public and are SIL-only. So if each REPL invocation creates a new SIL module I would expect it to have to re-emit the SIL.

I believe the way we're using the LLVM JIT allows linking to any symbols that've been previously emitted as long as they aren't private.