Skip to main content

Highly optimizing PL/M-80 compiler targeting Z80

Project description

uplm80 - PL/M-80 Compiler

PyPI version Tests Pylint License: GPL v3

A modern PL/M-80 compiler targeting Zilog Z80 assembly language.

PL/M-80 was the primary systems programming language for CP/M and other 8080/Z80 operating systems. This compiler can rebuild original CP/M utilities from their PL/M source code.

Repository: https://github.com/avwohl/uplm80

Features

  • Full PL/M-80 language support
  • Targets Z80 instruction
  • Multi-file compilation with cross-module optimization
  • Multiple optimization passes (peephole, post-assembly tail merging)
  • Generates relocatable object files compatible with standard CP/M linkers
  • Produces code competitive with the original Digital Research compiler

Code Quality

Compiled output is comparable to the original Digital Research PL/M-80 compiler:

Program DR PL/M-80 uplm80 Difference
PIP.COM 7424 bytes 7127 bytes -4.0%

Installation

Quick install from PyPI:

pip install uplm80 um80 upeepz80

Platform-specific guides:

Or install from source:

git clone https://github.com/avwohl/uplm80.git
cd uplm80
pip install -e .

Usage

Compile PL/M-80 to Assembly

uplm80 input.plm -o output.mac

Or run as a module:

python -m uplm80.compiler input.plm -o output.mac

Options:

  • -m cpm or -m bare - Runtime mode (default: cpm)
    • cpm: For new PL/M programs, maximum stack under BDOS
    • bare: Original Digital Research compatible (jump to start-3)
  • -o output.mac - Output file name
  • -O 0|1|2|3 - Optimization level (default: 2)
  • -D SYMBOL - Define conditional compilation symbol (can be repeated)

Multi-File Compilation

Compile multiple source files together for optimal cross-module optimization:

uplm80 main.plm helper.plm library.plm -o output.mac

When multiple files are provided:

  • All files are parsed together before code generation
  • A unified call graph is built across all modules
  • Local variable storage (??AUTO) is optimally allocated based on which procedures can be active simultaneously across module boundaries
  • A single combined output file is generated

This produces better code than compiling files separately, as the compiler can share local variable storage between procedures in different modules that never call each other.

Assemble and Link

Use your preferred Z80 assembler and linker. Example with um80/ul80:

um80 output.mac                              # Assemble to .rel
ul80 -o program.com output.rel runtime.rel   # Link to CP/M .com

Language Reference

PL/M-80 is a typed systems programming language with:

  • Data types: BYTE (8-bit), ADDRESS (16-bit)
  • Variables: Scalars, arrays, structures, BASED variables (pointers)
  • Control flow: DO/END, DO WHILE, DO CASE, IF/THEN/ELSE
  • Procedures: With parameters, local variables, recursion
  • Built-in functions: HIGH, LOW, DOUBLE, SHL, SHR, ROL, ROR, etc.
  • I/O: INPUT, OUTPUT for port access

Example:

hello: DO;
    DECLARE message DATA ('Hello, World!$');
    DECLARE i BYTE;

    print: PROCEDURE(addr) PUBLIC;
        DECLARE addr ADDRESS;
        /* CP/M BDOS print string */
        CALL mon1(9, addr);
    END print;

    CALL print(.message);
END hello;

See examples/hello_cpm.plm for a complete working example.

For more on CP/M BDOS usage, see docs/BDOS_REFERENCE.md.

Conditional Compilation

Later versions of PL/M-80 added conditional compilation directives embedded in comments. This allows the same source to be compiled for different configurations (e.g., CP/M 2.2 vs CP/M 3, single-user vs MP/M).

Directives

Directive Description
/** $set (NAME) **/ Define a symbol
/** $reset (NAME) **/ Undefine a symbol
/** $cond **/ Enable conditional compilation
/** $if NAME **/ Include following code if NAME is defined
/** $else **/ Else branch
/** $endif **/ End conditional block

Example

/** $set (CPM3) **/
/** $cond **/

DECLARE
/** $if CPM3 **/
    VERSION LITERALLY '30H',
/** $else **/
    VERSION LITERALLY '22H',
/** $endif **/
    MAXFILES BYTE;

Command Line

Symbols can also be defined from the command line:

uplm80 pip.plm -D CPM3 -D MPM -o pip.mac

Runtime Library

The compiler generates calls to these runtime routines (provide in a separate .rel file):

Routine Description
??MUL 16-bit unsigned multiply
??DIV 16-bit unsigned divide
??MOD 16-bit unsigned modulo
??SHL 16-bit shift left
??SHR 16-bit logical shift right
??SHRS 16-bit arithmetic shift right
??MOVE Block memory move

Runtime Modes

CP/M Mode (default: -m cpm)

For new PL/M programs. Provides maximum stack space by using the area under BDOS:

  • Program starts with ORG 100H (CP/M TPA)
  • Stack is set from BDOS address at location 0006H: LD HL,(6) / LD SP,HL
  • Maximum available stack (all memory between program end and BDOS)
  • Entry code calls main procedure with CALL MAIN
  • Returns to CP/M with JP 0 (warm boot) when main returns
  • Requires CP/M stubs: MON1, MON2, MON3, BOOT
  • System variables: BDISK, MAXB, FCB, BUFF, IOBYTE

Bare Metal Mode (-m bare)

For original Digital Research PL/M-80 compatibility. Programs begin with a jump to start-3:

  • Entry begins with JP ??START to jump over local stack buffer
  • Uses locally-defined stack (64 bytes in program image)
  • Entry code at ??START sets SP to ??STACK label, then calls MAIN
  • Compatible with original Digital Research programs (ED.PLM, PIP.PLM, etc.)
  • Program can define custom entry point via DATA declarations
  • No automatic OS return (program controls its own exit behavior)

Project Structure

uplm80/
├── compiler.py      # Main compiler driver / CLI
├── frontend.py      # plox-driven lexer + LR parse → AST
├── preprocess.py    # PL/M preprocessor ($INCLUDE, $if, LITERALLY, ...)
├── ast_nodes.py     # AST definitions
├── ast_optimizer.py # AST-level optimizations
├── codegen.py       # Z80 code generator
├── runtime.py       # Runtime helpers
├── symbols.py       # Symbol table
├── errors.py        # Diagnostic exception types
└── data/            # Pre-built plox grammar bundle (plm_full.json)

Peephole optimization is provided by the external upeepz80 package; the front-end is generated from plox grammars (plm_pre + plm_full) and loaded at import time from the JSON bundle in data/.

License

This project is licensed under the GNU General Public License v3.0 or later - see the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit issues and pull requests.

Related Projects

  • 80un - Unpacker for CP/M compression and archive formats (LBR, ARC, squeeze, crunch, CrLZH)
  • cpmdroid - Z80/CP/M emulator for Android with RomWBW HBIOS compatibility and VT100 terminal
  • cpmemu - CP/M 2.2 emulator with Z80/8080 CPU emulation and BDOS/BIOS translation to Unix filesystem
  • ioscpm - Z80/CP/M emulator for iOS and macOS with RomWBW HBIOS compatibility
  • learn-ada-z80 - Ada programming examples for the uada80 compiler targeting Z80/CP/M
  • mbasic - Modern MBASIC 5.21 Interpreter & Compilers
  • mbasic2025 - MBASIC 5.21 source code reconstruction - byte-for-byte match with original binary
  • mbasicc - C++ implementation of MBASIC 5.21
  • mbasicc_web - WebAssembly MBASIC 5.21
  • mpm2 - MP/M II multi-user CP/M emulator with SSH terminal access and SFTP file transfer
  • romwbw_emu - Hardware-level Z80 emulator for RomWBW with 512KB ROM + 512KB RAM banking and HBIOS support
  • scelbal - SCELBAL BASIC interpreter - 8008 to 8080 translation
  • uada80 - Ada compiler targeting Z80 processor and CP/M 2.2 operating system
  • uc80 - ANSI C compiler targeting Z80 processor and CP/M 2.2 operating system
  • ucow - Unix/Linux Cowgol to Z80 compiler
  • um80_and_friends - Microsoft MACRO-80 compatible toolchain for Linux: assembler, linker, librarian, disassembler
  • upeepz80 - Universal peephole optimizer for Z80 compilers
  • z80cpmw - Z80 CP/M emulator for Windows (RomWBW)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

uplm80-0.2.31.tar.gz (159.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

uplm80-0.2.31-py3-none-any.whl (162.8 kB view details)

Uploaded Python 3

File details

Details for the file uplm80-0.2.31.tar.gz.

File metadata

  • Download URL: uplm80-0.2.31.tar.gz
  • Upload date:
  • Size: 159.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for uplm80-0.2.31.tar.gz
Algorithm Hash digest
SHA256 86e6dafd9fe9e68fea54a3466c4c1584552e56881511ccc72a045ddb61ad9ce6
MD5 f5c6722b9e0d5274544df46fe917a47e
BLAKE2b-256 dd648b02672b56d8e61e0eb046f0e2c950a14e0b64cb6a11d376721a5afac505

See more details on using hashes here.

Provenance

The following attestation bundles were made for uplm80-0.2.31.tar.gz:

Publisher: publish.yml on avwohl/uplm80

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file uplm80-0.2.31-py3-none-any.whl.

File metadata

  • Download URL: uplm80-0.2.31-py3-none-any.whl
  • Upload date:
  • Size: 162.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for uplm80-0.2.31-py3-none-any.whl
Algorithm Hash digest
SHA256 8d01c53dc888b3b363a1480e679679d3ae2b28716b5b4a91f13f3df728f0e53e
MD5 6f352b736453b2cdd221d0024f5a48b4
BLAKE2b-256 66a829a74a24937344d068fc599209add662f1959db71ef0aeef9947544a2830

See more details on using hashes here.

Provenance

The following attestation bundles were made for uplm80-0.2.31-py3-none-any.whl:

Publisher: publish.yml on avwohl/uplm80

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page