52 comments

[ 1.7 ms ] story [ 113 ms ] thread
Please, next time add a description to the title, like " - BSD assembly language".
Actually, Int 80h is a op-code in x86 assembly language inherited from the Intel 8008 and 8080 8-bit processors. The post announces a Free BSD assembly language tutorial for the x86.
Note that for modern x86 processors there are the fast systemcall instructions syscall/sysret (AMD64) and the older sysenter/sysexit. In contrast to int 80h, those instructions do not bear the full interrupt overhead. I couldn't find this info anywhere on the website.

See e.g. [1] for a more comprehensive overview concerning the handling of system calls on Linux.

[1] http://blog.packagecloud.io/eng/2016/04/05/the-definitive-gu...

(comment deleted)
> Secondly, there is a common myth among programmers that assembly language is very hard to use and that it takes much longer to code the same program in assembly language than in a HLL. [...] An experienced assembly language programmer can and does code as fast in assembly language as an experienced C programmer does in C. It is simply a matter of familiarity.

I don't really understand this argument. If a few lines of c can generate dozens of lines of assembly, how can it not be faster to have the compiler write assembly for you?

Yes, and C isn't even particularly high level.
Macro assemblers. It would be a pain without macros.
Also when writing C you don't have to be 'an experienced assembly programmer' to write performant and portable code. That is one of the driving points of a HLL: you don't need to know as much minutiae to write good maintainable code. I understand getting into assembly for the purpose of learning or squeezing out that last bit of performance, but it isn't a general purpose programming language anymore and shouldn't be a default choice over a HLL for most applications.
Because programmers are not typists? Translating a thought into a programming concept usually takes more time than typing it out, even in assembly.
I dunno. I think by any measure being able to write "x / 3" will gain you productivity vs. "imul eax,2863311531; shr eax,1" (with the trip to the Hacker's Delight page necessary to look up the magic number).
Or just use DIV/FDIV/FIDIV.
DIV is way slower than MUL, so unless you need the extra precision or the result of the modulo, don't use DIV.
We were talking about readability, not speed. (Of course, you could also just write a macro.)
I came to post this exact thing. I guess the article is actually quite old. It talks people "migrating from MS-DOS to Unix/BSD" among other things, which sounds more than a little odd to be even from this millennium.
"It is simply a matter of familiarity."

Your comment focuses on how fast the compiler generates lines of assembly, after the programmer has constructed a solution.

But unless I have misunderstood the author was focusing on how fast the programmer can construct a solution.

The final step of depressing keys to input the solution into the computer can be sped up by using libraries and perhaps macros.

It's also possible to write programs ("code generators") that generate lines of assembly based on a "template". And that "template" need not be written in C or any other popular computer language.

It could be some "DSL", a shorthand for assembly, that the programmer creates for himself.

djb's qhasm might be considered an example.

While it may be generally true that C requires less key punching than assembly (certainly it requires fewer "lines"), this does not mean constructing a solution in C necessarily comes faster than constructing one in assembly... if the programmer is familiar and efficient working with assembly.

Something like "(x3+(y/20)5)" will generate a few lines of assembly (about 3 to 5 in this case, assuming registers are pre-loaded). But usually no, there won't be much more code. For example "while(1){..." equals just one instruction, and "for(x=10, x<0, x--){..." is 2-3 instructions.

edit: idk how to put a asterisk up there in the example above

I have no idea where people get the idea that one line of C equals 10 lines of asm.

PS Assembly is not hard at all, and is good to understand/read assembly when coding C. The only thing is that it is a bit rough to start coding in it (no good tutorials is one of the two-three difficulties).

> I have no idea where people get the idea that one line of C equals 10 lines of asm.

Because coding in asm takes more than just the raw instructions.

x86 doesn't have many registers so you're shifting around stuff across registers and memory all the time, not to mention stack setup/teardown and the error checking (e.g. div0, overflow) you are supposed to do after operations.

C doesn't check for overflows..

I also disagree with "shifting around stuff across registers and memory all the time". An "average" function doesn't really have all that many variables and x86 has 8 GP registers (16 on amd64). You don't even have to declare variables, you just take a register (and/or a location in memory) and use it as you wish.

I'm now sitting here thinking what can be done in C that would end up being a lot of instructions, and i can't think of anything. Going over an array does not, neither does recursion (recursion is just syntactic sugar in the end). What else.. i can't think of anything. Macros ? But macros are cheating in this comparison (not to mention that the flat assembler has actual macros, that are extremely powerful)

C doesn't check for overflows..

This is one of the things where (x86) Asm is definitely easier: divide overflows naturally cause an exception which will get handled automatically, and add/subtract/multiply overflows can be checked with one extra (JO or JC) instruction. In C, this is all undefined behaviour.

I'm now sitting here thinking what can be done in C that would end up being a lot of instructions, and i can't think of anything.

Crypto algorithms come to mind, but like you said, macros can take care of that easily... and on this page is an AES-256 CFB implementation in Asm that is around the same size of source as others in C (but much smaller binary):

http://reimagery.com/fsfd/encrypt.htm

Its a common trope expressed by bitter people. In general this happens when you don't keep up with technology trends, you just don't understand why somebody prefers one thing over another when both, peripherally, yield the same product. Its also obviously not true.
"But times have changed. For better or worse, the absolute majority of computers in existence today are built with the Intel family of microprocessors. Thus, portability is much less of a concern."

I'm guessing this article is somewhat out of date. The systems where performance matters most - those most likely to be constrained in some way, where coding in assembly would yield the most benefit - are mobile platforms, which invariably means ARM not x86.

Per the Internet Archive, this page is at least fifteen years old - it looked like this as of the first snapshot taken on February 1, 2001 (https://web.archive.org/web/20010201152900/http://int80h.org...). A lot has changed in computing since then.

ARM became dominant in mobile computing, which was barely a glimmer back in 2001. x86-64 became commonplace. Just targeting Intel and ARM processors now necessitates writing code for four architectures: x86, x86-64, AArch32 and AArch64 for a full range of compatibility.

While MS-DOS syscall numbers were documented and often _required_ assembly to use, the opposite is true on modern Windows - Windows system call numbers are undocumented and change from release to release, requiring programmers to link against Kernel32.dll or equivalent to stably call into the OS.

Compiler technology has advanced significantly, and processor manufacturers now often optimize towards patterns employed by compilers rather than by humans (case-in-point: the x86 "loop" instruction, while extremely convenient for handwritten code, is significantly less performant than a cmp-jmp loop).

Code has gotten more and more complex. Whether this is a good thing or bad, the reality is that hundreds of millions of lines of assembly would be required to replicate complex modern programs like web browsers - projects of that scale will always require powerful HLLs to manage abstraction, something that assembly does not and cannot provide on its own.

View this page as what it is - a historical artifact. Although assembly programming definitely still has its uses, the arguments this page makes in favor of it are largely no longer relevant.

I think programming in assembly really teaches you how a computer work. That's its main benefit imho.
MS-DOS was written for only one processor.

UNIX kernels could run on a variety of processors, including ARM; UNIX was a relative latecomer to x86.

As for Windows kernels, both then and now, UNIX kernels are open source and do not deliberately obscure system calls.

It's not unreasonable for someone to argue the benefits in writing programs for UNIX.

"View this page as what it is..."

It's one of the few pages written about UNIX assembly. That's how I view it. Years later it was put into The FreeBSD Handbook.

Performant is not a word. Use a word more specific to your meaning, perhaps "efficient" in this context.
Well, what once was not, certainly now appears to be. It's been used so much, I think it's fair to say it has become a word. Moans and koans to contrary will be accepted, however. And hey, if you're lucky its existence will be short-lived.
"Performant" is a perfectly fine word. You might even call it cromulent.
I found my self thinking that performant is definitely a word, and it is, in French.
Performant is a perfectly valid word which has been part of software engineering since at least the 1990s.

"... aiming to provide performant tools ..." (1995) - https://books.google.com/books?id=uZjYdk4bQgkC&pg=PA1&dq=per...

definitions for different aspects of 'peformant' (1998) - https://books.google.se/books?id=FYkmwYKBoCwC&pg=PA235&dq=%2...

"The network and hierarchical data models, introduced in the 1960's, provide a very performant data organization for well-defined, update-intensive transaction processing of large volumes of data at the cost of conceptual complexity" (1993) - https://books.google.com/books?id=Ro3zjjTNE9UC&pg=PA327&dq=p...

Here's the n-grams trend. https://books.google.com/ngrams/graph?content=performant&yea... .

There are many domain-specific terms which have not yet made it to general dictionaries. For example, from your comments, I see you used the word 'multicast', This is not in Merriam-Webster, at http://www.merriam-webster.com/dictionary/suggestions/multic... . What makes it a word but not performant? Is "zero-copy" a word? "smarthost"? "type-punning"? "upstream"?

Those example words have precise meaning, "performant" does not. Engineers should communicate using precise language, like other technical disciplines.
I think that's what bugs me about "performant" --- it feels like vague buzzwordy management-speak, because there's already a well-understood word for the same concept: "fast". Ditto for "lightweight"; "small" gets the point across clearly, and if it's both fast and small, it's "efficient".

Software can be big or small, fast or slow, efficient or inefficient (for combinations of size and speed.) I don't think we need any more adjectives beyond those.

Instead of "significantly less performant", you could just say "much slower". (Note that LOOP was much slower on older Intel CPUs, but was the same as the longer sequence on AMD's. It would not surprise me if newer Intel ones now decode LOOP into the same sequence of uops. In any case, unless the loop is a tiny one, what happens in the body is likely to make a much more significant difference, especially if cache misses are involved.)

Some interesting reading on why LOOP was slower on Intel CPUs --- because Windows 95's timing loop used it (observe the lack of "performant" in any of those posts): https://groups.google.com/d/topic/alt.windows98/liuMAIctHRg

But do you think it's not a word?

Also, I think performant is often used as a synonym for "fast enough", which is slightly different than "fast."

What's imprecise about: "Performant: 1) meeting the objective, often applied to a performance objective, 2) productive"?

In looking around just now, economics seems to prefer the second definition, which is why I added it. All of the uses I found are compatible with this definition.

What's the definition of "object oriented" again?

A vague word is useful when you are trying to convey a vague concept, in much the same way as it would be hard to convey the idea that you know that some metric is between 20% and 40%, if you are not allowed to be vague and are required to arbitrarily pick some number like 38.51% and say that one instead
I basically cringe and stop taking an author seriously when they use the non-word "performant", but I've kind of given up pointing it out. Brother, we're losing the fight to people who think it's perfectly cromulent to embetter the language by innoventing their own words.
What defines a language anyways? Languages evolve. That's the beauty of them: They aren't set in stone. Who knows? Maybe in a few decades, "your" will actually have a definition added for "you are" instead of the (currently correct) "you're". "Computer" used to refer to an actual person. "Caught" and "cot" used to be pronounced differently; now they are slowly merging.[0]

Languages evolve. Whether for better or worse is up to the reader. If it weren't for evolving languages, we'd all still be writing like Shakespeare.

[0]: https://en.wikipedia.org/wiki/Phonological_history_of_Englis...

Where do you think language comes from anyway? If we'd adopted prescriptivist ideas like this early enough, we'd still be grunting and pointing
It is a word. Performant means "performing well". I wonder if can we apply this kind of adjective-forming to other verbs, e.g. by taking the gerund form, removing "-ing" and adding the suffix "-ant".

For example:

- observing => observant =?= observing well (yes, exists in this meaning)

- informing => informant =?= informing well (does not exist as adjective, but could in the future?)

- thinking => thinkant =?= thinking well (does not exist at all, but could in the future?)

"Performs well" has no concrete meaning. It almost always requires a second question for clarification, e.g. "What does good performance specifically mean here?" Or "performs well compared to what?"

Scientist, doctors, lawyers, and other engineering disciplines use precise words. Software engineers should too.

As more and more people use "performant" fewer and fewer people will understand what good performance actually means. I wouldn't be surprised if this encourages a culture of wasteful premature optimization.

Whether this is a good thing or bad, the reality is that hundreds of millions of lines of assembly would be required to replicate complex modern programs like web browsers - projects of that scale will always require powerful HLLs to manage abstraction, something that assembly does not and cannot provide on its own.

On the other hand, when using Asm you can often leverage techniques not possible in HLLs to avoid complexity and abstraction. This is a fact which seems not commonly known, even in CS courses involving Asm, as those tend to be more about inspecting compiler output and the few "write in Asm" exercises are mimicing code that a compiler would generate, which IMHO misses the whole point of writing in Asm.

Here is an operating system containing many nontrivial applications, all written in 100% Asm:

http://www.menuetos.net/

Among other things, it contains a minimal yet functional web browser (around the same level of minimalism as Dillo or NetSurf, i.e. no JS or fancy CSS), and the whole package fits on a single floppy disk.

An entire OS with kernel, drivers, and applications in total can be smaller than a "Hello World" application in a "modern" programming language (which depends on countless other external libraries too, including an OS several GB or more.) It really makes you think.

Menuet OS could be the same size if it was written in C or C++. It's small because it's minimal and uses few dependencies.
> Here is an operating system containing many nontrivial applications, all written in 100% Asm:

Here's a small snippet I found from draw.asm:

    add   r9  , [copyxe]
    add   r9  , [copyxe]
    add   r9  , [copyxe]
    sub   r9  , [copyx]
    sub   r9  , [copyx]
    sub   r9  , [copyx]
    add   r10 , [sizex]
    add   r10 , [sizex]
    add   r10 , [sizex]
    add   r14 , 1
72 bytes.

Here's what GCC generates with -Os for the same code:

    imul rax,qword ptr [copyxe],3
    inc r14
    add r9,rax
    imul rax,qword ptr [sizex],3
    add r10,rax
    imul rax,qword ptr [copyx],-3
    add r9,rax
36 bytes.

As a bonus, GCC with -O2:

    mov rax,[copyxe]
    add r14,1
    lea rax,[rax+rax*2]
    add r9,rax
    mov rax,[sizex]
    lea rax,[rax+rax*2]
    add r10,rax
    mov rax,[copyx]
    mov rcx,rax
    neg rcx
    lea rax,[rax+rcx*4]   ; clever!
    add r9,rax
52 bytes.

So, not only does GCC hugely beat this snippet of handwritten Menuet assembly in code size, it can even do so handily while folding all the repeated additions into LEA and eliminating most of the loads.

This is an excellent example of why you shouldn't write in assembler.

An assembler programmer at full attention can beat a compiler at most operations. That said, the average level of attention a compiler can give over the whole program is much greater than an assembly programmer. When performance is the ultimate goal it makes sense to only drop into assembler for your inner loop.

However assembly programmers will also instinctively avoid things that result in massive amounts of code because they have to manually write every line of it. Compared to C++ where templates are effectively a code generation meta-language. C makes you work a lot harder to generate huge amounts of machine code - and its binaries tend to be smaller because of it.

72 bytes.

I don't think that's correct; assuming RIP-relative for all the global variables, the first 9 instructions are 7 bytes each and the last one 4, giving a total of 67 bytes.

You can cherry-pick examples all you want, but I think this is a great example of why you should use Asm: It's not even trying to be optimised, and it still somehow manages to be smaller overall!

Try to optimise it, however, and you can definitely beat GCC...

    lea rcx, [copyxe]
    imul rax, [rcx], 3
    add r9, rax
    imul rax, [rcx+copyx-copyxe], -3
    add r9, rax
    imul rax, [rcx+sizex-copyxe], 3
    add r10, rax
    inc r14
...with 32 bytes. This human gives, for "-O2", the following 41-byte snippet:

    lea rcx, [copyxe]
    mov rax, [rcx]
    lea rax, [rax+rax*2]
    add r9, rax
    mov rax, [rcx+copyx-copyxe]
    lea rax, [rax+rax*2]
    sub r9, rax
    mov rax, [rcx+sizex-copyxe]
    lea rax, [rax+rax*2]
    add r10, rax
    inc r14
GCC may be clever to turn (-3) * x into x - 4 * x, but not clever enough to realise that turning 3 * x into x + 2 * x like it did for the other two "statements" and a subtraction would be shorter and faster because it means one less operation on the critical path.
I love this! I've been staring at it (on and off) most of the day, trying to understand what's going on. I see that the compiler has changed the order of execution of "add r14, 1". Would that be to avoid stalling the pipeline due to several rax-intensive instructions bunched together ("data hazard")? Would you be able to elaborate more on what's going on with the code and why it's smart?
r14 is not used at all in that snippet except for that one instruction; its increment can thus be put anywhere between the others, and where the compiler put it is as arbitrary as what a human would do in the absence of any further information. In any OoO architecture (for Intel x86, everything except early Atoms and the tiny embedded cores) the CPU will reorder instructions anyway and can look ahead several dozen instructions:

http://blog.stuffedcow.net/2013/05/measuring-rob-capacity/

The repeated use of rax is also no problem because of register renaming. Despite two dependency chains specifying rax, the CPU can detect that they're independent and assign different physical registers for rax, allowing them to execute in parallel.

Sorry to rant for the nth time about this, but why does Chrome offer you to make this wonderful, CSS-free, content-oriented document "mobile friendly"? That is, why ask for permission? Can't browsers render documents as they see fit when there are no CSS rules? As far as I can tell, the only difference once you accept is more generous margins as well as a gray background for <code> elements... I'd say it does look more readable, maybe even on the desktop. Chrome, Firfox, IE, Safari, et al: be bold (or otherwise styled) with CSS-free documents!
I remember coming across this article back then, in 2001, or maybe it was even 2000. I found it fascinating at the time and wrote lots of little programs that talk to the syscall interface directly. It was a good learning experience.

I think the emphasis on the 0x80 interrupt was to draw parallels to the old DOS calls - int 21h - and make a point that nothing technically stops you from doing the equivalent on Unixy system. (A few years after this the sysenter thing came along and so now int 80h is a legacy path..)

Sadly a lost art, all of this. Talking about this is a great way to get blank stares from many people.