Pass by ref for inline functions?


#1

So JVM is not exactly well suited for passing variables by reference (aka inout, ref, &) but it seems like inline functions could support that fairly easily… It’d be very helpful in some cases, especially in performance critical code and for breaking down large/complex functions…

For example,

inline fun normalize(ref x: Double, ref y: Double) {
    val invLen = 1.0 / Math.hypot(x, y)
    x *= invLen
    y *= invLen
}

Now you either have to duplicate this all over the place or allocate many objects, neither of which is very good…


#2

I’d prefer a solution where you return a tuple (or a Pair) and use destructuring.
Unfortunately, destructuring only seems to work when declaring the variable.

As for the overhead of creating Pairs/tuples, maybe there could be something like inline data structs/classes.
Some data type you could use to pass structured data from and to inline functions, but without creating an object on the heap.


#3

There’s no way to return more than one value at a time in JVM, so you can’t do this without allocation…

But regardless, how come you’d prefer destructuring, it seems way more cumbersome? Compare

normalize(ref x, ref y)

Versus

normalize(x, y).let { (newX, newY) -> 
    x = newX
    y = newY
}

#4

The major issue here is support by the Kotlin targets. How could this be implemented on the JVM and in JavaScript without an allocation for each method invocation?

The JVM would need value types/objects, but there does not seem to be much progress here: https://bugs.openjdk.java.net/browse/JDK-8046159

It should be feasible for the native target though.


#5

Well, that’s why I’m (initially?) suggesting it only for inline functions, where the allocations can be bypassed… To generate the non-inline version, I guess the compiler could make the “official” JVM parameters be arrays (of expected length 1), and leave it up to the caller to deal with allocation - a little punishment for not using Kotlin :wink:


#6

inline functions are inlined (I guess they are inlined in JS/other dialects as well), so there’s no need for allocations or anything like that, compiler can just replace variable references with passed variables. That said, I’m not convinced that this feature is really needed, but I might be wrong.


#7

Sorry, forgot about/missed that while writing my reply.

I agree with @vbezhenar that it should not be needed for inline functions. The compilers (Kotlin to bytecode, HotSpot, V8, etc.) should be able to optimize the invocations of inline functions.

But I guess the bytecode for an invocation of this function will currently be less than optimal?

inline fun normalize(x: Double, y: Double): Pair<Double, Double> {
    val invLen = 1.0 / Math.hypot(x, y)
    return Pair(x * invLen, y * invLen)
}

If multiple return values were supported natively, then it would be easier for the compiler to generate good code for inline functions, I guess.


#8

There’s no need for that return value with inlined invocations… This:

fun something() {
   var x = ...
   var y = ...
   normalize(ref x, ref y)
}

should simply become something like

fun something() {
   var x = ...
   var y = ...
   val __invLen_0 = 1.0 / Math.hypot(x, y) // well, any weird generated name will do
   x *= __invLen_0
   y *= __invLen_0
}

#9

That’s why I was suggesting it for inline functions only.
The same way you’re only suggesting refs for inline functions.


#10

I understand. But I don’t mind the extra syntax. With the standard syntax, there is no issue for functions that are used to modify only a single variable. Functions that modify more variables add more syntax (which IMO is tolerable), but also introduce an allocation (that is unlikely to be optimized away).

But your suggestion will introduce entirely new semantics. Up until now you could be sure that whatever you called, your variables (but not what they pointed to) had exactly the same values as before. Now you have to check each invocation to see if it is an inline function, and if it is, if it uses ref parameters.

I guess this can be mitigated if you have to add ref to the call site too:

fun something() {
   var x = ...
   var y = ...
   normalize(ref x, ref y)
}

#11

Yes, I totally agree about requiring call-site ref. I wrote it that way a few posts up, but here I forgot…


#12

That’s why I was suggesting it for inline functions only.

Then I misunderstood your idea. So how would they (inline data structs/classes) work?


#13

My idea was simply that the compiler would create local variables to represent the field/properties of those classes instead of allocating them on the heap.

The idea is simply to remove the overhead of allocating objects, when you only need them locally. For example, when you need to return multiple values from inline functions.

Of course, recursivity wouldn’t be possible. And you would only be allowed to used them locally, pass them to inline functions or return them from inline functions.

Or, you could allow them to be passed to other functions and return them from other functions, but in that case, an allocation would be necessary to hold the values being passed.


#14

In such case is really probable the allocation of Pair in the stack (not heap).

Yes, JVM (1.4+) can allocate objects in the stack (https://www.ibm.com/developerworks/library/j-jtp09275/index.html).


#15

So over the weekend I played a bit with a toy ray tracer in two versions, one with mostly immutable objects (the way @elizarov likes it :), and one optimized towards avoiding allocations. The results were:

  • if the code was such that the escape analysis worked well, the difference was small, though still not entirely negligible - around 10-15%, depending on the scene
  • as soon as escape anaylsis didn’t work (which happens fairly easily), the object-heavy version was 3-5x slower, which isn’t that surprising - it allocated billions of objects per render, vs. tens in the other case.

So, I can confirm what @fvasco said, that the inline classes thing already seems to happen behind the scenes and so they’re probably not worth the effort. But I still think pass by ref could/should be supported, as it is often very useful, both for refactoring and when a function (i.e. some chunk of code) inherently has 2+ things to modify…


#16

If we are voting I would like to cast an infinite number of votes AGAINST any form of pass by reference. It is a REALLY bad idea. Have you not seen any of the many talks on why mutability is bad?


#17

Please, feel free to write code in any way you think is best, and allow me to do the same :slight_smile:

I think many of you here are very dogmatic about certain things (and do continue to be so, if you want, I don’t mind or even care much)… But please consider this - if we take your position to extreme, the program should have no mutable state whatsoever. But as far as I can tell, that means it has to be entirely useless, by definition.

Even the simplest computer (i.e. Turing machine) has a couple of instructions and what amounts to RAM (well, more like LAM :). It’s not ROM, because all such a “computer” could do is move the head back and forth, which can’t really achieve anything? So, IMHO, looking at mutable state as something inherently bad has to be mistaken in one way or another, because mutable state seems like the basis for any/all computation?


#19

Sorry, I will actively fight against perverting Kotlin to allow bad programming practices in the same way I would argue vehemently against for example getting rid of visibility modifiers (i.e. make everything public) or adding C style pointers.

I am not arguing for forbidding mutability altogether, but the idea that a function should be able to modify its parameters is horrendous. Kotlin does not forbid mutability, but favors functional programming and immutability. And you seem to be confused on what no mutable state means. It does not mean that there is no state. New state can be created, immutability means that once a state is created it is not modified. It does not mean that you cannot create new state.

I won’t bother trying to educate you on why mutable state is bad. Feel free to read up on it: https://www.google.com/search?q=why+is+mutable+state+bad

And for the record, I am not new to arguing against pass-by-reference. Here is a LONG thread I was involved in back in 1999 with someone that was an ex-C programmer that thought Java was useless because it did not have pointers that allowed a function to modify its input parameters: https://groups.google.com/forum/#!topic/comp.lang.java.programmer/RbnERebXbEI[1-25]


#20

You seem to be projecting your own feelings onto me… :slight_smile:

I’m happy to continue this discussion if you manage to explain the difference you’re making between “the program’s state changed” and “the program is now in a new state”. To me, they mean literally the same thing.


#21

Which once again points out that you are confused on what avoiding mutable state means. Once again, I am not wasting my time trying to explain it to you. Feel free to read up on it: https://www.google.com/search?q=why+is+mutable+state+bad2. Not the best talk on the subject, but this talk by Uncle Bob on the subject makes some of the points easy to understand: https://www.youtube.com/watch?v=7Zlp9rKHGD4.

Think of this. Java String objects are immutable. Once you create a String its content cannot be changed. Does that mean that Java does not let you do string manipulation? Of course not! You can create new strings but the original string does not get modified. FP tries to do the same for variables. As Uncle Bob says in that talk, mutable state means variable assignment. When you add assignment you introduce the concept of time to the program, which then leads to issues of concurrency.

Whether you get why this is better, I don’t really care. As long as no one goes perverting Kotlin with horrible practices like pass by reference.