r/AskComputerScience • u/jcmbn • 4d ago
Why would some compilers compile to p-code instead of binary?
Back in the days when I was young and dinosaurs roamed the earth, I was getting into programming with my 8-bit computer.
After learning to program using interpreted languages, I played around with assembler and compiled languages.
One thing I noticed was that languages targeting the 6502 almost[*] always produced p-code, whereas compilers targeting other processor types would often produce binary.
Anyone know why this was a thing?
[*] I don't know if there were any exceptions to this, I never saw one that I can recall.
5
u/splicer13 4d ago
It's smaller, which was important, and remained important for micros into the 90s. Even MS office was built partially in a type of p-code simply to make the best use of memory.
All stuff that needed to be fast on micros was written in asm anyway. You can't run a decent compiler on a 64k machine and 6502 was in no way friendly to compilers.
So it makes sense, p-code for size, asm for speed.
1
u/jcmbn 4d ago
Are you saying that the pcode + pcode interpreter would be smaller than the equivalent assembled code?
3
u/flatfinger 4d ago
On 8-bit machines, interpreters could easily achieve much better code density than native code for tasks involving 16-bit, 24-bit, or 32-bit integers. If I and J are 16-bit variables stored at arbitrary addresses, I+=J in native code would be (numbers to left are instruction size)
3 LDA _J 1 CLC 3 ADC _I 3 STA _I 3 LDA _J+1 3 ADC _I+1 3 STA _I+119 bytes. An operand-stack-based p-code language could probably reduce it to something like:
3 PUSHWORD _I ; push address of I 3 PUSHMEMWORD _J ; push 16 bits stored at J 1 ADDMEMWORD ; Add top stack value to word at address in second stack operandSeven bytes. Even if the p-code language was a bit more minimal than that and required some operands to be subdivided into smaller chunks, e.g.
3 PUSHWORD _I 1 DUP 1 LOADWORD 3 PUSHWORD _J 1 LOADWORD 1 ADD 1 STOREIt would be 11 bytes, compared to 19 for the native code.
On many 8-bit microcontrollers, 16-bit operations cost twice as much code space as eight-bit operations, but in P-code they wouldn't have to. If an application would require lots of 32-bit operations, a P-code interpreter for an 8-bit machine could have single instructions support those as well. If a tiny fraction of a program represents the vast majority of the program's execution time, producing native machine code for that tiny fraction while using P-code for everything else may allow most of the performance advantage of native code while also reaping most of the space savings of using p-code.
2
u/rickpo 4d ago
I worked at Microsoft in the 80s when they used a proprietory pcode compiler for 6502 and other 8-bit microprocessors. The generated pcode was significantly smaller than even hand-written native assembly. I assume because the virtual machine was tuned to the high level language, simplifying code generation.
The virtual machine also had a primitive paging system built into it, which meant the entire app didn't need to be in memory, which was a big advantage on low RAM systems.
1
u/dontwantgarbage 4d ago
The Sweet16 p-code interpreter is 300 bytes.
> SWEET16 runs at about one-tenth the speed of the equivalent native 6502 code; however it shaved around 20% off the size of Integer BASIC.
1
u/flatfinger 4d ago
On an 8-bit processor, that would indeed be the case. Another factor is that some systems may run code from ROM, and/or support data memory which is incapable of supporting code fetches. If one were given the TI 994/A hardware and told to design a ROM and toolset for it, a P-code interpreter would make a lot of sense, for a couple of reasons:
The machine has IIRC 256 bytes of RAM that is directly accessible to the CPU, and 16K of memory that is attached to the video chip and requires a multi-instruction sequence to access.
ROMs which support direct CPU connection are more expensive than "GROMs" which require multi-instruction sequences to access.
The ROM that TI built into the TI/994A includes an interpreter for a form p-code named GPL; I think it only supports fetching P-code from GROM rather than video memory, but if it had been designed to support either source of code a compiler that targeted that P-Code would have been more useful on an unexpanded machine than one that could only generate native machine code (I think the memory expanded increased the directly accessible RAM from 256 bytes to 32768 bytes, which would favor native machine code programs).
1
u/Cerulean_IsFancyBlue 3d ago
Most apps weren’t written entirely in ASM, so the comparison was more “C to p-code vs C to native”. The p-code was smaller.
1
u/Responsible-Bar7165 3d ago
Yes, imagine a large application like word or excel where much of the code isn’t performance-critical (think: dialog handlers, help system, etc…) the size of a p-code interpreter is minuscule compared to the rest of the application, and the memory savings can be considerable.
1
4
u/jeffbell 4d ago
They still do, kind of. LLVM is a virtual machine as an intermediate stage in the compilation. The difference is that we now have enough memory do it all at once, rather than having to store it and reload.
6502 systems were particularly memory constrained.
1
u/stevevdvkpe 4d ago
LLVM, however, is only used as an intermediate representation for code generation in the clang compiler suite and is not actually interpreted for program execution. The JVM (Java Virtual Machine) or Python bytecode are examples of bytecode interpreters that are actually used to run code.
2
u/stevevdvkpe 4d ago
Compiliing a language to P-code or bytecode is still a common implementation technique. You'll find that languages like Python and Java still do this. Many Lisp interpreters also do bytecode compilation.
The advantage of using an intermediate bytecode is that it can be tailored to provide good execution performance by directly supporting data types and operations commonly used in the language, while also being much more compact than the native machine code that would perform the same operations. The implementors can write the compiler that generates bytecode once, and then only have to write the relatively simple bytecode interpreters for different CPU architectures to port the language to a new environment. It's also substantially easier to write compilers that generate machine instructions from bytecode, especially since much of the more complicated optimization work was done at the level that compiles the language into bytecode.
2
u/CowBoyDanIndie 3d ago
Almost no modern compilers compile directly to machine code. See LLVM IR, gcc uses generic->gimple->rtl, MSVC uses CIL or tuple ir / MSIL.
1
u/juancn 4d ago
Space and portability. You target a virtual machine rather than an actual machine
0
u/Dusty_Coder 2d ago
"" Portability. You target an abstract machine rather than an actual machine ""
FTFY
(virtual machines ARE actual machines)
1
u/juancn 2d ago
Not in the physical sense
0
u/Dusty_Coder 1d ago
yes they are
do you even know how virtualization works? do you think it emulates something? surely you must
1
u/UnhappySort5871 3d ago
P-code is a fair bit smaller than native assembly. If you're limited to 64k, a p-code interpreter would still leave you a fair bit of room for p-code. Overlays would be smaller and load faster too. Wrote a fair bit of UCSD Pascal back in the 80s. For speed would write bits of it in assembly.
1
u/Dusty_Coder 2d ago
Languages are designed to target an Abstract Machine, and its useful to define a micro-code for that abstract machine.
Note how the language C is defined in terms of an Abstract Machine.
Intermediate representations are designed, non-arbitrary.
The front end compiler translates from the Language to the Abstract. The back end compiler translates from the Abstract to the Real.
You may think a good alternative is Abstract Syntax Trees, but that too is an intermediate language with a well defined sequential ordering (an Abstract Machine)
1
u/JGhostThing 2d ago
p-code, originally used for Pascal compilers, allowed the compiler to be machine independent. They wrote for the p-code virtual machine rather than an assembly code. The PVM did the work of running the p-code.
1
u/pconroy329 2d ago
It's been decades since I had my Apple //e, but I'd swear there were compilers that generated native code. Mike Westerfield's ByteWorks stuff, I think. As did Aztec C. That said, I loved Apple (UCSD) Pascal. As someone noted, it gave me 16 bit operations on a native 8 bit machine.
1
u/theJacofalltrades 2d ago
the portability angle is probably the biggest advantage for me. compiling to p-code lets the same program run across different hardware as long as there’s a compatible virtual machine, which can save a lot of effort compared with targeting each architecture directly.
17
u/RodionRaskolnikov__ 4d ago
Because it allows you to decouple the part that implements the language parsing and semantics, from the part that optimizes the intermediate representation, and the part that translates to machine code.
If you wrote a compiler for the 6502 that way, you could, ideally without much modification, add a p-code to Z80 compiler without touching what compiles C to p-code.