Hey, Is there a way to transform an IrCall of an ...
# compiler
m
Hey, Is there a way to transform an IrCall of an inline external library function after the function body has been inlined? I need some information from the call side for applying those informations. Are there any alternative way to accomplish this?
🚫 1
s
I doubt it is possible after inlining, as current extension for transforming IR is called before that happens Most of the information you may need is usually contained in the call itself (e.g. value/type params). You can also try to check into inlined function code using its symbol, which you can also find from the call
👌 1
Also a mental note I have for myself is that on JVM inlining happens on bytecode level for now, as it distributed with JARs. There you won't have access to inline function body coming from different module either.
👌 1
m
Do you think inlining could be moved to IR one day? The problem is that i create a class for each call to a specific function. But if the calling function is a inline function i would like to create the class on each inlined call site. I had some strange ideas like adding a synthetic lambda value parameter to the inline function which would delegate the responsibility of calling the function to a non inline caller. But this is not ideal.
s
Regarding moving it to IR, as I mentioned, it is done on other platforms already, but maybe someone from JB could share more I am not sure why exactly you want to create a class for each call, but may be able to help if you share a bit more info on what you are trying to achieve?
m
It's a DI project and the call im talking about is the entry point into the library. Here's a small sample:
Copy code
// provide the db
@Given
fun provideDb() = Db(given<Application>())

fun main(application: Application, userId: String) {
    // call the runReader function which is the entry point
    // and provide runtime arguments
    // to access the db and fetch the user
    val user = runReader(application) {
        given<Db>().getUser(userId)
    }
}
Code will then get transformed to something like this:
Copy code
// provide the db
@Given
fun provideDb($context: provideDbContext) = Db($context.application())

interface provideDbContext {
    fun application(): Application
}

class GeneratedContext(val application: Application) : mainLambdaContext, provideDbContext {
    override fun db() = provideDb(this)
    override fun application() = application
}

fun main(application: Application, userId: String) {
    val user = GeneratedContext(application).db().getUser(userId)
}

interface mainLambdaContext {
    fun db(): Db
}
Now i wanna create a convenience function for running DI code in the application context like so:
Copy code
inline fun <R> Application.runApplicationReader(block: @Reader () -> R): R {
    return runReader(this) { block() }
}
And then use it like this:
Copy code
fun main(application: Application, userId: String) {
    // access dependencies and get user
    val user = application.runApplicationReader {
        given<Db>().getUser(userId)
    }
}
It should create a DI component for each inlined runReader call but in case of the runApplicationReader function it creates only one component because we cannot transform the inlined call. Hope you understand it better now.
s
Oh, that's quite cool 🙂 So is this
given
accessible only when you are running it on the spot, or you can keep its contents in a separate function? E.g. is it always:
Copy code
application.runApplicationReader { 
    given<Db>().getUser(userId)
}
Or it can be
Copy code
application.runApplicationReader(::doStuff)

fun Reader.doStuff() {
    given<Db>().getUser(userId)
}
First case is actually quite easy to handle, you have the body of the function in IR and can go through it and check/modify whatever you please In the second case, you could have it in a separate JAR, and probably here you'll need some indication of which types you could expect from this
Reader
I was thinking for awhile how to create a similar system myself, but didn't really go far there 🙂
m
I updated my last post to show the full generated code
given<T>()
is an intrinsic which can only be called from
@Reader
functions (or
@Reader
classes) similar to
suspend
or
@Composable
.
@Reader
adds an synthetic
$context
parameter. the
given<T>()
call translates to a getter function in the generated context interface of the calling function. If a
@Reader
function calls another
@Reader
function it will add the callee context as an supertype of it's own context and passes the context down to the callee. the
runReader
call then triggers the execution The generated context implements the context of the
@Reader
lambda which get's passed. And satisfies dependencies via the inputs and
@Given
declarations which are kind of like
@Provides
and
@Inject
in dagger terms.
@Given
declarations are implictly marked as
@Reader
so that they can also call
given<T>()
and other
@Reader
functions to provide values So yes you can extract the contents of the
runReader
block to it's own function The idea is that you call
runReader
at the top level and then use
@Reader
functions and
@Reader
classes all the way.
s
Yep, got it In that sense you could unwrap lambda body directly if it is in the current method. I suspect you could apply similar optimization if definition is in the same compilation unit as usage, just peeking into that function body. Not sure about making them work across different jars, it seems to be below IR level :)