The following is probably a bad idea, but it's been kicking around in my head so I thought I might as well mention it. There's been a lot of discussion about syntax, so... what if we didn't add new syntax?
Starting point:
fn inline_asm<const FMT: &'static str>() { ... }
Example usage:
inline_asm::<"nop">();
Well, that's kind of ugly; it would be better to implement some kind of "const
arguments" feature so it could be inline_asm("nop")
instead. Such a feature would be useful for other things too; SIMD intrinsics currently use an unstable feature to imitate it (rustc_args_required_const
). But for now let's go with the existing syntax.
What about constraints? They could be passed as arguments to the inline_asm
function. Ideally the function would be variadic so you could pass any number of constraints. Well, no need for new language features for that; it can already be simulated:
struct inline_asm<const FMT: &'static str>;
impl<Args: TupleOfInlineAsmConstraints,
const FMT: &'static str>
FnOnce<Args> for inline_asm<{FMT}> {
type Output = ();
extern "rust-call" fn call_once(self, args: Args) {
// call intrinsic here
}
}
where TupleOfInlineAsmConstraints
is defined like this:
trait InlineAsmConstraint { }
trait TupleOfInlineAsmConstraints {}
// For now, manually implement for different sizes of tuple...
// size 0
impl TupleOfInlineAsmConstraints for () {}
// size 1
impl<T1: InlineAsmConstraint>
TupleOfInlineAsmConstraints for (T1,) {}
// size 2
impl<T1: InlineAsmConstraint, T2: InlineAsmConstraint>
TupleOfInlineAsmConstraints for (T1, T2) {}
// etc...
What do the constraints themselves look like? Something like this:
struct InReg<T, const NAME: &'static str>(T);
struct OutReg<'a, T, const NAME: &'static str>(&'a mut T);
struct InOutReg<'a, T, const NAME: &'static str>(&'a mut T);
Out and in-out variants take a mutable reference because they mutate their argument. The assembly code itself would not receive a reference; it would receive a register name that you write into, like usual. On one hand, this could be confusing. On the other hand, with the existing proposals, the idea that passing out(reg) x
causes x
to be mutated is, IMO, also confusing. There are very few language constructs that mutate lvalues without requiring you to explicitly take a mutable reference: the =
operator and its variants, and the .
operator. I can't think of any others.
Anyway, you could use constraints like this:
let mut foo = 4;
let mut bar = 0; // need to initialize with
// dummy value :\
inline_asm::<"mov {bar}, {foo}">(
OutReg::<_, "bar">(&mut bar),
InReg::<_, "foo">(foo));
Ouch – for the constraints the turbofishes are even uglier. With "const
arguments", the whole thing could look much nicer:
inline_asm(
"mov {bar}, {foo}",
OutReg("bar", &mut bar),
InReg("foo", foo));
Even with that, I'm not at all convinced that the benefits (of technically not adding any new syntax) outweigh the drawbacks. But I'm posting this anyway just as food for thought.