You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
60 and 0 are number literals and need to be casted to a type to be valid. This causes an assertion failure in the compiler if you don't cast the number literals. Assembly syntax should include types for inputs.
[number], [arg1], [ret] unused, and that is awkward.
This shuffles the syntax around and makes it more like a function call. Clobbers are extra "inputs" that don't have a name and a type. The register names are still clunky.
This proposal also operates on the assumption that all inline assembly can operate on inputs and outputs.
Reacted by tecanec, The Potato Chronicler and Rich Remer
Right, you really made me thinkg here, haven't done that much asm in zig yet, here are a few that I've used on x86, they primarily struggle with the issue of multiple return values, the below examples may not be correct, I always end up spending some time reading the GCC manuals when doing inline asm in C, it isn't always straight forwards.
I just skimmed through the discussion over at Rust users and Rust inline assembly, they seem to have similar discussions and it seems that the asm feature may not be used that much. If you really need highly optimized or complex asm wouldn't you break out to asm (or possibly llvm ir)?
I guess what we have to play with is what LLVM provides, at least as long as zig has a tight connection to it (It seems there are discussions on also supporting Cretonne in Rust according to the LLVM Weekly).
With the above proposal would I write the PPC eieio (and isync, sync) like this _ = asm volatile () -> () "eieio" (); and old style _ = asm volatile ("eieio");? This may typically be available as an intrinsic barrier, I guess. Think I read somewhere that the _ would be the same as Nims discard, it may not be needed as this asm didn't return anything.
Not sure I answered you question...
inline fn rdtsc() -> u64 {
var low: u32 = undefined;
var high: u32 = undefined;
// ouput in eax and edx, could probably movl edx, fingers x'ed...
low = asm volatile ("rdtsc" : [low] "={eax}" (-> u32));
high = asm volatile ("movl %%edx,%[high]" : [high] "=r" (-> u32));
((u64(high) << 32) | (u64(low)))
}
The above obviously is a kludge, I initially hoped to write it that more like this, it does however feel strange having to specify the outputs twice, both lhs and inside the asm outputs, with the potential of mixing the order which may be important.
inline fn rdtsc() -> u64 {
// ouput in eax and edx
var low: u32 = undefined;
var high:u32 = undefined;
low, high = asm
// no sideeffects
("rdtsc"
: [low] "={eax}" (-> u32), [high] "={edx}" (-> u32)
: // No inputs
: // No clobbers
);
((u64(high) << 32) | (u64(low)))
}
Or possibly like this, not having to undefined/zeroes/0 the output only parameters;
inline fn rdtsc() -> u64 {
// ouput in eax and edx
const (low: u32, high: u32) = asm
// no sideeffects
("rdtsc"
: [low] "={eax}" (-> u32), [high] "={edx}" (-> u32)
: // No inputs
: // No clobbers
);
((u64(high) << 32) | (u64(low)))
}
I've also tinkered with the cpuid instruction which is particularly nasty;
inline fn cpuid(f: u32) -> u32 {
// See: https://en.wikipedia.org/wiki/CPUID, there's a boatload of variations...
var id: u32 = 0;
if (f == 0) {
// Multiple outputs (as an ASCII string) which we mark as clobbered and just leave untouched
return asm volatile ("cpuid" : [id] "={eax}" (-> u32): [eax] "{eax}" (f) : "ebx", "ecx", "edx");
} else {
return asm volatile ("cpuid" : [id] "={eax}" (-> u32): [eax] "{eax}" (f));
}
}
cpuid with the proposal. I propose that instead of naming the function after the assembly instruction, we name it after the information we want. So let's choose one of the use cases, get vendor id.
Not too familiar with the x86 asm, I nicked that example from the Rust discussions, in this case rcx (and ecx i 32 bit) is a loop counter somewhat similar to ctr on Power PC. So the movq, decq, jnz drives the loop. So as long at that condition is met it probably doesn't matter. Maybe it could have been done with the loop instruction that decrements and tests at the same time.
result is both an input and an output, like if you were updating a cksum or similar where you would feed in an initial or intermediate value that you want to update.
Are you planning to support all the various architecture specific input/output/clobber constraints and indirect inputs/outputs present in LLVM?
Another avenue to go down is the MSVC way of doing inline assembly. M$ does a smart augmented assembly, where you can transparently access C/C++ variables from the assembly. An example would be a memcpy implementation:
It provides a really nice experience. However, MSVC isn't smart about the registers, so all registers used are backed up to the stack before emitting the assembly, and are then restored after the assembly. This avoids the mess of having to specify cluttered registers, but at the cost of a fair bit of performance.
The smart syntax is awesome, but it might be hard fit with a LLVM backend, if you do not want to write an entire assembler as well.
Reacted by jadon, dd, dwood, Saulius Krasuckas, Pandaa-P, February, TunifyBasic and Tim Hutt
As kiljacken says, I personally really, really enjoy the Intel syntax over GAS as D has done it (except for GDC, which is based on GCC). I'm only assuming it'll be harder to implement a MSVC-styled inline assembly feature.
The end game is we will have our own assembly syntax, like D, which will end up being compiled to llvm compatible syntax. It's just a lot of work.
I at first tried to use the Intel syntax but llvm support for it is buggy and some of the instructions are messed up to the point of having silent bugs.
Reacted by Layne Gustafson, cpli, Ethernet, Saulius Krasuckas, Robin, The Potato Chronicler and Tim Hutt
The question is "what is robust" though. It is a well known problem that string based inline asm has zero checks that it actually uses the correct clobbers etc. At least in my research I found pages discussing bugs in asm blocks due to people either misunderstanding the constraints or mistakenly forgetting about some.
The problem here of course being that they're only observable in specific scenarios, so it would occur almost randomly depending on the final register allocation and code ordering.
So one also has to ask oneself: is manual clobbering LESS or MORE likely to be correct than automatic clobbering? If one is worried about things like INT21, then perhaps start with a pessimistic clobber, allowing the user to ease the restrictions. This is something that should be discussed.
________________________________
From: Christoffer Lerno ***@***.***>
Sent: Sunday, June 18, 2023 12:22:54 AM
To: ziglang/zig ***@***.***>
Cc: eLeCtrOssSnake ***@***.***>; Mention ***@***.***>
Subject: Re: [ziglang/zig] inline assembly improvements (#215)
The question is "what is robust" though. It is a well known problem that string based inline asm has zero checks that it actually uses the correct clobbers etc. At least in my research I found pages discussing bugs in asm blocks due to people either misunderstanding the constraints or mistakenly forgetting about some.
The problem here of course being that they're only observable in specific scenarios, so it would occur almost randomly depending on the final register allocation and code ordering.
So one also has to ask oneself: is manual clobbering LESS or MORE likely to be correct than automatic clobbering? If one is worried about things like INT21, then perhaps start with a pessimistic clobber, allowing the user to ease the restrictions. This is something that should be discussed.
—
Reply to this email directly, view it on GitHub<#215 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AJSXC4QBT4DFJG5BKLSA3ETXLYN25ANCNFSM4CWYUVMQ>.
You are receiving this because you were mentioned.Message ID: ***@***.***>
instead of text asm we could perhaps have a std.asm.<arch> ? since llvm already forgoes optimization when using inline asm, by using functions and comptime we could ensure its safety
and then language-level inline asm would only be text thats generated at comptime by the functions
Reacted by Pavel Atanasov, Chris Clark, February and Yay295
instead of text asm we could perhaps have a std.asm.<arch> ? since llvm already forgoes optimization when using inline asm, by using functions and comptime we could ensure its safety
and then language-level inline asm would only be text thats generated at comptime by the functions
This immense amount of work to even make these simple intrinsics FOR EVERY ARCH, excluding adding safety. Also, what do you mean by llvm forgoes optimization for inline asm? It does everything in it's power to make the assembly block performant based on inputs, outputs, clobbers. It won't touch your asm code, for the reasons you(programmer) wrote it in the first place.
Why not just make asm blocks with a zig-like DSL? Reuse as much zig syntax as possible:
asmvolatile {
mov (rax, ecx);
}
IMO the asm syntax shouldn't be annoying or ridiculously complicated. As it currently stands, the current asm syntax definitely doesn't convey intent precisely, nor do any of the proposals that I've seen on this thread -- they all just make everything more confusing to me, and I assume it's confusing to the majority of others who try to write it, let alone read it. Though this isn't a problem unique to Zig.
@eLeCtrOssSnake You want a "robust" solution for asm syntax; many of us want an easy to read and write one too. The only way you could achieve a robust one is to write your own assembler. It'll take a lot of work, but that's inevitable anyway unless you don't want anything checking the correctness of your code and just "assuming" you're an assembly genius who always knows what they're doing. Sure, LLVM is good at optimizing asm blocks with inputs/outputs/clobber specs, but it sacrifices readability and writability and it doesn't actually check the constraint codes you use other than verifying they're correct.
Reacted by lin72h, cn, Marco Toniut, Nic Hodlofski and Rich Remer
The MSVC way is good; I know that D and Pascal do something like it. But it lacks any constraint mechanism. But that could probably be added. Point is, if we're going to stick with ridiculously unreadable inline assembly I might as well just forgo inline asm and just write a bunch of asm in a big .s file for the architecture I'm trying to support; that at least is readable and comprehensible. It doesn't seem too unreasonable to expect a similar guarantee from my programming language.
Would one even need to specify clobbers if every arch and instruction were supported? Is there a scenario where we wouldn't know which regs were clobbered?
I see a lot of newly proposed syntax to structure the inputs, outputs, and clobbers of an inline assembly. This is all in my opinion unreadable and difficult for a beginner to understand. I believe this could all be implemented reusing existing Zig syntax of anonymous structs, tuples, and enum literals:
I apologize for using a simple syscall as an example, but I do not know assembly, and I am afraid I could not write a correct string of more complex assembly. I hope it gets my idea of the syntax across. Giving the inputs types could either be done through the type of the variable passed in or another argument of the tuple. Since this uses entirely valid existing Zig syntax, this could even use a builtin instead of a keyword. I am, however, unsure of using a builtin due to the inability to use the volatile keyword. Volatility could be accomplished through a boolean flag, but I would much prefer the usage of the existing volatile keyword.
Reacted by Pavel Atanasov, Chris Clark, sreehax and Luke Wilson
@Spitz7279 I'm not exactly sure how what your proposing is any more readable than a syntax like that which I proposed previously. I mean yes that reuses (some) zig syntax but at this point why not make the syntax just automatically deduce templated arguments or something?
My proposed usage reuses all valid Zig syntax. Outside of an inline assembly block, it would be valid Zig that you could parse on your own. It also retains the comptime known string of assembly, allowing the developer to use @embed. That DSL you proposed looks like valid Zig, but wouldn't really function the same. It would function more like @cImport and @cInclude currently do, since the compiler would need to be told all of the inputs and clobbers (note that your proposed syntax lacks the ability to specify the returned registers and their types.) As far as I am aware, there are possible plans to scrap the current c import model in favor of integration with the build system. I choose to ignore all of the massive burden that it would place to create the DSL and then maintain it as new assembly instructions and registers are added. Although I am unaware of any cases of this, as I do not know assembly much at all, the DSL might not be able to sufficiently deal with the intricacies of a certain platform.
I'm new to this but I hope whatever syntax is used fixes these issues with GCC's:
volatile is a terrible name. Not at all clear what it does from the name. Can we have something like asm with_side_effects or asm(side_effects=true) or asm keep or asm ... outputs=foo, bar, side_effects or asm ... outputs=foo, bar, other. Anything but volatile!
The order of :inputs :outputs :clobbers :gotos is just way too hard to remember. Did you spot that it's actually :outputs :inputs? Please make it more obvious where the outputs and inputs are (also don't forget about asm goto).
Activity
andrewrk commentedon Nov 18, 2016
One idea:
This shuffles the syntax around and makes it more like a function call. Clobbers are extra "inputs" that don't have a name and a type. The register names are still clunky.
This proposal also operates on the assumption that all inline assembly can operate on inputs and outputs.
andrewrk commentedon Nov 18, 2016
@ofelas can I get your opinion on this proposal?
ofelas commentedon Nov 18, 2016
Right, you really made me thinkg here, haven't done that much asm in zig yet, here are a few that I've used on x86, they primarily struggle with the issue of multiple return values, the below examples may not be correct, I always end up spending some time reading the GCC manuals when doing inline asm in C, it isn't always straight forwards.
I just skimmed through the discussion over at Rust users and Rust inline assembly, they seem to have similar discussions and it seems that the asm feature may not be used that much. If you really need highly optimized or complex asm wouldn't you break out to asm (or possibly llvm ir)?
I guess what we have to play with is what LLVM provides, at least as long as zig has a tight connection to it (It seems there are discussions on also supporting Cretonne in Rust according to the LLVM Weekly).
With the above proposal would I write the PPC
eieio(andisync,sync) like this_ = asm volatile () -> () "eieio" ();and old style_ = asm volatile ("eieio");? This may typically be available as an intrinsic barrier, I guess. Think I read somewhere that the_would be the same as Nimsdiscard, it may not be needed as this asm didn't return anything.Not sure I answered you question...
The above obviously is a kludge, I initially hoped to write it that more like this, it does however feel strange having to specify the outputs twice, both lhs and inside the asm outputs, with the potential of mixing the order which may be important.
Or possibly like this, not having to undefined/zeroes/0 the output only parameters;
I've also tinkered with the
cpuidinstruction which is particularly nasty;andrewrk commentedon Nov 18, 2016
With the proposal,
rdtscwould look like this in zig:This seems like an improvement.
cpuid with the proposal. I propose that instead of naming the function after the assembly instruction, we name it after the information we want. So let's choose one of the use cases, get vendor id.
Once again
volatilenot necessary here.cpuiddoesn't have side effects, we only want to extract information from the assembly.So far, so good. Any more use cases?
ofelas commentedon Nov 18, 2016
Yes, that ain't too shabby, so with the correct input in
eaxit is;Would something like this be possible, ignoring my formatting?
andrewrk commentedon Nov 19, 2016
Ah right, nice catch.
I like putting the values of the inputs above as you did. Then we don't need them below.
Is the count arg necessary to have the movq instruction? seems like we could pass that as a register.
And then finally result should be an output instead of an input right?
So it would look like this:
This is a good example of why we should retain the constraint syntax, since we might want
{rcx}or=r.ofelas commentedon Nov 19, 2016
Not too familiar with the
x86asm, I nicked that example from the Rust discussions, in this casercx(andecxi 32 bit) is a loop counter somewhat similar toctron Power PC. So themovq,decq,jnzdrives the loop. So as long at that condition is met it probably doesn't matter. Maybe it could have been done with theloopinstruction that decrements and tests at the same time.resultis both an input and an output, like if you were updating acksumor similar where you would feed in an initial or intermediate value that you want to update.Are you planning to support all the various architecture specific input/output/clobber constraints and indirect inputs/outputs present in LLVM?
kiljacken commentedon Dec 9, 2016
Another avenue to go down is the MSVC way of doing inline assembly. M$ does a smart augmented assembly, where you can transparently access C/C++ variables from the assembly. An example would be a memcpy implementation:
It provides a really nice experience. However, MSVC isn't smart about the registers, so all registers used are backed up to the stack before emitting the assembly, and are then restored after the assembly. This avoids the mess of having to specify cluttered registers, but at the cost of a fair bit of performance.
The smart syntax is awesome, but it might be hard fit with a LLVM backend, if you do not want to write an entire assembler as well.
dd86k commentedon Oct 19, 2017
As kiljacken says, I personally really, really enjoy the Intel syntax over GAS as D has done it (except for GDC, which is based on GCC). I'm only assuming it'll be harder to implement a MSVC-styled inline assembly feature.
andrewrk commentedon Oct 19, 2017
The end game is we will have our own assembly syntax, like D, which will end up being compiled to llvm compatible syntax. It's just a lot of work.
I at first tried to use the Intel syntax but llvm support for it is buggy and some of the instructions are messed up to the point of having silent bugs.
50 remaining items
lerno commentedon Jun 17, 2023
The question is "what is robust" though. It is a well known problem that string based inline asm has zero checks that it actually uses the correct clobbers etc. At least in my research I found pages discussing bugs in asm blocks due to people either misunderstanding the constraints or mistakenly forgetting about some.
The problem here of course being that they're only observable in specific scenarios, so it would occur almost randomly depending on the final register allocation and code ordering.
So one also has to ask oneself: is manual clobbering LESS or MORE likely to be correct than automatic clobbering? If one is worried about things like INT21, then perhaps start with a pessimistic clobber, allowing the user to ease the restrictions. This is something that should be discussed.
eLeCtrOssSnake commentedon Jun 17, 2023
nektro commentedon Jun 17, 2023
instead of text asm we could perhaps have a
std.asm.<arch>? since llvm already forgoes optimization when using inline asm, by using functions and comptime we could ensure its safetyand then language-level inline asm would only be text thats generated at comptime by the functions
lerno commentedon Jun 19, 2023
@nektro Do you mean essentially making a mini DSL using compile time functions corresponding to the particular instructions?
eLeCtrOssSnake commentedon Jun 19, 2023
This immense amount of work to even make these simple intrinsics FOR EVERY ARCH, excluding adding safety. Also, what do you mean by llvm forgoes optimization for inline asm? It does everything in it's power to make the assembly block performant based on inputs, outputs, clobbers. It won't touch your asm code, for the reasons you(programmer) wrote it in the first place.
ethindp commentedon Jul 21, 2023
Why not just make asm blocks with a zig-like DSL? Reuse as much zig syntax as possible:
IMO the asm syntax shouldn't be annoying or ridiculously complicated. As it currently stands, the current asm syntax definitely doesn't convey intent precisely, nor do any of the proposals that I've seen on this thread -- they all just make everything more confusing to me, and I assume it's confusing to the majority of others who try to write it, let alone read it. Though this isn't a problem unique to Zig.
@eLeCtrOssSnake You want a "robust" solution for asm syntax; many of us want an easy to read and write one too. The only way you could achieve a robust one is to write your own assembler. It'll take a lot of work, but that's inevitable anyway unless you don't want anything checking the correctness of your code and just "assuming" you're an assembly genius who always knows what they're doing. Sure, LLVM is good at optimizing asm blocks with inputs/outputs/clobber specs, but it sacrifices readability and writability and it doesn't actually check the constraint codes you use other than verifying they're correct.
ethindp commentedon Jul 21, 2023
The MSVC way is good; I know that D and Pascal do something like it. But it lacks any constraint mechanism. But that could probably be added. Point is, if we're going to stick with ridiculously unreadable inline assembly I might as well just forgo inline asm and just write a bunch of asm in a big .s file for the architecture I'm trying to support; that at least is readable and comprehensible. It doesn't seem too unreasonable to expect a similar guarantee from my programming language.
coffeebe4code commentedon Dec 29, 2023
Would one even need to specify clobbers if every arch and instruction were supported? Is there a scenario where we wouldn't know which regs were clobbered?
Spitz7279 commentedon Jun 13, 2024
I see a lot of newly proposed syntax to structure the inputs, outputs, and clobbers of an inline assembly. This is all in my opinion unreadable and difficult for a beginner to understand. I believe this could all be implemented reusing existing Zig syntax of anonymous structs, tuples, and enum literals:
I apologize for using a simple syscall as an example, but I do not know assembly, and I am afraid I could not write a correct string of more complex assembly. I hope it gets my idea of the syntax across. Giving the inputs types could either be done through the type of the variable passed in or another argument of the tuple. Since this uses entirely valid existing Zig syntax, this could even use a builtin instead of a keyword. I am, however, unsure of using a builtin due to the inability to use the volatile keyword. Volatility could be accomplished through a boolean flag, but I would much prefer the usage of the existing volatile keyword.
ethindp commentedon Jun 14, 2024
@Spitz7279 I'm not exactly sure how what your proposing is any more readable than a syntax like that which I proposed previously. I mean yes that reuses (some) zig syntax but at this point why not make the syntax just automatically deduce templated arguments or something?
Spitz7279 commentedon Jun 14, 2024
My proposed usage reuses all valid Zig syntax. Outside of an inline assembly block, it would be valid Zig that you could parse on your own. It also retains the comptime known string of assembly, allowing the developer to use
@embed. That DSL you proposed looks like valid Zig, but wouldn't really function the same. It would function more like@cImportand@cIncludecurrently do, since the compiler would need to be told all of the inputs and clobbers (note that your proposed syntax lacks the ability to specify the returned registers and their types.) As far as I am aware, there are possible plans to scrap the current c import model in favor of integration with the build system. I choose to ignore all of the massive burden that it would place to create the DSL and then maintain it as new assembly instructions and registers are added. Although I am unaware of any cases of this, as I do not know assembly much at all, the DSL might not be able to sufficiently deal with the intricacies of a certain platform.Timmmm commentedon Feb 12, 2025
I'm new to this but I hope whatever syntax is used fixes these issues with GCC's:
volatileis a terrible name. Not at all clear what it does from the name. Can we have something likeasm with_side_effectsorasm(side_effects=true)orasm keeporasm ... outputs=foo, bar, side_effectsorasm ... outputs=foo, bar, other. Anything butvolatile!:inputs :outputs :clobbers :gotosis just way too hard to remember. Did you spot that it's actually:outputs :inputs? Please make it more obvious where the outputs and inputs are (also don't forget aboutasm goto).Good luck!