Note: This question is somewhat related to How exactly is bytecode "parsed"?, but it is not a duplicate of it. In this question, I'm asking about a specific part of how bytecode is generated, not how bytecode is "parsed".
As stated in the title, how are literals(such as strings, integers, etc...), encoded into bytecode files? My confusion comes from the fact that the byte representation of any given literal is of variable length. Which means that a virtual machine would have no idea how many bytes it needs to gather in order to read the entire literal. If my question is still not clear, I believe a visual example will help illustrate my confusion.
Consider this example. A parser has just constructed an abstract syntax tree. It converted the expression
3 + 2 into:
+ / \ 3 2
Your compiler/interprter now begins to walk the tree. It generates the following bytecode:
PUSH 3 PUSH 2 ADD | | | | | |-----| |--------------| |-----| |----------| |-----| b'\x00' b'\x00\x00\x00\' b'\x00' b'\x00\x00\' b'\x05'
Your virtual machine then begins reading in the bytecode file. It reads the first byte, and sees that it is the opcode PUSH. It now needs to read the argument to the opcode PUSH.
But here is the problem. The virtual machine has no way of knowing how many bytes it needs to read to get the entire argument to PUSH. the arguments to PUSH are a variable number of bytes, so the virtual machine does not know how many bytes it needs to read for each argument. As seen in the pseudo bytecode above, The number of bytes used to represent different values can vary, and are not consistent.
And while the above example only uses integers, this can also apply to other things. Such as strings, or string representations of identifier names.
I tried searching on various blogs and even the official documentation of some languages bytecode, but I still have not found a explanation for how literals are encoded.
To give an example that makes the bytes-per-operation very explicit there is SPIR-V. The first 4-byte word of each instruction is constructed as 2-byte length + 2-byte opcode.
It seems that what he is saying is that SPIR-V forces all opcodes an their arguments to be compressed or expand to fill two bytes. While I suppose on could do this, I'm fairly sure this is not what he meant.
What is the common practice when encoding literal values, whose byte representations are of variable length, into bytecode files? Of course, I assume that their is a common practice, but perhaps each language does it differently?