Gankra / abi-cafe

Pair your compilers up at The ABI Cafe!
https://faultlore.com/abi-cafe/book/
228 stars 15 forks source link

Test variadic functions #1

Open bjorn3 opened 2 years ago

bjorn3 commented 2 years ago

Most platforms implement them by passing variadic arguments the same way as non-variadic arguments. As I understand it AArch64 macOS however forces the variadic arguments to always be passed on the stack even if there are still free registers.

Gankra commented 2 years ago

Ooh fun, good tip!

Gankra commented 5 months ago

We're way closer to this being possible with #20 landed, but variadics are specifically a feature I punted on shipping in the first version. It would be helpful to have a few sketches of test inputs we'd want to express. I've generally avoided messing with variadics in my career, so I don't have a sense of like, how it's normally used.

That said with the new ValTree abstraction we have the ability for both sides to pre-agree on the values/types passed at compile time (used to allow us to test untagged unions, and even self-referential types which therefore have variable field counts... which is kinda what varargs are).

Gankra commented 5 months ago

Rough sketch of an idea here...

The following syntax could be supported:

fn "my_func" {
  inputs {
     _ "f32"
     _ "i32"
     _ "..." {
         _ "f32"
         _ "u64"   
     }
  }
}

The new syntax is here:

     _ "..." {
         _ "f32"
         _ "u64"   
     }

We would take "argument with type ..." to mean varargs, and expect the body of a union decl to follow.

The idea here is that this would let you specify the set of possible types you want to test the varargs with.

We wouldn't actually create a TyIdx for this, but we would embed a UnionTy in the function arg.

In the ValueTree, when we encounter such an argument:

https://github.com/Gankra/abi-cafe/blob/dd3747097ad3b15b19869f24db6102d0baf4d2bd/src/abis/vals.rs#L120-L130

We would then apply an expansion from one arg to N args as follows in the above code:

if let ArgKind::Varargs(varargs_union) = arg.kind {
    // Burn a Value to compute the length of the varargs length, uniformly in 0..8 (arbitrary)
    let mut varargs_len_generator = generators.next();
    let mut varargs_len = varargs_len_generator.generate_index(8);
    let mut sub_args = vec![];
    for sub_arg_idx in sub_args.len() {
        // Burn another Value to compute the the type of the vararg
        let tag_generator = generators.next(ty_idx, path.clone());
        let active_variant_idx = tag_generator.generate_idx(varargs_union.fields.len());

        // Now generate a (sub)arg with the selected type
        if let Some(field) = varargs_union.fields.get(active_variant_idx) {
            let mut vals = vec![];
            let arg_name = format!("{}{sub_arg_idx}", arg.name.to_string());
            generators.build_values(types, arg.ty, &mut vals, arg_name.clone())?;
            sub_args.push(ArgValues {
                ty: field.ty,
                arg_name,
                vals,
            });
        }
    }
    Ok(sub_args)
}

It's tempting here to "evaporate" the varargs completely but for various reasons we'll probably just want to make it so anyone who wants to look at args needs to deal with them potentially being a list of arguments. Also more code will need to shift to treating the ValueTree's accounting of the function args as canonical, as opposed to the one from the kdl-script type system. Right now it's kinda arbitrary/inconsistent in the code, i think.

Gankra commented 5 months ago

The benefits of doing the expansion in the ValueTree and remembering the "real" signature are that: