jessewilson
07/30/2026, 9:18 PM.kt like so:
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:
complexToPolar(f64, f64) -> (f64, f64)
Any interest? Mind if I create a YouTrack? A bit of background in a thread...jessewilson
07/30/2026, 9:21 PMoption<string> into `(i32, i32, i32)`:
• 0 for null, 1 for non-null
• address in memory
• length in memoryjessewilson
07/30/2026, 9:26 PMencode 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 heapjessewilson
07/30/2026, 9:28 PMjessewilson
07/30/2026, 9:30 PMjessewilson
07/30/2026, 9:30 PMCharlieTap
07/30/2026, 10:33 PMJim Teichgräber
07/31/2026, 8:37 AMMind 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):
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.Frank Bouwens
07/31/2026, 9:33 AMFrank Bouwens
07/31/2026, 9:37 AMJim Teichgräber
07/31/2026, 9:38 AMJim Teichgräber
07/31/2026, 9:41 AMjessewilson
07/31/2026, 11:46 AMjessewilson
07/31/2026, 11:47 AM@FlattenResult would be explicit. (I can already manually flatten parameters!)Jim Teichgräber
07/31/2026, 11:48 AMjessewilson
07/31/2026, 11:51 AMJim Teichgräber
07/31/2026, 11:56 AMJim Teichgräber
08/06/2026, 8:34 AM