Skip to content

Commit 9e9716d

Browse files
committed
Update CLAUDE.md
1 parent e8afef5 commit 9e9716d

File tree

1 file changed

+140
-9
lines changed

1 file changed

+140
-9
lines changed

CLAUDE.md

Lines changed: 140 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ Performance, execution cost, and interpreter compatibility are critical.
2929
- Maximum string length is 65535 characters.
3030
- If a file is updated then its copyright date should be bumped. Ensure that the current year is used.
3131
- Tests should be extended to cover any error conditions raise by new code.
32+
- Don't ever `git reset` to try to rewrite history when there is a risk of losing recent work.
33+
- Backup work before running irreversible commands such as `sed` against a large number of files.
3234

3335

3436
## AI Observations
@@ -96,6 +98,11 @@ that affect the behaviour of BWIPP:
9698
- `enabledontdraw` - Allow the user to set dontdraw, in case they are providing custom renderers
9799
- `default_{barcolor,backgroundcolor,...}` - Defaults for certain renderer options set by the user
98100

101+
For example:
102+
103+
```postscript
104+
/uk.co.terryburton.bwipp.global_ctx << /preload true >> def
105+
```
99106

100107
### Directory Structure
101108

@@ -189,7 +196,8 @@ Encoder source files contain metadata comments:
189196

190197
`REQUIRES` lists dependencies of the resource in order. It is used by the build
191198
system and is API for users that want to assemble the resources into a PS file
192-
prolog.
199+
prolog. It is not transitive: If must list the recursive dependencies of all
200+
required resources.
193201

194202

195203
### Resource output file structure
@@ -442,7 +450,7 @@ Encoders create a common dictionary structure expected by their renderer:
442450
- Use `//name` immediate lookup for static data (avoids runtime structure allocation cost)
443451
- Defer expensive computation to lazy init (first-run cost, cached thereafter)
444452
- Prefer stack manipulation over creating intermediate dictionaries
445-
- Use immediate references directly in main procedures instead of creating local aliases (e.g., use `//encoder.fnc1` directly rather than `/fnc1 //encoder.fnc1 def`). This saves runtime dictionary lookups.
453+
- Use immediate references directly instead of creating local aliases (e.g., use `//encoder.fnc1` directly rather than `/fnc1 //encoder.fnc1 def`). This saves runtime dictionary lookups.
446454
- When latevars needs a helper function to compute values, define it in static scope (ensure to bind it; see `auspost.rsprod`) and reference via `//encoder.helper exec` in latevars to save lookups.
447455

448456
**Conditional Assignment Pattern**
@@ -498,6 +506,51 @@ Loops should be commented as such in the first line:
498506
} loop
499507

500508

509+
**Reading Global Context**
510+
511+
Read configuration from (optional) global context with a default:
512+
513+
```postscript
514+
/configvalue 42 def % Default
515+
/uk.co.terryburton.bwipp.global_ctx dup where {
516+
exch get /configkey 2 copy known {get /configvalue exch def} {pop pop} ifelse
517+
} {pop} ifelse
518+
```
519+
520+
521+
**Module-Level Caching with fifocache**
522+
523+
For expensive computations that benefit from caching across invocations (e.g.,
524+
generation of Reed-Solomon polynomial coefficients), use the `fifocache`
525+
resource:
526+
527+
```postscript
528+
/encoder.coeffscachemax N def % Override with global_ctx.encoder.coeffscachemax
529+
/encoder.coeffscachelimit M def % Override with global_ctx.encoder.coeffscachelimit
530+
/uk.co.terryburton.bwipp.global_ctx dup where {
531+
exch get
532+
dup /encoder.coeffscachemax 2 copy known {get /encoder.coeffscachemax exch def} {pop pop} ifelse
533+
/encoder.coeffscachelimit 2 copy known {get /encoder.coeffscachelimit exch def} {pop pop} ifelse
534+
} {pop} ifelse
535+
536+
/encoder.coeffscache encoder.coeffscachemax encoder.coeffscachelimit //fifocache exec def
537+
538+
/encoder.coeffscachefetch {
539+
/key exch def
540+
key
541+
{ //encoder.gencoeffs exec } % Generator procedure
542+
{ length } % Cardinality function; applied to output of generator procedure
543+
//encoder.coeffscache /fetch get exec
544+
} bind def
545+
```
546+
547+
Cache parameter guidelines:
548+
549+
- `max` limits cache entry count (number of keys); `limit` limits total elements (as measured by cardinality function)
550+
- Set `limit=0` to disable caching
551+
- Set `max` and `limit` high enough to avoid evictions when memory allows
552+
553+
501554
### Anti-patterns
502555

503556
- Creating variables (dictionary entries) in hot loops
@@ -528,7 +581,6 @@ elements, then repacking.
528581
```
529582

530583
It can be difficult to determine the required size for an array in advance.
531-
PostScript has no build-in mechanism to automatically extend or shrink arrays.
532584

533585
It is sometimes necessary to over-size the array and later take the used prefix
534586
of the array with `/arr arr 0 len getinterval def`, but there is an associated
@@ -568,6 +620,40 @@ The RSEC loops in qrcode, datamatrix, pdf417 demonstrate advanced uses of this
568620
pattern, including stack-based access to variables outside of the inner loop.
569621

570622

623+
**Updating a dictionary item**
624+
625+
When applying a function to a dictionary key:
626+
627+
```postscript
628+
% Bad: More lookups; verbose
629+
dic /item dic /item get 1 add put
630+
631+
% Good: Clean; efficient
632+
dic /item 2 copy get 1 add put
633+
```
634+
635+
**Beware built-in operators**
636+
637+
When a procedure is `bind`ed, names that match operators are resolved at bind
638+
time. Using operator names as variables causes unexpected behavior:
639+
640+
```postscript
641+
% count is a built-in operator that returns stack depth
642+
(a) (b) (c) count = % prints 3
643+
644+
% Problem: "count" as a variable in a bound procedure
645+
/badproc {
646+
/count 5 def
647+
/count count 1 add def % BUG: count resolves to operator
648+
count
649+
} bind def
650+
651+
badproc = % prints 0 (stack depth), not 6!
652+
```
653+
654+
Common operators to avoid as variable names: `count`, `length`, `index`.
655+
656+
571657
### Profiling
572658

573659
Simple profiling of the overall runtime for some encoder:
@@ -578,13 +664,28 @@ time gs -q -dNOSAFER -dNOPAUSE -dBATCH -sDEVICE=nullpage -c \
578664
100 { 0 0 moveto (DATA) (options) /encoder /uk.co.terryburton.bwipp findresource exec } repeat'
579665
```
580666

581-
### Detailed Profiling Results
667+
668+
### Profiling Results (including failed optimisation attempts)
582669

583670
Large 2D symbols have different runtime bottlenecks, for example:
584671

585-
- QR Code v40 - Mask evaluation is ~75% of overall runtime; RSEC is fast
586-
- Data Matrix 144x144 - RSEC codeword calculation (excluding polynomial generation) is ~75% of overall runtime due to many codeword per block
587-
- Aztec Code 32 layers - RSEC coefficients generation is ~95% of overall runtime due to large Galois fields and many ECC codewords
672+
**QR Code** - >60% of V40 runtime is mask evaluation; bottleneck is penalty calculation requiring multiple full-symbol scans; RSEC is fast
673+
- Direct masklayers access (bitshift per pixel instead of extracting to array): Slower - per-pixel overhead in inner loops exceeds allocation savings
674+
- Array reuse (preallocate masksym, rle, lastpairs, thispairs): No gain
675+
676+
**Data Matrix** - ~75% of 144x144 runtime is RSEC codeword calculation; bottleneck is due to many codewords per block
677+
- ECC codeword calculation is already optimal so there is little that can be gained
678+
- Generator polynomical generation is negligable compared to codeword calculation
679+
680+
**Aztec Code** - ~95% of 32-layer runtime is RSEC coefficient generation; bottleneck is large Galois field operations
681+
- This cost is largely amortised during long production runs by using a FIFO cache
682+
683+
**PDF417** - At high ECC levels RSEC coefficient generation is ~85% of initial runtime
684+
- Lazy loading of all polynomials reduces this time for subsequent symbols
685+
686+
**MicroPDF417** - No significant bottleneck
687+
- Coefficient generation is quick due to limited number of error codewords
688+
588689

589690
## Testing
590691

@@ -601,9 +702,15 @@ all resource tests:
601702
- `isEqual` - Compare output arrays (pixs, sbs)
602703
- `isError` - Verify specific error is raised
603704

604-
Encoders may have one or more of the following debug options:
705+
To access the intermediate dictionary without rendering, each of the encoders
706+
support the following option, which requires `enabledontdraw` to be set in
707+
global context:
605708

606709
- `dontdraw` - Return structured dict without rendering
710+
711+
Encoders may have one or more of the following debug options, each of which
712+
requires `enabledebug` to be set in global context:
713+
607714
- `debugcws` - Return codeword array
608715
- `debugbits` - Return bit array
609716
- `debugecc` - Return error correction data
@@ -818,6 +925,30 @@ Inserting stack elements requires index adjustment:
818925
(a) (b) (c) /x 1 index def => (a) (b) (c) ; and x = (c), not (b) due to /x on the stack!
819926
```
820927

928+
GhostScript can recursively expand structures with `===`:
929+
930+
```postscript
931+
[ [ << /a 1 >> << /b 2 >> ] [ << /c 3 >> ] /d ] ===
932+
```
933+
934+
Dictionaries will dynamically grow as needed:
935+
936+
```postscript
937+
/dic 1 dict def % Initial size, should be sized appropriately; for static data, prefer next power of 2 above expected size
938+
dic /a 1 put
939+
dic /b 2 put % May overflow capacity, but is resized transparently (with associated performance impact)
940+
```
941+
942+
It is not possible to dynamically grow an existing array:
943+
944+
```postscript
945+
/arr 1 array def % Fixed size, should be sized appropriately
946+
arr 0 /a put % Indexed at zero
947+
arr 1 /b put % Error: Out of bounds write
948+
arr 1 get % Error: Out of bounds read
949+
```
950+
951+
821952
Names and strings are treated as equivalent when compared:
822953

823954
```postscript
@@ -856,7 +987,7 @@ Some PLRM terminology is a source of confusion. As a result of the following com
856987
```
857988

858989
- `a` is referred to as the "object" (within the currentdict)
859-
- The `--array--` created by `]` is referred to as "the storage for the object in VM" (either global or local VM depending on the allocation mode indicated by `globalstatus`)
990+
- The `--array--` created by `]` is referred to as "the storage for the object in VM" (either global or local VM depending on the allocation mode indicated by `currentglobal`)
860991
- `b` is also an "object" that refers to the same VM storage as `a`
861992

862993
The terminology differs from many languages where the array itself would be referred to as an object and a and b would be referred to as names or references.

0 commit comments

Comments
 (0)