Bytecode is something that modern programmers rarely have to touch. The rare few that do typically get at least an assembler up and running as soon as possible. Bytecode is rarely practical, and horrible for maintenance and readability.
Even the most basic of assemblers hold a few critical
advantages. With bytecode, the code you write
has to conform to fixed positions. If you want to jump
to a point in code, or access a place in memory, that
position must be hardcoded. On top of that, assembly
uses mnemonics that are far easier to remember and quickly
interpret. rather than needing a instruction table,
most assembly instructions are intuitive.
ADD A B
is far easier to read and manage mentally than a block containing the number
Comments are also quite useful. With bytecode, there's only space for code and data, as everything you write could be executed by the processor. With assembly, you can select lines or parts of lines to ignore, and use that space to store information to clarify program specifics to the reader.
Bytecode maintains one advantage. Self-modification. A clever programmer writing bytecode can take make use of far more than the apparant usable space by storing new instructions on top of the old ones, and then re-executing the block.
Unfortunately, it does have a few drawbacks. While it's nice for space-saving, it's not great for the modern processor's caching systems. When you modify the code at runtime, the processor's cached bytecode becomes invalid, and it has to stop and pull in the new code. Additionally, it's not great for system security. If the program can modify itself, the user may be able to coerce the program to make malicious self-modifications, jumping into blocks of code that the program wasn't intended to run.
Written in bytecode manually, I had to first write the program out on paper, and then translate the chunks into operations by consulting the instruction table. This was slow to write, and annoying to debug.
Taking far less time to write, this was written in assembly and then assembled using an assembler. The resulting program performs the same operations in the same period of time, and the source is readable, and far more easily modified. As an added legibility bonus, logical errors are far easier to spot.
; INIT start: LOAD A 80 ; load x LOAD B 81 ; load y CMP A B ; is x > y? JG swap ; goto swap ; 9 - SAVE_VAR save_var: STORE A 96 ; save min(x, y) as i STORE B 112 ; save max(x, y) as increment JMP loop ; 32 - SWAP swap: SWAP A B ; (x, y) -> (y, x) JMP save_var ; 36 - LOOP loop: LOAD A 96 ; get i ISZERO A ; is i == 0? JZ exit DEC A ; i-- STORE A 96 ; save new i JMP add_increment ; 67 - ADD INCREMENT add_increment: LOAD A 97 ; read in cumulative value LOAD B 112 ; read in increment ADD A B ; cumulative = cumulative + increment JERR exit ; if overflow, exit STORE A 97 ; save new cumulative JMP loop ; 255 - EXIT exit: HALT ; DATA $80 6 4 ; x, y