# Future-work analysis — 2026-10-25 This note records the design discussions from 2026-10-25 so the questions do not have to be re-derived later. It covers `TBITSIZE`, address arithmetic, set ordering, complex types, pragmas, PIM versus ISO coroutines, external assembly, and the dynamic-set design. It is not a commitment to implement everything. **State at the time of writing:** suite 310/310, one-shot fixpoint OK (3,845,439 bytes), per-object fixpoint OK (11 objects), corpus 521/612 compile-OK, 0 crashes, 64 `LANG` and 27 `LIB`. --- ## 1. `TBITSIZE` `TBITSIZE` is a GNU Modula-2 `SYSTEM` procedure function: ```modula2 PROCEDURE TBITSIZE () : CARDINAL; ``` It returns the **minimum number of bits needed to represent a type**, not its normal storage size in bytes. It is mainly useful for packed records/bitfields. A `BOOLEAN` may be one bit logically but occupy a word in an unpacked record. - GNU Modula-2 supports it (and uses it in `M2WIDESET`). - V3 does not support it. - V3's existing `SIZE`/`TSIZE` report storage size in bytes. - Adding it would need: - a type/variable argument form in the grammar; - a descriptor query for minimum bit width (integer/real/char/enum/ subrange/record/packed-record cases); - constant folding so `TBITSIZE(BYTE) = 8` works in `CONST`; - interaction with packed record layout. It is a small-to-medium language gap, not a runtime feature. --- ## 2. `ADDRESS` arithmetic ISO `SYSTEM` defines: ```modula2 PROCEDURE ADDADR (addr: ADDRESS; offset: CARDINAL): ADDRESS; PROCEDURE SUBADR (addr: ADDRESS; offset: CARDINAL): ADDRESS; PROCEDURE DIFADR (addr1, addr2: ADDRESS): INTEGER; PROCEDURE MAKEADR (high: ; ...): ADDRESS; ``` V3 currently supports: - `ADR` - `ADDRESS`/pointer assignment - `ADDRESS`/pointer equality - `VAL`/`CAST` for compatible scalar representations V3 does **not** support: - `ADDADR`/`SUBADR`/`DIFADR`/`MAKEADR` - `ADDRESS + offset`, `ADDRESS - offset`, `ADDRESS - ADDRESS` - C-style arithmetic on typed `POINTER TO` values ### Why it is missing `ADDRESS` is an opaque pointer-like value in V3's type system. Raw pointer arithmetic is not part of the PIM/ISO base language and would weaken V3's separation between typed pointers and addresses. ### Recommended implementation Add the ISO SYSTEM functions first, not general pointer arithmetic: - byte-addressed `ADDADR`/`SUBADR`; - `DIFADR` returning the byte difference (range-checked for `INTEGER`); - optional `MAKEADR`; - tests for offsets, null addresses, overflow and pointer/address equality. For external C/assembly interaction, a C helper remains the pragmatic escape hatch. --- ## 3. Set ordering PIM/ISO set operations are: - equality `=`, inequality `#`, membership `IN`; - union `+`, difference `-`, intersection `*`, symmetric difference `/`. There is **no standard `<`, `<=`, `>`, `>=` ordering for sets**. GNU Modula-2 adds inclusion ordering: ```modula2 a <= b (* a is a subset of b *) a < b (* a is a proper subset of b *) a >= b (* a is a superset of b *) a > b (* a is a proper superset of b *) ``` V3 currently rejects these; corpus case `largeset5` remains a `LANG` failure. ### If implemented as a gm2-compatibility extension Fixed/inline sets: ```text a <= b iff (a AND NOT b) == 0 for every word a < b iff a <= b AND a # b ``` Dynamic sorted-range sets: ```text a <= b iff every range of a is covered by a range of b a < b iff a <= b AND a # b ``` Compiler work: - `SymTab.RelCheck`/`OrdCheck`: accept set ordering; - `Lower`: branch fixed/dynamic set ordering; - `QbeGen`: subset/superset comparisons; - tests: empty sets, equal sets, subset, proper subset, superset, disjoint sets, mixed widths; - `largeset5` would likely move from `LANG` to compile-OK. This is a safe gm2 compatibility extension, not standard PIM/ISO. --- ## 4. PIM versus ISO coroutines ### PIM Low-level SYSTEM primitives: ```modula2 TYPE PROCESS; PROCEDURE NEWPROCESS (body: PROC; workspace: ADDRESS; size: CARDINAL; VAR p: PROCESS); PROCEDURE TRANSFER (VAR from: PROCESS; to: PROCESS); PROCEDURE IOTRANSFER (VAR from, to: PROCESS; interruptNo: CARDINAL); ``` - caller supplies workspace; - caller explicitly transfers; - no scheduler; - no coroutine identity query; - no attach/detach/protection model; - `IOTRANSFER` uses an explicit interrupt number. V3 implements this with `ucontext`; `IOTRANSFER` currently degrades to a normal transfer because there is no interrupt subsystem. ### ISO 10514-1 `COROUTINES` ```modula2 DEFINITION MODULE COROUTINES; TYPE COROUTINE; INTERRUPTSOURCE = CARDINAL; PROTECTION = [UnassignedPriority .. 7]; PROCEDURE NEWCOROUTINE (procBody: PROC; workspace: ADDRESS; size: CARDINAL; VAR cr: COROUTINE; [initProtection: PROTECTION]); PROCEDURE TRANSFER (VAR from: COROUTINE; to: COROUTINE); PROCEDURE IOTRANSFER (VAR from: COROUTINE; to: COROUTINE); PROCEDURE ATTACH (source: INTERRUPTSOURCE); PROCEDURE DETACH (source: INTERRUPTSOURCE); PROCEDURE IsATTACHED (source: INTERRUPTSOURCE): BOOLEAN; PROCEDURE HANDLER (source: INTERRUPTSOURCE): COROUTINE; PROCEDURE CURRENT (): COROUTINE; PROCEDURE LISTEN (p: PROTECTION); PROCEDURE PROT (): PROTECTION; PROCEDURE TurnInterrupts (to: PROTECTION): PROTECTION; PROCEDURE ListenLoop; END COROUTINES. ``` Main differences: | Area | PIM | ISO | | --- | --- | --- | | Context type | `PROCESS` | `COROUTINE` | | Creation | `NEWPROCESS` | `NEWCOROUTINE` + optional protection | | Switch | `TRANSFER` | `TRANSFER` | | Interrupt transfer | `IOTRANSFER(..., interruptNo)` | `IOTRANSFER(from,to)` with attached source | | Current context | none | `CURRENT` | | Interrupt registration | none | `ATTACH`/`DETACH`/`IsATTACHED`/`HANDLER` | | Protection/priority | none | `PROTECTION`, `LISTEN`, `PROT`, `TurnInterrupts` | | Event loop | user-written | `ListenLoop` | V3 has PIM coroutines but no ISO `COROUTINES` module. A pure coroutine subset (`NEWCOROUTINE`, `TRANSFER`, `CURRENT`, workspace management) could map onto the existing ucontext layer. The real interrupt/protection parts need an OS signal/event layer. --- ## 5. External assembly instead of inline `ASM` External assembly works today through `EXTERNAL` and the normal linker; no compiler support for inline `ASM` is required. ```modula2 DEFINITION MODULE AsmOps; PROCEDURE Add (a, b : CARDINAL) : CARDINAL; END AsmOps. ``` ```modula2 IMPLEMENTATION MODULE AsmOps; PROCEDURE Add (a, b : CARDINAL) : CARDINAL; EXTERNAL; END AsmOps. ``` ```asm .text .globl Add .type Add,@function Add: movl %edi, %eax addl %esi, %eax ret ``` ```sh cc -c asm_add.s -o asm_add.o M2 -c AsmOps.def AsmOps.mod qbe -o AsmOps.s AsmOps.ssa cc -c AsmOps.s -o AsmOps.o cc Prog.o AsmOps.o asm_add.o runtime/syslib/shim.c -o Prog -lm ``` For `M2make --separate`, add the assembly object with `--extra asm_add.o` or link manually. ### ABI notes - QBE target is `amd64_sysv`. - `INTEGER`/`CARDINAL` are 32-bit; `LONGINT` is 64-bit. - pointers are 64-bit `l` values. - `VAR` parameters receive addresses. - external procedures get no V3 static-link argument. - open arrays, especially `ARRAY OF CHAR`, are passed as descriptor addresses, not raw C arrays. - record/array value parameters are passed by address. - composite results use a hidden result pointer. For anything beyond scalar arguments, use a small C wrapper around the assembly routine so the V3-facing signature is C-compatible. This is the recommended route for low-level code; inline `ASM` is unnecessary. --- ## 6. Dynamic `SET OF CARDINAL` / `INTEGER` — chosen design V3 now uses a sorted linked list of disjoint inclusive ranges: ```text set = pointer to { lo : CARDINAL; hi : CARDINAL; next : pointer } ``` This was chosen over a full 2^32-bit bit-vector (512 MiB per set value) and over a linked list of individual bits (too much per-value overhead). The range-list representation is: - O(number of contiguous runs), not O(cardinality); - compact for dense ranges; - supports the full 32-bit base range; - `INTEGER` values map with `VAL(CARDINAL, x) XOR 0x80000000`. Implemented operations include empty/range literals, `INCL`/`EXCL`/`IN`, assignment, equality, union/intersection/difference and symmetric difference. Bounded sets such as `SET OF [0..100000]` keep the inline bit-vector form. ### Current limitations - dynamic `SET CONST` is rejected (loud `230`), because no static range-list layout exists yet; - record/array assignment containing dynamic sets currently copies the pointer rather than deeply copying the range list; - nodes use `malloc`; assignment frees old lists but there is no GC or program-exit cleanup; - `PACKEDSET` remains capped at 32 bits. Future work: runtime-initialised dynamic constants, deep composite copy, ownership/lifetime rules, and optionally a hash/paged bitset if range lists become too fragmented. --- ## 7. Complex types: built-in types versus a library V3 currently has no complex arithmetic. `COMPLEX32`, `COMPLEX64` and `COMPLEX128` are declaration-compatible aliases of `REAL` (currently 8 bytes each), so they are placeholders, not real complex types. Their names imply two 4-, 8- and 16-byte components (8, 16 and 32 bytes respectively), so the current aliases are also the wrong size. The comparison table correctly marks `COMPLEX`/`LONGCOMPLEX` as unsupported. ### What ISO expects ISO Modula-2 treats `COMPLEX`/`LONGCOMPLEX` as **predefined types**. The language is expected to provide: - complex arithmetic operators (`+`, `-`, `*`, `/`); - the `CMPLX` constructor; - `RE`/`IM` accessors; - complex-aware assignment, parameters and function results. Transcendental functions live in the separate `ComplexMath` and `LongComplexMath` library modules. The ISO architecture is therefore: ```text compiler-known COMPLEX/LONGCOMPLEX types + operator lowering / builtins + ComplexMath / LongComplexMath libraries ``` A separate library can provide complex-number *functions*, but it cannot by itself provide natural Modula-2 syntax such as `a + b` or `CMPLX`. Operator overloading does not exist in Modula-2, so a library-only design is necessarily a nonstandard utility API. ### Why not classes Classes are a poor fit for complex numbers: - complex numbers are small value types, not objects with identity; - class methods give `a.Add(b)` instead of `a + b`; - class allocation/dispatch is unnecessary overhead for a pair of reals; - classes are not ISO Modula-2 OO (V3's `CLASS` is Clarion-form); - a class still cannot add operators, literals or `RE`/`IM` syntax. Use a value record or a compiler-known type, not a class. ### What generics can do V3 has ISO 10514-2 generics, so a generic implementation could share code between real and long-real variants: ```modula2 GENERIC MODULE Complex(T); TYPE Value = RECORD re, im : T END; ... END Complex. ``` That is useful for implementation sharing, but generics still do not provide operators, literals or ISO source compatibility. Since V3's `REAL` and `LONGREAL` are both 8 bytes today, two small concrete modules are also acceptable and simpler for separate compilation. ### Option A: nonstandard value-record library For practical complex math without an ISO-conformance claim: ```modula2 DEFINITION MODULE Complex; TYPE T = RECORD re, im : REAL END; PROCEDURE Add (a, b : T) : T; PROCEDURE Sub (a, b : T) : T; PROCEDURE Mul (a, b : T) : T; PROCEDURE Div (a, b : T) : T; PROCEDURE Conj (z : T) : T; PROCEDURE Abs (z : T) : REAL; PROCEDURE Arg (z : T) : REAL; END Complex. ``` Advantages: value semantics, no allocation, simple ABI, easy to test. Limitations: call syntax only; no operator or literal support; does not satisfy ISO `COMPLEX`. Do **not** call this type `COMPLEX` or reuse the existing `COMPLEX*` aliases, because that would falsely suggest ISO support. ### Option B: ISO-compatible built-in types Implement `COMPLEX`/`LONGCOMPLEX` as compiler-known value types, with arithmetic lowered to runtime helpers: ```text COMPLEX = two REAL values a + b -> __m2_complex_add(a, b) a * b -> __m2_complex_mul(a, b) CMPLX -> compiler builtin RE/IM -> compiler builtins ``` Then implement `ComplexMath` and `LongComplexMath` as libraries on top. This avoids requiring QBE to understand a native complex type while still providing ISO syntax and semantics. Work items: - representation/layout (`COMPLEX` is 16 bytes with the current 8-byte `REAL`; `COMPLEX32` cannot be 8 bytes until a real 4-byte `SHORTREAL` exists); - type checking for mixed real/complex expressions and conversions; - lowering of arithmetic to helper calls; - `CMPLX`, `RE`, `IM` and constant expressions; - ABI for complex parameters/results, including separate compilation; - library implementations of `abs`, `arg`, `conj`, `sqrt`, `exp`, `ln`, trigonometric functions and `polarToComplex`; - tests for arithmetic, constants, mixed precision, copying, parameters, function results and separate objects. For C interop, do not assume the C99 `_Complex` ABI without checking; prefer small C wrappers where necessary. ### Recommendation - If ISO conformance is a project goal, complex types must eventually be built-in; the library is the math layer, not a replacement. - If only practical complex math is wanted, add the value-record `Complex` library first, with no classes and no fake `COMPLEX` names. - Generics are optional implementation sharing, not a substitute for language support. - Regardless of option, remove or clearly mark the current `COMPLEX32/64/128` `REAL` aliases so programs cannot silently get wrong-size, non-complex values. --- ## 8. Pragmas: TopSpeed compatibility versus portable scoped pragmas V3 has no general pragma facility. It currently parses and ignores two gm2 declaration pragmas (`__BUILTIN__` and `__ATTRIBUTE__ __BUILTIN__ (( name ))`) after `PROCEDURE`, and its TopSpeed sidecar lexes `(*# ... *)` as an ordinary comment. The local `EXCEPTIONS.def` therefore omits `<* noreturn *>`. There are two separate design questions: 1. give V3 a native pragma facility; 2. consume legacy TopSpeed pragmas. The recommendation is to separate them: use the portable, scoped model as the native design, and add a narrow TopSpeed compatibility mode. ### 8.1 TopSpeed V3 pragmas TopSpeed Modula-2 pragmas are a special form of comment: ```modula2 (*# save *) (*# check(range => on, index => off, overflow => off) *) PROCEDURE P (...); (*# restore *) ``` They are **stateful compiler directives**: - a setting applies from that point forward; - `save` / `restore` maintain a stack of compiler settings; - a procedure's calling convention is the setting in force at its definition; - a variable's layout is the setting in force at its declaration. They are grouped into pragma classes: - `check(...)` — range, index, overflow, NIL, stack checks; - `call(...)` — calling convention, register parameters, interrupt handlers, open-array passing; - `data(...)` — segments, packing, volatile, enum size, stack/heap size; - `optimize(...)` — CSE, register allocation, peephole, CPU choice; - `module(...)` — initialization code, implementation-object rules; - `name(...)` — external symbol naming; - `debug(...)` — debug information and procedure tracing; - `option(...)` — language extensions such as bitwise operations on cardinals; - `save` / `restore` — compiler-option stack; - `link`, `project`, `define` — project-system integration. Older `$` compiler directives are also accepted by TopSpeed for compatibility. **Strengths** - Pragmatic and expressive. - Local check overrides without recompiling the build system. - Historical TopSpeed code already uses the syntax. - Easy to lex as a comment; V3's corpus already parses `(*# ... *)`. **Problems for V3** - Much of it is tied to 16-bit x86: segments, near/far calls, interrupts, `reg_param`, `iopl`, memory models, overlays, CPU and coprocessor selection. These do not map to V3's amd64/QBE target. - Semantics are position/state-dependent rather than attached to declarations. - Source annotations are mixed with global build settings such as optimisation and debug options. - It is a frozen, vendor-specific dialect, not a portable standard. - Because `(*# ... *)` is a comment to other compilers, ignoring it silently changes checks, layout or ABI without any diagnostic. TopSpeed pragmas are therefore useful as a **compatibility adapter**, not as V3's native pragma model. ### 8.2 Benjamin / R10 portable pragmas The portable proposal (used by Modula-2 R10/BSK) uses `<* ... *>`: ```modula2 PROCEDURE Reboot <*NORETURN*>; TYPE Header = RECORD <*PACKED*> ... END; VAR Port <*VOLATILE*> : BYTE; ``` It defines: - standard pragmas in uppercase; - implementation-defined pragmas with a lowercase implementation prefix, e.g. `<*v3.SomeOption=...*>`; - explicit scopes for module, type, record, field, procedure and parameter placement, plus point-forward and no-scope placement; - combinable clauses in one block; - `PRESETS` / `UNSET` for external pragma settings instead of a TopSpeed-style global `save` / `restore`; - a mandatory BSK subset: `DEPRECATED`, `FFI`, `FFIDENT`, `INLINE` and `PRIVATETO`, plus `ENCODING` where Unicode is supported. The full proposal is much larger: `ABI`, `ADDR`, `ALIGN`, `PACKED`, `PADBITS`, `LEADBITS`, `VOLATILE`, `NORETURN`, `PURE`, `WEAK`, `IN`, `OUT`, `SINGLEASSIGN`, `ARC`, `GC`, `OWNER`, `BORROWER`, `RETAIN`, `RELEASE`, `MSG`, `TELL`, `TICKET` and others. R10 itself omits some of these (`RANGE`, `RELEASE`, `RETAIN`). **Strengths** - Attached to syntax, not a global mutable state. - Deterministic and easier to validate. - Fits definition/implementation consistency and separate compilation. - `<* ... *>` aligns with ISO-style/gm2 pragma syntax. - Implementation-defined pragmas have a collision-free namespace. - Provides a path to standard annotations for ABI, layout, volatility and diagnostics. - Much better fit for a strict, self-hosting compiler. **Problems** - The proposal is large; implementing all of it is a project of its own. - Many pragmas are promises that require real analysis: `PURE`/`WEAK`/`DETM` need effect checking, `IN`/`OUT` need parameter-write analysis, `NORETURN` needs control-flow checks and `SINGLEASSIGN` needs data-flow checks. - Some are irrelevant to V3: `ARC`, `GC`, `OWNER`, `BORROWER`, `RETAIN`, `RELEASE`; `RESIDENT`, `CONTIGUOUS` and `LOWLATENCY` are mostly OS/runtime hints; `ENCODING` needs scanner/Unicode work. - `ADDR`, `ALIGN`, `PACKED`, `PADBITS` and `LEADBITS` require real layout support. - The proposal is still evolving; V3 should implement a versioned subset, not chase every edit. ### 8.3 Comparison | Aspect | TopSpeed V3 | Benjamin / portable | | --- | --- | --- | | Status | Vendor dialect | Proposed portable specification, used by R10/BSK | | Delimiters | `(*# ... *)` | `<* ... *>` | | Form | `class(name => value, ...)`, `save`, `restore` | `PRAGMA`, `PRAGMA=expr`, clause sequences | | Scope | Stateful from point forward | Attached to explicit syntactic scopes | | ABI/layout | Many 16-bit-specific directives | Portable ABI/layout annotations | | Unknown pragmas | Often ignored or comment-like | Standard versus implementation-defined, diagnosable | | Separate compilation | Needs external project-state discipline | Naturally attachable to symbols and object stamps | | V3 fit | Compatibility adapter | Native pragma design | TopSpeed is not a standard; the `<* ... *>` syntax is the better long-term direction because it is already used by ISO-style/gm2 compilers and by the portable proposal. ### 8.4 Recommended V3 design Use `<* ... *>` with explicit scope attachment as the native pragma syntax. Do not make `(*# ... *)` the default; handle it only in the TopSpeed sidecar or behind an explicit compatibility flag. **Phase 1 — infrastructure and low-risk semantics** - scanner/parser support for `<* ... *>` and adjacent blocks; - AST/symbol-table pragma nodes with source locations; - scope validation; - unknown-pragma policy; - `DEPRECATED`; - `NORETURN`; - `NOINLINE`; - `INLINE` as advisory, with a warning if not honoured; - `MSG` / `TICKET` diagnostics if desired; - `PRIVATETO`. `NORETURN` is especially valuable immediately: it lets V3 consume standard ISO/gm2 definition files without stripping pragmas. **Phase 2 — layout and ABI** - `PACKED`; - `ALIGN` / `MEMALIGN`; - `FFI` / `FFIDENT`; - `ADDR` only once absolute variables and address arithmetic are designed; - `PADBITS` / `LEADBITS` only after packed-record layout exists. Every ABI/layout pragma must participate in: - definition/implementation consistency checks; - the separate-compilation object ABI stamp; - the content hash used by `M2make --separate`. Otherwise two objects can disagree about record layout or symbol names and still link. **Phase 3 — contracts** - `PURE`, `WEAK`, `DETM`; - `IN` / `OUT`; - `SINGLEASSIGN`. These should not be accepted as no-ops. If V3 cannot verify them, it should reject them or emit a clear "accepted but not enforced" warning. Silently pretending to enforce a promise is worse than not supporting it. **Do not implement initially** - `ARC`, `GC`, `OWNER`, `BORROWER`, `RETAIN`, `RELEASE`; - `RESIDENT`, `CONTIGUOUS`, `LOWLATENCY`; - full `PRESETS` / pragma-file machinery; - Unicode `ENCODING`. These need runtime support V3 does not have, or are platform hints with little value on the current target. ### 8.5 TopSpeed compatibility Keep TopSpeed pragmas out of the default V3 grammar. Handle them in the existing `TopSpeed-V3-M2.atg` sidecar, or add a `--topspeed-pragmas` mode to the main compiler. Map only the meaningful subset: - `check(range/index/overflow/nil/stack)`; - `data(packed/volatile)`; - `module(init_code)`; - `call(c_conv)`; - `save` / `restore`. Reject or warn about: - `near_call`, `far`, segments, `seg_name`, `ds_*`; - `reg_param`, `reg_saved`, `reg_return`; - `interrupt`, `iopl`; - overlays, memory models, `cpu`, `copro`; - Windows/OS2/DOS-specific options. TopSpeed stateful pragmas can be translated by keeping an option stack during parsing and snapshotting the current state onto each declaration. That is an adapter, not the native model. ### 8.6 Never silently ignore - A TopSpeed pragma that V3 treats as a comment must produce at least a warning in TopSpeed-compatibility mode. - An unknown standard pragma should be an error. - An unknown `v3.*` pragma should be a warning or informational message. Otherwise V3 will compile code with different checks or ABI and call it success. ### 8.7 Sources - TopSpeed Developer's Guide, chapter on pragmas. - `trijezdci/M2-Pragmas` wiki, especially *Pragma Specification*, *Pragma Placement* and *Pragma Scopes*. - Modula-2 R10/BSK pragma facilities chapter. --- ## 9. Suggested order of future work 1. `TBITSIZE` (small, standard-facing). 2. `ADDADR`/`SUBADR`/`DIFADR` (small, ISO SYSTEM). 3. Set ordering (bounded gm2 compatibility; fixes `largeset5`). 4. Complex types: choose ISO built-ins versus a nonstandard value-record library; remove the misleading `COMPLEX*` aliases. 5. Pragmas: native `<* ... *>` parser/IR plus a low-risk semantic subset; later add the TopSpeed adapter and ABI/layout pragmas. 6. ISO `COROUTINES` subset, then interrupt/protection layer. 7. Dynamic `SET CONST` and deep composite copies. 8. Parser error recovery and diagnostics. External assembly needs no compiler work; document it as the supported low-level escape hatch.