4 ms·
> AT&T bakes the width into the mnemonic (movb, movw, movl, movq [...] Intel’s syntax is to prefix the memory operand with byte, word, dword, or qword, but Odin
by AshamedCaptain 1mo ago
> AT&T bakes the width into the mnemonic (movb, movw, movl, movq [...] Intel’s syntax is to prefix the memory operand with byte, word, dword, or qword, but Odin’s just uses the Odin type system directly.
In GAS you can skip the width suffix from the mnemonic, and in most Intel assemblers you can skip the memory type operators like byte. They happily guess it from the operands. The problem is that on x86 (but also other ISAs, even if to a lower extent) the different operand sizes have a lot of side effects, which is why everyone just makes the operand size explicit, up to the point that apparently the author/LLM believes that it is mandatory to specify them.
This kind of defeats the headline of the article...
Tomorrow you need to pass a 128 bit int into two registers and your fancy syntax then also becomes a messy bunch of hacks. This is why everyone's inline assembly syntax looks like that, because they want to cover the weird cases (gcc's one is almost like an history book). You're normally using inline assembly for when you have some ridiculous corner case, if not, then what you ought to use is more akin to intrinsics...
Also it forgets Watcom C, which does have a complete but messy syntax for inline assembly (which combines nicely with its ability to specify really weird calling conventions).
- winocm 1mo agoOh man, #pragma aux.
- WalterBright 1mo agoZortech had a complete inline assembler in the 80's. It's now in the D compiler!
- 10000truths 1mo ago> Tomorrow you need to pass a 128 bit int into two registers and your fancy syntax then also becomes a messy bunch of hacks. There are no 128-bit integer registers in x64 or arm64 or riscv64. There are operations that represent 128-bit scalar operands/results by storing the top and bottom halves in two 64-bit registers. From what I can gather, it would look something like this in Odin for x64: my_asm_mul :: asm(a: u64, b: u64) -> (c, d: u64) [ a -> d = %rax, c = %rdx, ] { mul b } my_mul :: proc(a: u64, b: u64) -> u128 { hi, lo := my_asm_mul(a, b) result := (u128(hi) << 64) | u128(lo) return result }
- bananaboy 1mo agoI love the Watcom C inline assembler. I use it frequently in my retro projects!
- gingerBill 1mo ago> the different operand sizes have a lot of side effects Which we have massive tables for each form which track those side effects and clobbering information too. > author/LLM I am the author, and not an LLM. > Tomorrow you need to pass a 128 bit int into two registers Okay? There are no 128-bit integer registers on AMD64, ARM64, nor RISCV-64. So I have no idea what you are on about. And note they are templates, so if you want 128-bit integer support, you can just wrap that template in a procedure and handle the behaviour yourself.
- camel-cdr 1mo agoI really like what you are doing here, the state of inline assembly is a similar travesty to the state of guided codegen/autovec. On concern I have is how this maps to ARM64 syntax, because ARM64 is massively overloading all mnemonics. For example: ld1d z0.h, p0/z, [x1, x2, lsl 3] ld1d z0.h, p0/z, [x1, z0.h, lsl 3] Have extremely different performance characteristics, yet would map to the same code: ld1d dst, p0/z, [base + idx<<3] Imo this makes reading the assembly quite bothersome. I'm already not a fan of ARM64 doing the mnemonic overloading, but at least you can figure out the operation by looking at the same line further to the right. Also, maybe I missed it, but how are you dealing with things like the /z modifier, pre/post-increment load/store and load pair? Or things like TBL/ST4/LD4? Oh and how are the types going to work for RVV, where the type can't be determined at compile-time in all situations?
- gingerBill 1mo agoI haven't fully thought out that syntax yet, but it's a problem with AVX-512 in terms of its predicate operands too. My hunch would be the following: sve_ld1d_scalar :: asm(base: [^]u64, idx: i64, #mask pred: u16) -> (dst: #simd[vscale * 4]f32) [ dst = %z0, pred = #predicate(indirect=zeroing) %p0, dsth: u16 = dst, ] { ld1d dsth, pred, [base + idx<<3] } sve_ld1d_gather :: asm(base: [^]u64, #mask pred: u16) -> (dst: #simd[vscale * 4]f32) [ dst = %z0, pred = #predicate(indirect=zeroing) %p0, dsth: u16 = dst, ] { ld1d dsth, pred, [base + dsth<<3] } So the parameter is marked as a predicate with zeroing or whatever, and then `pred` is just a normal operand as the binding section specifies everything. This is not current behaviour yet but it I am considering it when I need to specify this for even AVX-512 and RISC-V behaviour (which has multiple different possibilities).