A fundamental introduction to x86 assembly programming
71–78 of 78 posts
Re: A fundamental introduction to x86 assembly programming
#72Earlier quoted context omitted.
FST/FLD can store/load full 80 bits if you need such precision. No problem whatsoever.
I've never seen anyone store 10 byte IEEE-754 values in memory. People store doubles, and people get upset when compiler optimizations (like when to spill from registers to memory and whether to use one or two instructions for multiply-and-add) change not just the performance but also the result of computations.
But you can, if you are after precision as the OP apparently was.
I'm not sure what this c-word is doing in your post, I thought we were talking assembly, but as far as c-things go, at least gcc and clang represent long double as 80b extended precision on x86.
So, for example, compiling this beauty (which is too large for x87 stack):
long double a[64], b[64], c[64], d[64];
// load a,b,c,d from somewhere
long double x =
((((((((a[0]+a[1])+(a[2]+a[3]))+((a[4]+a[5])+(a[6]+a[7])))
+(((a[8]+a[9])+(a[10]+a[11]))+((a[12]+a[13])+(a[14]+a[15]))))
+((((a[16]+a[17])+(a[18]+a[19]))+((a[20]+a[21])+(a[22]+a[23])))
+(((a[24]+a[25])+(a[26]+a[27]))+((a[28]+a[29])+(a[30]+a[31])))))
+(((((a[32]+a[33])+(a[34]+a[35]))+((a[36]+a[37])+(a[38]+a[39])))
+(((a[40]+a[41])+(a[42]+a[43]))+((a[44]+a[45])+(a[46]+a[47]))))
+((((a[48]+a[49])+(a[50]+a[51]))+((a[52]+a[53])+(a[54]+a[55])))
+(((a[56]+a[57])+(a[58]+a[59]))+((a[60]+a[61])+(a[62]+a[63]))))))
+((((((b[0]+b[1])+(b[2]+b[3]))+((b[4]+b[5])+(b[6]+b[7])))
+(((b[8]+b[9])+(b[10]+b[11]))+((b[12]+b[13])+(b[14]+b[15]))))
+((((b[16]+b[17])+(b[18]+b[19]))+((b[20]+b[21])+(b[22]+b[23])))
+(((b[24]+b[25])+(b[26]+b[27]))+((b[28]+b[29])+(b[30]+b[31])))))
+(((((b[32]+b[33])+(b[34]+b[35]))+((b[36]+b[37])+(b[38]+b[39])))
+(((b[40]+b[41])+(b[42]+b[43]))+((b[44]+b[45])+(b[46]+b[47]))))
+((((b[48]+b[49])+(b[50]+b[51]))+((b[52]+b[53])+(b[54]+b[55])))
+(((b[56]+b[57])+(b[58]+b[59]))+((b[60]+b[61])+(b[62]+b[63])))))))
+(((((((c[0]+c[1])+(c[2]+c[3]))+((c[4]+c[5])+(c[6]+c[7])))
+(((c[8]+c[9])+(c[10]+c[11]))+((c[12]+c[13])+(c[14]+c[15]))))
+((((c[16]+c[17])+(c[18]+c[19]))+((c[20]+c[21])+(c[22]+c[23])))
+(((c[24]+c[25])+(c[26]+c[27]))+((c[28]+c[29])+(c[30]+c[31])))))
+(((((c[32]+c[33])+(c[34]+c[35]))+((c[36]+c[37])+(c[38]+c[39])))
+(((c[40]+c[41])+(c[42]+c[43]))+((c[44]+c[45])+(c[46]+c[47]))))
+((((c[48]+c[49])+(c[50]+c[51]))+((c[52]+c[53])+(c[54]+c[55])))
+(((c[56]+c[57])+(c[58]+c[59]))+((c[60]+c[61])+(c[62]+c[63]))))))
+((((((d[0]+d[1])+(d[2]+d[3]))+((d[4]+d[5])+(d[6]+d[7])))
+(((d[8]+d[9])+(d[10]+d[11]))+((d[12]+d[13])+(d[14]+d[15]))))
+((((d[16]+d[17])+(d[18]+d[19]))+((d[20]+d[21])+(d[22]+d[23])))
+(((d[24]+d[25])+(d[26]+d[27]))+((d[28]+d[29])+(d[30]+d[31])))))
+(((((d[32]+d[33])+(d[34]+d[35]))+((d[36]+d[37])+(d[38]+d[39])))
+(((d[40]+d[41])+(d[42]+d[43]))+((d[44]+d[45])+(d[46]+d[47]))))
+((((d[48]+d[49])+(d[50]+d[51]))+((d[52]+d[53])+(d[54]+d[55])))
+(((d[56]+d[57])+(d[58]+d[59]))+((d[60]+d[61])+(d[62]+d[63]))))))))
;
produces only 80b spills (fstpt in GNU syntax): $ objdump -d fpmonster |grep fst
400457: db 7c 1c 10 fstpt 0x10(%rsp,%rbx,1)
400462: db bc 1c 10 04 00 00 fstpt 0x410(%rsp,%rbx,1)
400470: db bc 1c 10 08 00 00 fstpt 0x810(%rsp,%rbx,1)
40047e: db bc 1c 10 0c 00 00 fstpt 0xc10(%rsp,%rbx,1)
400b47: db 7c 24 10 fstpt 0x10(%rsp)
400d91: db 3c 24 fstpt (%rsp)
Clearly, extended precision can be done right both in C and raw assembly.> People store doubles, and people get upset
That's their fault :) and another story altogether. For reproducible low precision, indeed SSE is the way to go.
Re: A fundamental introduction to x86 assembly programming
#73Earlier quoted context omitted.
eax is the accumulator register, ebx the base register, ecx the counter register, edx the data register, esi is the source index, edi is the destination index, ebp is the base pointer, and esp is the stack pointer. When I originally wrote four general purpose registers, I had eax, ebx, ecx and edx in mind, but after fully listing them above, I revise my earlier statement: the x86 assembler has two general purpose reg…
6502 has only accumulator anyways. X and Y are not general purpose. 68k is pretty nice, d0-d7 registers are indeed interchangeable. Of course a0-a7 are just for addressing, I think a7 was usually stack pointer. SPARC I've never programmed, so no comments about it. I've written x86 code in the past (20 years ago) using all 8 registers for general purpose task -- yes, even ESP. It was faster that way to implement a tex…
Re: A fundamental introduction to x86 assembly programming
#74Earlier quoted context omitted.
6502 has only accumulator anyways. X and Y are not general purpose. 68k is pretty nice, d0-d7 registers are indeed interchangeable. Of course a0-a7 are just for addressing, I think a7 was usually stack pointer. SPARC I've never programmed, so no comments about it. I've written x86 code in the past (20 years ago) using all 8 registers for general purpose task -- yes, even ESP. It was faster that way to implement a tex…
Indeed. While repurposing ESP might be rightfully considered ugly, repurposing EBP is quite common and EBX, ECX, ESI, EDI pretty much are general purpose because nobody has been using them for their fixed functions for two decades.
I agree that stosb/rep would probably be confined to memory management, and saving/restoring register around such ops isn't the end of the world. Not sure about movsb -- I suppose if you're copying enough data, store/restore is going to be negligible overhead in terms of speed, but if you're actually trying to write clear code, it would certainly be easier to not have to worry about the book keeping?
Re: A fundamental introduction to x86 assembly programming
#75Earlier quoted context omitted.
Indeed. While repurposing ESP might be rightfully considered ugly, repurposing EBP is quite common and EBX, ECX, ESI, EDI pretty much are general purpose because nobody has been using them for their fixed functions for two decades.
Is there something wrong with loop and friends? I agree that stosb / rep would probably be confined to memory management, and saving/restoring register around such ops isn't the end of the world. Not sure about movsb -- I suppose if you're copying enough data, store/restore is going to be negligible overhead in terms of speed, but if you're actually trying to write clear code, it would certainly be easier to not have…
Currently the fastest way to memset large chunks of memory is probably to use SSE or AVX. I'd guess this is what gets generated if compiler target arch allows.
With SSE/AVX you also have an option to use non-temporal moves to avoid polluting caches. This might have a negative impact on any memset micro-benchmark [1], but significantly help any concurrently executing memory bound CPU cores.
Properly aligned (cache line 64-byte boundary) you might be able to avoid read-for-ownership as well, further reducing memory bus traffic.
So most use of rep-prefix might be pointless, unless you can accept the performance hit.
[1]: Just like micro-benchmarking any other resource constrained operation. Micro-benchmarks can give you very wrong idea of what is best for the system as a whole.
Re: A fundamental introduction to x86 assembly programming
#76Earlier quoted context omitted.
Indeed. While repurposing ESP might be rightfully considered ugly, repurposing EBP is quite common and EBX, ECX, ESI, EDI pretty much are general purpose because nobody has been using them for their fixed functions for two decades.
Is there something wrong with loop and friends? I agree that stosb / rep would probably be confined to memory management, and saving/restoring register around such ops isn't the end of the world. Not sure about movsb -- I suppose if you're copying enough data, store/restore is going to be negligible overhead in terms of speed, but if you're actually trying to write clear code, it would certainly be easier to not have…
The CPUs have lots of duplicated logic to process many instructions in parallel and, on "friendly" code, can sustain average throughput of 2 or more instructions per clock cycle, provided that the instructions are simple enough.
The end result is that a loop made with normal adds, cmps and jnes outperforms those dedicated looping instructions.
They are only used by compilers when optimizing for code size and maybe by people who want concise hand written assembly, though I'm not sure why wouldn't they just use C in such case.
See "Software Optimization Guides" released by AMD/Intel for more info.
Re: A fundamental introduction to x86 assembly programming
#77Earlier quoted context omitted.
And once you start reading the docs on the more efficient but far less consistent 64-bit calling convention, you may find yourself choosing words to describe it other than the "improved" that this author opted for.
I take it you're a fan of the plan9 calling convention (f. ex.: all arguments and return value(s) are on the stack)? Curiously, it doesn't actually appear all that ineffecient. Go uses it AFAIK. I wonder whether anyone has studied it. I also wonder whether gccgo uses that convention or defaults to SysV x64.
I don't think of it in terms of like-vs-dislike. My observation is that it's a difficult thing to get right without a compiler, and thus avoided for introductory material.
As far as I am aware, storing values in a register requires fewer instructions. However, I have never personally confirmed the performance difference of this calling convention.
Handling of all calling conventions is one of the many things that I am personally much happier leaving up to GCC and LLVM in practice.
Re: A fundamental introduction to x86 assembly programming
#78If you want to learn x86 assembly, I recommend one of my favorite books Programming From The Ground Up: http://savannah.nongnu.org/projects/pgubook/ (free pdf!) This is a practical book and teaches assembly programming on Linux. Author Jonathan Bartlett wrote this book because he was frustrated to no end with the existing books. At the end of them he could still ask, "How does the computer really work?" and not have…
This book was what made C click for me (in the few chapters I digested way back when). I actually stopped reading twice because I suddenly understood something that had blocked my progress in C, and went on my way for a year or two until I decided to pick the book up again. For a quick idea of what ASM can look like if you build up the foundations step-by-step, and understand what you're working with: .include "recor…
The function should read in a loop, to fulfill its contract (read a record).
#include
ssize_t read_record(int fd, void* buf, size_t record_size, size_t* written) {
for (*written = 0; *written