<--  back   Last regenerated: 2022-04-20 17:31:36 kio

zasm - Z80 Assembler – Version 4.4

Assembler directives

#compress

#compress <segment_name>
#compress <segment_name1> to <segment_name2>

new in version 4.1.0

This defines that the named code segment or range of code segments shall be compressed using Einar Saukas' "optimal" LZ77 packer. The named segments must be defined before the #compress directive and they must be Assembler directives: #code
Including C Source Files: #code
#code segments. Compression of Assembler directives: #data
Including C Source Files: #data
#data segments makes no sense, as they are not stored in the output file. zasm currently only supports bottom-up compression, where the decompression increments like in 'ldir'. Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
If multiple segments are compressed then all but the first must not have an explicit address Pseudo instructions: defl, set and '='
Labels: SET
set. They must follow each other without any address gap.

Command Line Options: --z80
Pseudo instructions: .z80, .z180 and .8080
Targets: #target Z80
Z80 decompressors are included in the zasm distribution or can be downloaded from Einar Saukas' Dropbox.

Defined Labels

#compress will define some Pseudo instructions: Label definition
Numeric expressions: Labels
8080 Assembler: Labels
labels for you to use in your source:

#compress SEGMENT
SEGMENT_csize
SEGMENT_cgain
SEGMENT_cdelta
#compress SEGMENT1 to SEGMENT2
SEGMENT1_to_SEGMENT2_size
SEGMENT1_to_SEGMENT2_csize
SEGMENT1_to_SEGMENT2_cgain
SEGMENT1_to_SEGMENT2_cdelta

Note: zasm always defines the following Pseudo instructions: Label definition
Numeric expressions: Labels
8080 Assembler: Labels
labels for all segments:

SEGMENT
SEGMENT_size
SEGMENT_end
SEGMENT_size and SEGMENT1_to_SEGMENT2_size

This is the uncompressed size of the segment(s).

SEGMENT_csize and SEGMENT1_to_SEGMENT2_csize

This is the compressed size of the segment(s) as stored in the output file.

SEGMENT_cgain and SEGMENT1_to_SEGMENT2_cgain

This is the difference between uncompressed and compressed size. Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
If it's negative then compression actually increased the size of your data.

SEGMENT_cdelta and SEGMENT1_to_SEGMENT2_cdelta

A common szenario is that the compressed data and decompressed code segment overlap and the decompressed data overwrites the compressed data while it grows. Then the compressed data must be loaded high enough above the decompressed data so that no unprocessed bytes are overwritten. Pseudo instructions: Label definition
Numeric expressions: Labels
8080 Assembler: Labels
Label 'cdelta' defines the minimum difference between compressed data Pseudo instructions: end, .end
8080 pseudo instructions: END
end
and uncompressed data Pseudo instructions: end, .end
8080 pseudo instructions: END
end
.

Implementation

Compression unavoidably leads to relocated code. So let's start with code relocation first.

Imagine you load code at a 'load_address' and move it down to 'code_address' before it is executed:

Assembler directives: #code
Including C Source Files: #code
#code LOADER, load_address loader: ld hl, LOADER_end ; address of Assembler directives: #code
Including C Source Files: #code
#code MCODE after loading ld de, code_address ; to be moved here ld bc, MCODE_size ldir jp reset ; = code_address   Assembler directives: #code
Including C Source Files: #code
#code MCODE, code_address reset: di ld sp,0 ...

Note: you must be careful to get your Pseudo instructions: Label definition
Numeric expressions: Labels
8080 Assembler: Labels
label values right. This can be a real headache.

The whole code is loaded to (or initially located at) 'load_address'. Therefore we declare our LOADER segment to start with this physical address, so that all Pseudo instructions: Label definition
Numeric expressions: Labels
8080 Assembler: Labels
labels inside this segment are right. (currently there's only one Pseudo instructions: Label definition
Numeric expressions: Labels
8080 Assembler: Labels
label 'loader').

The CODE segment is copied to address 'code_address' and executed there. Therefore we declare our CODE segment to start with this physical address, so that all Pseudo instructions: Label definition
Numeric expressions: Labels
8080 Assembler: Labels
labels inside this segment are right.

It becomes obvious that source and destination block must not overlap and the loader code must not be overwritten as well, at least not the last 5 bytes: 'ldir' and 'jp reset'.

Since large code loaded from tape can frequently not be loaded high or low enough so that the relocated block does not overwrite the loader, we have to make one first improvement: Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
If we move the block downwards then put the loader at the Pseudo instructions: end, .end
8080 pseudo instructions: END
end else use lddr instead of ldir. The lddr version is not discussed here as it is not yet supported by zxsp. So let's put the loader at the Pseudo instructions: end, .end
8080 pseudo instructions: END
end:

Assembler directives: #code
Including C Source Files: #code
#code MCODE, code_address reset: di ld sp,0 ...   Assembler directives: #code
Including C Source Files: #code
#code LOADER, load_address + MCODE_size loader: ld hl, load_address ; Assembler directives: #code
Including C Source Files: #code
#code MCODE after loading ld de, code_address ; to be moved here ld bc, MCODE_size ldir jp reset ; = code_address

Now let's add compression: This is very similar except that the code on the tape is compressed and therefore the block will grow during 'ldir'.

Assembler directives: #code
Including C Source Files: #code
#code MCODE, code_address reset: di ld sp,0 ...   Assembler directives: #code
Including C Source Files: #code
#code LOADER, load_address + MCODE_csize loader: ld hl, load_address ; Assembler directives: #code
Including C Source Files: #code
#code MCODE after loading ld de, code_address ; to be moved here ;ld bc, MCODE_size ; not reqired call decompress_zx7_standard ; ldir jp reset ; = code_address Assembler directives: #include
Including C Source Files: #include
#include "decompress_zx7_standard.s" ; include the zx7 decompressor   #compress MCODE ; define MCODE to be compressed Assembler directives: #assert
Pseudo instructions: #assert
#assert code_address+MCODE_csize+MCODE_cdelta <= load_address+MCODE_csize #assert MCODE_end+MCODE_cdelta <= LOADER

Mind the difference: the LOADER is no longer located at load_address+MCODE_size but now at load_address+MCODE_csize. Right after loading the bytes from load_address to load_address+MCODE_csize are compressed and not executable at all.

#compress MCODE defines that the code segment 'MCODE' shall be written compressed to the output file.

Finally there's a check to Assembler directives: #assert
Pseudo instructions: #assert
assert that the decompressed code will not overwrite the not yet processed compressed data. Both Assembler directives: #assert
Pseudo instructions: #assert
#asserts are identical in this #insert: Examples:
#assert: Example:
incbin: Examples:
#assert: Example:
example.

Resolving Cyclic Dependencies

It's not obvious but zasm resolves a cyclic dependency here: As the LOADER address depends on the compressed MCODE size, all code Pseudo instructions: Label definition
Numeric expressions: Labels
8080 Assembler: Labels
labels in the LOADER segment are not valid until the compressed MCODE size is valid. But the compressed MCODE size cannot be valid unless all Pseudo instructions: Label definition
Numeric expressions: Labels
8080 Assembler: Labels
labels in the source are valid, because a not-yet valid Pseudo instructions: Label definition
Numeric expressions: Labels
8080 Assembler: Labels
label may be used in the MCODE segment and the compressed size may change when the Pseudo instructions: Label definition
Numeric expressions: Labels
8080 Assembler: Labels
label value is finally resolved. Therefore MCODE_csize never becomes valid.

zasm uses a 'preliminary' state here to solve this problem and therefore requires at least one more pass to assemble the source.

Sometimes 'preliminary' Pseudo instructions: Label definition
Numeric expressions: Labels
8080 Assembler: Labels
labels may toggle between each pass or infinitely grow and grow and grow, depending on your source. Then assembly fails. The way to solve this situation depends on the kind and location of the problem. Frequently the problem results from code alignments in your source. E.g. there is a '.align 2' and in one pass it adds no space, zasm adjusts the segment address by one and now it inserts a byte, annihilating zasm's effort. Similarly the compressed data size can toggle between two values when a Pseudo instructions: Label definition
Numeric expressions: Labels
8080 Assembler: Labels
label used in the uncompressed code depends on the compressed data size.

This can eventually be solved by adding a dummy byte. The effect will not last for ever, Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
if it happens again remove the byte.

__date__ and __time__

Normally the assembly success should be predictable. Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
If the uncompressed code contains assembly-time dependent data, as __date__ and __time__, then assembly may succeed or fail unpredictably: you assemble: it fails, you assemble again: it succeeds, you assemble again: it fails, and so on. Using the 1-byte trick may eliminate the problem for a while, but may also just put this data in a uncompressed section instead.

Compression Time

Though the zx7 compression is pretty fast on small code blocks, it can become very slow on larger ones because it has a quadratic time stamp. Additionally larger sources with lots of segments tend to require more passes for the assembler. Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
If assembly time becomes too long then you can do two things:

• Split the compressed blocks. This will speed up assembly time but grow your source, both for additional calls to the decompressor and a not so good compression result.
• Store and compress data in separate files and #insert them. This is the traditional method anyway. This is good for static data with no references to other data but does not work Assembler directives: #if, #elif, #else, #endif
Pseudo instructions: if, endif
if it contains references to non-const Pseudo instructions: Label definition
Numeric expressions: Labels
8080 Assembler: Labels
labels in the rest of the program.

Supported Target Files

Compression can be used with most file formats. There are certain restrictions on what can be compressed, e.g. the file headers of snapshot files cannot be compressed. While with TAP files the major benefit is the reduced loading time, the major benefit for snapshot files probably is that you can store more game data like levels.

Currently (vs. 4.1.0) Command Line Options: --z80
Pseudo instructions: .z80, .z180 and .8080
Targets: #target Z80
Z80 files cannot be compressed. This will be implemented as soon as possible.

Valid HTML   Valid CSS