Assembly is COOL. It's a family of programming languages where each statement is "assembled" by an "assembler" into machine code for a target CPU. Because it's unique for each CPU ISA, and sometimes having different syntaxes even for the same ISA, there's no 1 "assembly language". There's ARM assembly, MIPS assembly, SPARC assembly, x86 assembly, etc. But all should share the same fundamentals, like mnemonics, registers, and memory addressing, and all assemblers should have similar directives and such. Programming in assembly is also very OS-specific. To even see what you're doing in it, you'll have to interact with the OS, and that requires OS-specific system calls... add in the fact that there's multiple assemblers you can use, and now it's tricky.

But even when not getting into the OS-specific stuff, or much into assembler-specific stuff, there's a lot to learn. There's probably more to learn there than how to make it interact with the OS. For printing something to the screen, or accepting input, etc, learn what system calls are and how to put them into the code. Also figure out the assembler's syntax for the different sections of code and whatnot, and/or the ELF spec. But I don't wanna get bogged down too much into typing all about that. Even trickier because I use Linux and you likely use Windows... so idk I'll just avoid it a lot...

But again, there's enough in the x86_64 architecture as-is without complicating/entangling it with OS-specific quirks. I've had dreams about x86 before! How absurd is that? In my like 4 years of messing around with programming in other languages, I've never dreamed about them. But there's so much more substance in x86_64, I guess. So cool.

A prior knowledge of programming is fairly assumed. Knowledge of computers (mostly just the CPU, how there's different CPU architectures, and RAM) is also helpful.

X86_64 ASSEMBLY!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

First of all, x86_64 is a 64-bit extension of x86. It's very similiar to it, because backwards-compatability was very important when they made it. Sometimes it's called x64, amd64, or intel 64. Something like that. Fun Fact: x86_64 harkens all the way back to Intel's 8086 that came out in like the 70s! Try not to get tired of spammy terminology like that. x86 and x86_64 assembly are similiar enough that you know one, then you pretty much know the other and vice-versa, minus the whole "32-bit vs 64-bit" thing. Maybe viewing them as the same language would be helpful, but some people might get pedantic about that. I dunno. I try to always call it x86_64 on this website, but 9/10 times you could replace it with 'x86' and it'd mean the same thing when not talking about 64-bit-exclusive stuff.

registers

Like probably all other CPU architectures, x86_64 revolves around registers. You might be familiar with the term 'register' if you were curious about obscure stuff in C and saw that there's a register keyword. In C, the register keyword makes a variable a "register variable". A register variable:

Of course, now everything will tell you that modern compilers do such a good job of optimizing C code, there's no reason for a programmer to explicitly define something as a register variable (probably wouldn't get any performance gains. Plus can't take the address of it), so the register keyword is pretty much deprecated... but CPU registers surely aren't! A register is a pretty small area of memory on a CPU. It's pretty much the fastest location to store memory at, far faster than RAM. All the registers have names and an intended purpose, and they're important to know for assembly programming.
x86 has the following 8 general-purpose 32-bit registers:

When x86_64 came out, it implemented 64-bit versions of all those registers. But those 32-bit registers are all still valid in x86_64; as will be seen soon, registers have multiple "sizes".
x86_64 has 16 general purpose 64-bit registers:

(which are 64-bit versions of x86's registers). The other 8 are:

Remember that x86_64 pretty much "added on" to x86, so x86's registers are present in x86_64, but not all x86_64 registers are present in x86.

Those are the general-purpose registers for x86 and x86_64, but there are other general-purpose registers, too; they are of a smaller size (in bits/bytes) than those registers, and are just a subregister of them. The register AX, for example, is the low 2 bytes of EAX, which is the low 4 bytes of RAX, which is 8 bytes long. A good visualization for this is from this wikipedia page:

Notice some patterns in the naming of the registers. For the general purpose registers:

Not pictured in that diagram are the registers R8-R15 and the smaller registers they contain. R8-R15 are rather simpler than the above registers; R8-R15 are the 64-bit registers, R8D-R15D are their low 32 bits, R8W-R15W are their low 16 bits, and R8B-R15B are their low 8 bits. The high byte of R8W-R15W don't have a register associated with them and can't be encoded. Their names come from the sizes of data they can store; a group of 4 bytes is called a doubleword, a group of 2 bytes is called a word, and a single byte is... called a byte. Hence the R_D, R_W, R_B in the register names. Note that a group of 8 bytes is called a quadword. R8-R15 don't have a Q in them, but many assembly mnemonics later on can.

This was made with inspect element on the diagram from before, and shows the structure of R8-R15:

So that's enough about the general purpose registers. Just think of them as variables--they store data. Manipulating them (putting values in them, reading values from them) is very easy to do in assembly. All the fancy talk of subregisters and registers having different sizes is kinda complicated, but in the end, perhaps they're usually just smaller-sized variables stored in a larger one.

Now registers are out of the way, it's time for the actual ASSEMBLY. x86/x64 assembly is composed of statements, typically one on each line, consisting of a mnemonic and 0-3 operands. Operands can be registers, immediate values, or memory addresses. Some mnemonics might have prefixes on them. These statements make the CPU interact with its registers, the system's memory, and the stack (which is in memory). Once you get past the vernacular of registers, the stack, and perhaps some bitwise operations, these statements are individually pretty straightforward.