I would like to write a Kotlin function that compi...
# webassembly
j
I would like to write a Kotlin function that compiles to a Wasm function with multiple return values. For example, I would like to be able to write
.kt
like so:
Copy code
value class Complex(real: Double, imaginary: Double)
value class Polar(radius: Double, angle: Double)
fun complexToPolar(complex: Complex): Polar
And have the compiler produce a
.wasm
with this signature:
Copy code
complexToPolar(f64, f64) -> (f64, f64)
Any interest? Mind if I create a YouTrack? A bit of background in a thread...
When the component model implements flattening, it turns
option<string>
into `(i32, i32, i32)`: • 0 for null, 1 for non-null • address in memory • length in memory
I would like to encapsulate this into an
encode
function. But without multiple return values, I’m effectively forced to either inline the function (bloats code size) or transmit
address
and
length
through the heap
Host.kt
⬆️ This sample is a work-in-progress of the component model host. My generator does everything inline, and it creates way too much code. Variants are particularly brutal 'cause they require big switch statements each time they’re used.
(Doing everything inline won’t work for recursive structures anyway)
c
I guess the hard part is where you draw the line with the arity, I think the upper bound on params/results in core wasm is u32 but realistically you'll probably say anything over n and you just return a pointer
1
j
Mind if I create a YouTrack?
Always feel free to create a feature request :) But to be honest, I think "forcing" this behavior via value classes is possibly not the optimal way to do this. I'd prefer it if we had something somewhat akin to sroa, that simply does this for you in simple cases like the example you give, even if they're normal classes (with the prerequisite that the parameter is not modified in place by reference) But I think in both cases, the implementation would come with challenges. For parameters this might be possible, but for returns, even the canonical ABI of the component model itself, when flattening, boxes more than one flat return (from CanonicalABI.md#flattening):
Copy code
MAX_FLAT_PARAMS = 16
MAX_FLAT_ASYNC_PARAMS = 4
MAX_FLAT_RESULTS = 1

def flatten_functype(opts, ft, context):
  flat_params = flatten_types(ft.param_types(), opts)
  flat_results = flatten_types(ft.result_type(), opts)
  if not opts.async_:
    if len(flat_params) > MAX_FLAT_PARAMS:
      flat_params = [opts.memory.ptr_type()]
    if len(flat_results) > MAX_FLAT_RESULTS:
      match context:
        case 'lift':
          flat_results = [opts.memory.ptr_type()]
        case 'lower':
          flat_params += [opts.memory.ptr_type()]
          flat_results = []
    return CoreFuncType(flat_params, flat_results)
  else:
    match context:
      case 'lift':
        if len(flat_params) > MAX_FLAT_PARAMS:
          flat_params = [opts.memory.ptr_type()]
        if opts.callback:
          flat_results = ['i32']
        else:
          flat_results = []
      case 'lower':
        if len(flat_params) > MAX_FLAT_ASYNC_PARAMS:
          flat_params = [opts.memory.ptr_type()]
        if len(flat_results) > 0:
          flat_params += [opts.memory.ptr_type()]
        flat_results = ['i32']
    return CoreFuncType(flat_params, flat_results)

def flatten_types(ts, opts):
  return [ft for t in ts for ft in flatten_type(t, opts)]
Note the definition and use of
MAX_FLAT_RESULTS
. We'd have to support a compile-time way to distinguish when such a flat representation of a type can be passed, and either: • analyze every single usage of the type (and be sure there are no others), to make sure we can do sroa on all of them ◦ (this is much simpler in a simpler language) • Have a conversion between the 2 variants when there are cases where the type wants to be used in a scalar form, and where it needs to be used in the original aggregate form. This one sounds very hairy But I'm interested in the conversation, no doubt. If we could prove that on a sufficently large codebase such an sroa would match a bunch of code, that would allow us to get rid of a bunch of heap allocations too, I think that could bring pretty nice performance benefits.
f
Maybe use
Pair
with a
type alias
? kotlinlang.org/docs/inline-classes.html#…
Scherm­afbeelding 2026-07-31 om 11.37.44.png
j
Even without a type alias, a pair will not be turned into a multiple return right now, for many reasons. One of them being that, while all pair elements are val on a kotlin source level, val fields aren't actually marked immutable in wasm so far
👍 1
So once we mark them mutable (as we do for any val field right now), there's no way for any part of the optimization pipeline to realize this could be returned by value (except to reanalyze every usage to make sure it couldn't be immutable after all, which would waste a bunch of compile-time. We should instead just explore/fix KT-88159)
j
Thanks for the thoughtful analysis! Yes, the SROA optimization is exactly what I want.
👍 1
Would it make sense to hint that I want SROA on a function?
@FlattenResult
would be explicit. (I can already manually flatten parameters!)
👍 1
j
I think if we can implement it in general, then it should be on by default in optimized compilation, and what an annotation like that could provide is a warning if it's not successful (kind of like how tailrec works in some languages)
yes black 1
💯 1
j
(Could this optimization also work on other Kotlin Native targets? Might be easier to get engineering resources to make iOS faster, even if all the LLVM targets that benefit!)
j
I think flattening parameters automatically (and "stackifying" classes in simple cases, like clang/llvm sroa does for C/"POD" (plain old data) structs), should probably be possible generically, and could be discussed as a common IR optimization. Result types are a special beast, because many ABIs are quite funny about struct returns, and require pre-allocation on the stack, etc. But given that LLVM should handle all of that in the case of K/Native, I personally don't see a blocker for it. It might just be that this ABI funniness means it doesn't actually get us good benefits, and thus won't be prioritized. Though I can't guarantee a) that I'm right and b) that this will be a priority for K/Native. But, to be clear, I think it would be awesome! 🙃
🙃 1
To follow our progress on this (can't guarantee we'll prioritize it immediately though, so don't get your hopes up too high :)): KT-88358
🙏🏻 1