Statement emission now runs from parser pass 2: assignment, byte + and -, and print reach codegen.s through emit.s, and the statements fixture executes the emitted bytes in x128. Restoring the parser trace fixture's length arithmetic (it emitted between the two halves of the subtraction, which clears carry) brings the 41-fixture parser verification back to green after emit.s joined the link. Also check in the vim clone's improved C128 build and the Power C reverse-engineering notes and tools. Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>main
| @@ -549,6 +549,37 @@ rejected before code generation: the parser reports `ACC_PRIVATE_MEMBER` | |||||
| (E120) for a private field or method reached outside its class, so this layer | (E120) for a private field or method reached outside its class, so this layer | ||||
| never sees one. | never sees one. | ||||
| ### 7.11 Statement emission (E04-S06, in progress) | |||||
| `src/compiler/emit.s` is the first emitter driven by the parser itself. Pass 2 | |||||
| parses statements for type checking; when `em_on` is set, the same parse also | |||||
| emits bytes into the shared `codegen.s` buffer. `parse_file` resets the | |||||
| emitter and the buffer at the start of pass 2, and pass 1 assigns the same | |||||
| frame slots without emitting, so the two passes agree by construction. | |||||
| Parameters take `FP+2` upward in declaration order and locals `FP-1` downward, | |||||
| with one byte for boolean/byte/char and two for integer/word. A slot is not | |||||
| addressed with `LDY #offset` / `(FRAME_FP),Y`: indexed-indirect addressing adds | |||||
| Y to the pointer, so a negative offset would address a page above `FP`. | |||||
| `em_point_slot` instead emits `EM_LP = FRAME_FP + offset` (low byte plus carry, | |||||
| high byte plus the offset's sign byte), leaves Y = 0, and the load or store uses | |||||
| `(EM_LP),Y`. The store keeps its value on the hardware stack across that | |||||
| sequence. | |||||
| Byte `+` and `-` push the left operand, evaluate the right, then combine | |||||
| through `EM_TMP`. `print` emits through `em_pr_text`, `em_pr_byte`, and | |||||
| `em_pr_cr`, which the caller fills; each call goes through a `JMP (cell)` | |||||
| trampoline, so the test records what the program asked to print instead of | |||||
| depending on CHROUT's mapping. A `print` that captured a string literal passes | |||||
| `gen_text` and its length; otherwise it passes the byte in A. | |||||
| The `statements.n8` fixture parses two locals, three assignments, an | |||||
| addition, and two prints, then JSRs the 138 emitted bytes in place. It checks | |||||
| `a = 10`, `b = 7`, and the recorded stream `SUM`, CR, `$FF`, `$0A`, CR. Control | |||||
| flow, calls, field stores, word values, arrays, the frame prologue, and | |||||
| reporting `em_bad` as a diagnostic remain open; E04-S07 owns sizing and pass-3 | |||||
| addresses. | |||||
| ## 8. Heap and Ownership | ## 8. Heap and Ownership | ||||
| - The generated-program heap initially resides in the same RAM bank as program | - The generated-program heap initially resides in the same RAM bank as program | ||||
| @@ -63,6 +63,7 @@ documented default. `Proposed` means a later change still needs approval. | |||||
| | D135 | E05-S03 construction and deletion live in `src/runtime/construct.s` above the heap and descriptor layers. `obj_new` takes A/X = descriptor, reads the instance size (zero is `OBJ_ERR_BAD`), allocates it, writes the descriptor pointer at object+0, then calls the caller-selected `obj_init_ptr` entry once with A/X = self and the constructor arguments still on the software frame stack at `FP+2`; the caller discards them with `frame_discard`. Allocation failure returns C=1 with `OBJ_ERR_HEAP` and never calls `init`. `obj_delete` takes A/X = object: null is a successful no-op, otherwise the descriptor's optional destructor runs once with A/X = self and the complete block goes back through `heap_free`. Both calls use `obj_call`, which takes its entry from zero-page `co_vec`, preserves A/X for `self`, pushes one synthetic return address, and ends with `JMP (co_vec)`; `co_vec` must not straddle a page. | Gives generated `new`/`delete` a testable runtime contract without freezing heap placement or the E06 dispatch. Carrying the entry in `co_vec` instead of A/X is what lets the callee still receive `self` in A/X, and the same shape serves method-table dispatch later. `construct.s` reports `OBJ_ERR_HEAP` rather than printing; codegen still maps that to R005. Implemented by `construct.s`, the `OBJ_*` codes in `class.inc`, and the object fixture. | | | D135 | E05-S03 construction and deletion live in `src/runtime/construct.s` above the heap and descriptor layers. `obj_new` takes A/X = descriptor, reads the instance size (zero is `OBJ_ERR_BAD`), allocates it, writes the descriptor pointer at object+0, then calls the caller-selected `obj_init_ptr` entry once with A/X = self and the constructor arguments still on the software frame stack at `FP+2`; the caller discards them with `frame_discard`. Allocation failure returns C=1 with `OBJ_ERR_HEAP` and never calls `init`. `obj_delete` takes A/X = object: null is a successful no-op, otherwise the descriptor's optional destructor runs once with A/X = self and the complete block goes back through `heap_free`. Both calls use `obj_call`, which takes its entry from zero-page `co_vec`, preserves A/X for `self`, pushes one synthetic return address, and ends with `JMP (co_vec)`; `co_vec` must not straddle a page. | Gives generated `new`/`delete` a testable runtime contract without freezing heap placement or the E06 dispatch. Carrying the entry in `co_vec` instead of A/X is what lets the callee still receive `self` in A/X, and the same shape serves method-table dispatch later. `construct.s` reports `OBJ_ERR_HEAP` rather than printing; codegen still maps that to R005. Implemented by `construct.s`, the `OBJ_*` codes in `class.inc`, and the object fixture. | | ||||
| | D134 | E05-S02 class descriptors are 8-byte packed records: 2-byte instance size (including the object header), 2-byte destructor address (0 = none), 1-byte interface count, 2-byte interface-directory pointer (0 when empty), and a reserved flags byte that must be 0. There is no parent-class slot. Objects begin with a 2-byte descriptor pointer and fields follow at +2. Interface-directory entries are 4 bytes: interface-descriptor pointer and method-table pointer. `objects.s` provides read-only accessors; `output.s` writes `CLASS name size destroy count ifdir` records after sections. | Makes object identity, size/deletion, and the future interface path deterministic before construction/dispatch. Flags are reserved for non-inheritance metadata; interface descriptor contents and generated `new` remain E05-S03/E06. Implemented by `class.inc`, `objects.s`, and the class fixture. | | | D134 | E05-S02 class descriptors are 8-byte packed records: 2-byte instance size (including the object header), 2-byte destructor address (0 = none), 1-byte interface count, 2-byte interface-directory pointer (0 when empty), and a reserved flags byte that must be 0. There is no parent-class slot. Objects begin with a 2-byte descriptor pointer and fields follow at +2. Interface-directory entries are 4 bytes: interface-descriptor pointer and method-table pointer. `objects.s` provides read-only accessors; `output.s` writes `CLASS name size destroy count ifdir` records after sections. | Makes object identity, size/deletion, and the future interface path deterministic before construction/dispatch. Flags are reserved for non-inheritance metadata; interface descriptor contents and generated `new` remain E05-S03/E06. Implemented by `class.inc`, `objects.s`, and the class fixture. | | ||||
| | D136 | E05-S04 member emission lives in `src/compiler/members.s`. A generated method body starts with `mb_self_store`, recording the incoming self in zero-page `MB_SELF` (`$F9-$FA`). `self.field` emits `LDY #offset` plus `LDA (MB_SELF),Y` or `STA (MB_SELF),Y`, with a second byte for word fields; the word store moves the high byte with `TXA` because the 6502 has no `STX (zp),Y`. `mb_point` loads any two-byte object reference into `MB_SELF` for a field or call on another receiver. A concrete call emits `mb_self_load`, `JSR target`, then `frame_discard` for the argument bytes, preserving a byte or word result. `codegen.s` gains `cg_ldy_imm`, the zero-page forms, and the indexed-indirect forms. | Gives generated methods the member half of the D129 ABI without freezing parser integration or interface dispatch. One receiver pointer keeps field access at four bytes per byte field and stays clear of `$FB-$FE` (link-time assertion). Private access never reaches this layer: the parser reports `ACC_PRIVATE_MEMBER` (E120) first. Implemented by `members.s`, `members.inc`, `codegen.s`, and the member fixture. | | | D136 | E05-S04 member emission lives in `src/compiler/members.s`. A generated method body starts with `mb_self_store`, recording the incoming self in zero-page `MB_SELF` (`$F9-$FA`). `self.field` emits `LDY #offset` plus `LDA (MB_SELF),Y` or `STA (MB_SELF),Y`, with a second byte for word fields; the word store moves the high byte with `TXA` because the 6502 has no `STX (zp),Y`. `mb_point` loads any two-byte object reference into `MB_SELF` for a field or call on another receiver. A concrete call emits `mb_self_load`, `JSR target`, then `frame_discard` for the argument bytes, preserving a byte or word result. `codegen.s` gains `cg_ldy_imm`, the zero-page forms, and the indexed-indirect forms. | Gives generated methods the member half of the D129 ABI without freezing parser integration or interface dispatch. One receiver pointer keeps field access at four bytes per byte field and stays clear of `$FB-$FE` (link-time assertion). Private access never reaches this layer: the parser reports `ACC_PRIVATE_MEMBER` (E120) first. Implemented by `members.s`, `members.inc`, `codegen.s`, and the member fixture. | | ||||
| | D137 | E04-S06 emission is driven from parser pass 2: with `em_on` set, the parser calls `emit.s` as it type-checks each statement, and `parse_file` resets the emitter (and the code buffer) at the start of pass 2; pass 1 only assigns the same slots. Parameters take `FP+2` upward in declaration order, locals `FP-1` downward, one byte for byte-sized types and two for words. A slot is reached by emitting `EM_LP = FRAME_FP + offset` with the offset's sign byte, because `(FP),Y` cannot address below `FP`; `EM_LP` is `$F5-$F6` and the combine temp `EM_TMP` is `$F7`. Byte `+`/`-` keep the left operand on the hardware stack. Print calls go through the `em_pr_text/byte/cr` cells and three `JMP (cell)` trampolines, so a fixture can record output without CHROUT. Emitter failures set `em_bad` (`EM_ERR_SIZE`, `EM_ERR_TARGET`, `EM_ERR_OP`, `EM_ERR_STATE`) and stop emitting; nothing is written outside the code buffer. | Gives the first parser-to-codegen path a testable shape without freezing pass 3: the fixture executes the emitted bytes in place and checks locals and recorded output. The 255-byte buffer, one captured string literal, and the fixture-owned frame prologue are known limits for E04-S07, which owns sizing, section addresses, and `frame_enter`. Control flow, calls, fields, word values, arrays, and diagnostic reporting remain E04-S06 work. Implemented by `emit.s`, `emit.inc`, the `parser.s` hooks, `codegen.s`, and the statements fixture. | | |||||
| ## Deferred | ## Deferred | ||||
| @@ -57,6 +57,23 @@ install this mapping and call `frame_init` when it starts emitting user code | |||||
| `FRAME_SP` nor `FRAME_FP`. Field offsets are the parser's layout offsets, with | `FRAME_SP` nor `FRAME_FP`. Field offsets are the parser's layout offsets, with | ||||
| the first field at +2, so generated code needs no runtime translation. | the first field at +2, so generated code needs no runtime translation. | ||||
| ## Statement Emission (E04-S06) | |||||
| | Element | Value | | |||||
| |---|---| | |||||
| | Slot pointer | `EM_LP` = `$F5-$F6`, set to `FP + signed offset` | | |||||
| | Combine temp | `EM_TMP` = `$F7` | | |||||
| | Parameters | `FP+2` upward, declaration order | | |||||
| | Locals | `FP-1` downward, declaration order | | |||||
| | Code buffer | `cg_code`, 255 bytes (E04-S03) | | |||||
| | Print entries | `em_pr_text`/`em_pr_byte`/`em_pr_cr` cells with `JMP (cell)` trampolines | | |||||
| `EM_LP` and `EM_TMP` sit below `MB_SELF` (`$F9-$FA`) and clear of the frame | |||||
| ABI at `$FB-$FE`; `emit.s` asserts at link time that `EM_TMP` does not overlap | |||||
| `MB_SELF`. Slot offsets are 8-bit frame displacements, so a function's locals | |||||
| and parameters together must stay under 127 bytes until pass-3 sizing assigns | |||||
| frame sizes. | |||||
| ## Heap Fixture (E05-S01) | ## Heap Fixture (E05-S01) | ||||
| | Element | Value | | | Element | Value | | ||||
| @@ -0,0 +1,114 @@ | |||||
| --- | |||||
| story: E04-S06 | |||||
| owner: codebuff | |||||
| status: in-progress | |||||
| started: 2026-11-09 | |||||
| depends-on: [E04-S04, E04-S05, E05-S04, E03-S04] | |||||
| --- | |||||
| # E04-S06 Claim | |||||
| ## Intended change | |||||
| Give the parser a first real emission path: while pass 2 type-checks a body, | |||||
| emit code for byte locals and parameters, number and char constants, `+`/`-`, | |||||
| assignment, and `print`. The emitted bytes run in place in x128, so a fixture | |||||
| can parse a source file and check both the resulting locals and the bytes the | |||||
| program asked to print. | |||||
| ## File area | |||||
| - `src/compiler/emit.s`, `src/common/emit.inc` (new) | |||||
| - `src/compiler/parser.s` (pass-2 hooks), `src/compiler/codegen.s` (forms) | |||||
| - `tests/compiler/statements.n8`, `tests/compiler/statements-test.s` | |||||
| - `scripts/build-statements-test.ps1`, `scripts/verify-statements-test.ps1` | |||||
| - parser-linking build scripts (link `codegen.s` and `emit.s`) | |||||
| - `docs/decisions.md`, `docs/architecture.md`, `docs/memory-map.md` | |||||
| ## Acceptance evidence | |||||
| - [x] Partly: statements emit through `codegen.s` — assignment, `+`/`-`, and | |||||
| `print`. `if`/`else`, `while`, `for`, and `return` do not emit yet. | |||||
| - [x] Partly: temporaries follow D129 for byte values (left operand on the | |||||
| hardware stack, combine through `EM_TMP`); results are in A. Parameters | |||||
| get `FP+2` slots in declaration order; argument pushing and discard are | |||||
| not emitted yet. | |||||
| - [ ] `self.field` and concrete calls through `members.s` — not emitted yet. | |||||
| `members.s` (E05-S04) and the codegen forms it needs are in place. | |||||
| - [x] Partly: emitter failures set `em_bad` (`EM_ERR_SIZE`, `EM_ERR_TARGET`, | |||||
| `EM_ERR_OP`, `EM_ERR_STATE`) and `cg_err` keeps the buffer detail; a | |||||
| failed emit writes nothing outside `cg_code`. No diagnostic is reported | |||||
| through `diag_report` yet. | |||||
| - [x] Partly: a source program with statements and expressions runs in x128 | |||||
| with an expected signature (see below). The concrete call in the | |||||
| criterion is not part of it yet. | |||||
| ## Work log | |||||
| - `pwsh -File scripts/verify-statements-test.ps1` (final): PASS — 138 bytes | |||||
| emitted, `a=10`, `b=7`, `cg_err=0`, no diagnostics, recorded output | |||||
| `SUM 0D FF 0A 0D`. | |||||
| - `verify-member-test`, `verify-control-test`, `verify-scalar-test`: PASS | |||||
| unchanged. | |||||
| - Bugs found while bringing the path up: `(FRAME_FP),Y` cannot address `FP-1` | |||||
| (it adds 255), so slots are reached through `EM_LP = FP + signed offset`; | |||||
| `em_stmt_begin` must not clear the target `em_target` captured; the store | |||||
| must keep A across the pointer setup (`PHA`/`PLA`); `cg_len` was | |||||
| uninitialized and pass 1 also emitted, so `em_reset` now calls `cg_init` and | |||||
| assignment emission is gated on pass 2. | |||||
| - Parser pass-2 growth pushed `beq @group` out of range; it now uses the | |||||
| branch-over-`JMP` form. | |||||
| - `build-hello-test.ps1` and `build-parser-test.ps1` were rewritten with the | |||||
| `codegen.s`/`emit.s` entries (a sed pass had corrupted their path strings). | |||||
| - `verify-hello-test`: PASS — diagnostics 0, one 18-byte print, HELLO.PRG 118 | |||||
| bytes, `HELLO FROM NOVA128` on row 13. | |||||
| - `verify-parser-test` failed at first on the expression-trace fixture | |||||
| (`prec.n8`, record 28): its `$FB` trace length came back as `$FF92`, so the | |||||
| host walker ran off the end of the dump. The trace itself was fine — a | |||||
| captured dump shows the payload starting `FF 06 00 03 07 0B 0F 9A 98`. | |||||
| - Root cause in the fixture: `dump_trace` emitted the low length byte between | |||||
| the two `sbc`s, and `emit` clears carry, so the high byte lost its borrow | |||||
| ($97-$05=$92 then $6A-$6A-0 = `$FF` instead of `$00`). Both length bytes | |||||
| are now computed before either is emitted. | |||||
| - `verify-parser-test`: PASS — 41 fixtures, `prec.n8` reports 15 traced | |||||
| statements. | |||||
| ## Completion | |||||
| - Tests run: statements, member, control, scalar, hello, and parser fixtures | |||||
| all pass. | |||||
| - Results: statement emission runs and is deterministic; 138 bytes for the | |||||
| fixture source. | |||||
| - Files changed: as listed above. | |||||
| - Remaining risks: the code buffer is still 255 bytes; the print path reads | |||||
| the single `gen_text` capture, so two string literals in one program do not | |||||
| work; the frame prologue is the fixture's, not emitted; nested expressions | |||||
| mix one `EM_TMP`, so deeper shapes need a spill rule; no field stores, no | |||||
| calls, no word values, no arrays. | |||||
| ## Next stepControl flow through `flow.s`, then calls and `self.field` through | |||||
| `members.s`, then report `em_bad` as a diagnostic. | |||||
| Design notes for the control-flow step, worked out while reading `flow.s`: | |||||
| - `flow.s` already writes into the shared code buffer, so `em_reset` can call | |||||
| `fl_init(FL_MODE_EMIT)` instead of `cg_init` and reuse its labels, fixed | |||||
| five-byte branches, and `JMP` fixups for `if`/`while`. `em_end_func` then | |||||
| calls `fl_finish` before the final `RTS`. | |||||
| - Label ids cannot be indexed by block depth alone: a second `if` at the same | |||||
| depth would redefine the label and `fl_label` rejects that. `flow.s` needs | |||||
| `fl_release` (resolve and patch the pending fixups that name a label, then | |||||
| clear its definition) so each closed block hands its two ids back. With two | |||||
| ids per block and `PARSE_MAX_DEPTH = 16`, `FL_LABELS` has to grow from 16 to | |||||
| at least 33. | |||||
| - Conditions should not be materialised as 0/1 in A. `em_bin` can emit `CMP` | |||||
| and remember the comparison in `em_flags`; `em_cond_end` then emits the | |||||
| branch straight from those flags (`>` needs two false branches, `BCC` then | |||||
| `BEQ`), which is also what `while` needs at the top of the loop. | |||||
| - `return` is a plain `RTS` while no frame prologue is emitted (E04-S07 owns | |||||
| `frame_leave`); with no prologue an inline `RTS` is the correct exit. | |||||
| - `for` is not covered by the above: the spec evaluates the end and step | |||||
| expressions once before the first iteration, and the comparison direction | |||||
| depends on the step's runtime sign, so it needs two per-depth temporaries | |||||
| plus a runtime sign test before the loop condition. E04-S07 then owns | |||||
| `sizing.s`, pass-3 addresses, and the emitted `frame_enter`. | |||||
| @@ -17,6 +17,8 @@ $link = Invoke-Nova128Link -Name "hello-test" -BuildDir $out ` | |||||
| (Join-Path $project "src\compiler\names.s"), | (Join-Path $project "src\compiler\names.s"), | ||||
| (Join-Path $project "src\compiler\diagnostics.s"), | (Join-Path $project "src\compiler\diagnostics.s"), | ||||
| (Join-Path $project "src\compiler\parser.s"), | (Join-Path $project "src\compiler\parser.s"), | ||||
| (Join-Path $project "src\compiler\codegen.s"), | |||||
| (Join-Path $project "src\compiler\emit.s"), | |||||
| (Join-Path $project "src\compiler\interfaces.s"), | (Join-Path $project "src\compiler\interfaces.s"), | ||||
| (Join-Path $project "src\compiler\types.s"), | (Join-Path $project "src\compiler\types.s"), | ||||
| (Join-Path $project "src\compiler\output.s"), | (Join-Path $project "src\compiler\output.s"), | ||||
| @@ -66,6 +66,8 @@ $link = Invoke-Nova128Link -Name "parser-test" -BuildDir $out ` | |||||
| (Join-Path $project "src\compiler\names.s"), | (Join-Path $project "src\compiler\names.s"), | ||||
| (Join-Path $project "src\compiler\diagnostics.s"), | (Join-Path $project "src\compiler\diagnostics.s"), | ||||
| (Join-Path $project "src\compiler\parser.s"), | (Join-Path $project "src\compiler\parser.s"), | ||||
| (Join-Path $project "src\compiler\codegen.s"), | |||||
| (Join-Path $project "src\compiler\emit.s"), | |||||
| (Join-Path $project "src\compiler\interfaces.s"), | (Join-Path $project "src\compiler\interfaces.s"), | ||||
| (Join-Path $project "src\compiler\types.s"), | (Join-Path $project "src\compiler\types.s"), | ||||
| (Join-Path $project "tests\parser\parser-test.s")) | (Join-Path $project "tests\parser\parser-test.s")) | ||||
| @@ -0,0 +1,34 @@ | |||||
| # E04-S06: build the statement-emission fixture and a D81 holding STMTS.N8. | |||||
| [CmdletBinding()] | |||||
| param() | |||||
| $ErrorActionPreference = "Stop" | |||||
| $project = Split-Path $PSScriptRoot -Parent | |||||
| Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force | |||||
| $out = Join-Path $project "build\statements-test" | |||||
| New-Item -ItemType Directory -Force -Path $out | Out-Null | |||||
| $link = Invoke-Nova128Link -Name "statements-test" -BuildDir $out ` | |||||
| -Config (Join-Path $project "cfg\compiler.cfg") -Sources @( | |||||
| (Join-Path $project "src\compiler\mmu.s"), | |||||
| (Join-Path $project "src\compiler\diskio.s"), | |||||
| (Join-Path $project "src\compiler\source.s"), | |||||
| (Join-Path $project "src\compiler\lexer.s"), | |||||
| (Join-Path $project "src\compiler\names.s"), | |||||
| (Join-Path $project "src\compiler\diagnostics.s"), | |||||
| (Join-Path $project "src\compiler\parser.s"), | |||||
| (Join-Path $project "src\compiler\interfaces.s"), | |||||
| (Join-Path $project "src\compiler\types.s"), | |||||
| (Join-Path $project "src\compiler\codegen.s"), | |||||
| (Join-Path $project "src\compiler\flow.s"), | |||||
| (Join-Path $project "src\compiler\emit.s"), | |||||
| (Join-Path $project "src\runtime\startup.s"), | |||||
| (Join-Path $project "tests\compiler\statements-test.s")) | |||||
| if ((Get-Nova128Label $link.Labels "test_entry") -ne 0x1C0D) { throw "test_entry is not at `$1C0D" } | |||||
| $pet = Join-Path $out "stmts.pet" | |||||
| [IO.File]::WriteAllBytes($pet, (ConvertTo-Nova128Petscii (Get-Content -Raw -LiteralPath (Join-Path $project "tests\compiler\statements.n8")))) | |||||
| New-Nova128Disk -Path (Join-Path $out "statements-test.d81") -Files @( | |||||
| @{ Name = "statements-test"; Path = $link.Program; Type = "prg" }, | |||||
| @{ Name = "stmts.n8"; Path = $pet; Type = "seq" }) | |||||
| Write-Host ("built {0}" -f $link.Program) | |||||
| @@ -0,0 +1,55 @@ | |||||
| # E04-S06: parse STMTS.N8 in x128, run the emitted body, and check the locals | |||||
| # and the bytes the generated print calls asked for. | |||||
| [CmdletBinding()] | |||||
| param([int]$Port = 6560, [int]$TimeoutSeconds = 180) | |||||
| $ErrorActionPreference = "Stop" | |||||
| $project = Split-Path $PSScriptRoot -Parent | |||||
| Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force | |||||
| & (Join-Path $PSScriptRoot "build-statements-test.ps1") | |||||
| $out = Join-Path $project "build\statements-test" | |||||
| $disk = Join-Path $out "statements-test.d81" | |||||
| $failures = @() | |||||
| $lbl = Get-Content -Raw -LiteralPath (Join-Path $out "statements-test.lbl") | |||||
| if ($lbl -notmatch "([0-9A-Fa-f]{6})\s+\.rec_buf\b") { throw "rec_buf not in the label file" } | |||||
| $recBuf = [Convert]::ToInt32($matches[1], 16) | |||||
| $session = Start-Nova128X128 -Autostart $disk -Drive8Image $disk -Port $Port ` | |||||
| -Screenshot (Join-Path $out "statements-vicii.png") | |||||
| try { | |||||
| $mb = Wait-Nova128Mailbox $session -TimeoutSeconds $TimeoutSeconds | |||||
| $log = Read-Nova128Memory $session $recBuf 16 | |||||
| } finally { | |||||
| Stop-Nova128X128 $session | |||||
| } | |||||
| Write-Host ("mailbox: " + (($mb | ForEach-Object { $_.ToString('X2') }) -join ' ')) | |||||
| Write-Host ("recorded: " + (($log | ForEach-Object { $_.ToString('X2') }) -join ' ')) | |||||
| if ($mb[2] -ne 0x02) { $failures += ("producer $($mb[2]) expected 2 (compiler)") } | |||||
| if ($mb[3] -ne 0xA5) { | |||||
| $failures += ("fixture status {0:X2} failed at stage {1}" -f $mb[3], $mb[4]) | |||||
| } | |||||
| if ($mb[8] -ne 0) { $failures += "diagnostics present: $($mb[8])" } | |||||
| if ($mb[9] -ne 0) { $failures += "emitter error $($mb[9])" } | |||||
| if ($mb[10] -ne 10) { $failures += ("a = {0}, expected 10" -f $mb[10]) } | |||||
| if ($mb[11] -ne 7) { $failures += ("b = {0}, expected 7" -f $mb[11]) } | |||||
| if ($mb[12] -lt 60) { $failures += "emitted only $($mb[12]) bytes" } | |||||
| if ($mb[13] -ne 0) { $failures += "codegen error $($mb[13])" } | |||||
| # print "sum" records SUM + CR; print a records $FF, $0A, CR. | |||||
| $expect = @(0x53, 0x55, 0x4D, 0x0D, 0xFF, 0x0A, 0x0D) | |||||
| if ($mb[14] -ne $expect.Count) { | |||||
| $failures += ("recorded {0} bytes, expected {1}" -f $mb[14], $expect.Count) | |||||
| } else { | |||||
| for ($i = 0; $i -lt $expect.Count; $i++) { | |||||
| if ($log[$i] -ne $expect[$i]) { | |||||
| $failures += ("recorded byte {0} is {1:X2}, expected {2:X2}" -f $i, $log[$i], $expect[$i]) | |||||
| } | |||||
| } | |||||
| } | |||||
| if ($failures) { throw ("statements verification failed:`n " + ($failures -join "`n ")) } | |||||
| Write-Host ("emitted {0} bytes; a=10, b=7; print recorded SUM, 10" -f $mb[12]) | |||||
| Write-Host "verified parser pass-2 statement emission executes in x128" | |||||
| @@ -0,0 +1,21 @@ | |||||
| ; emit.inc - parser-to-codegen statement emission (E04-S06). | |||||
| ; | |||||
| ; The emitter writes into the shared code buffer (codegen.s). Offsets are | |||||
| ; frame displacements relative to FP (frames.inc): locals at FP-1 downward and | |||||
| ; parameters at FP+2 upward. | |||||
| .ifndef EMIT_INC | |||||
| EMIT_INC = 1 | |||||
| EM_TMP = $F7 ; two-byte combine temp; below MB_SELF ($F9) | |||||
| EM_LP = $F5 ; two-byte slot pointer; below MB_SELF | |||||
| EM_ERR_SIZE = 1 ; a value or slot is not one byte | |||||
| EM_ERR_TARGET = 2 ; the assignment target is not a scalar local | |||||
| EM_ERR_OP = 3 ; operator the emitter does not handle yet | |||||
| EM_ERR_STATE = 4 ; missing runtime entry or bad emitter state | |||||
| EM_KIND_LOCAL = 0 | |||||
| EM_KIND_PARAM = 1 | |||||
| .endif | |||||
| @@ -17,6 +17,9 @@ | |||||
| ; cg_stx_zp A = zero-page address | ; cg_stx_zp A = zero-page address | ||||
| ; cg_lda_zpy A = zero-page pointer address, indexed by Y | ; cg_lda_zpy A = zero-page pointer address, indexed by Y | ||||
| ; cg_sta_zpy A = zero-page pointer address, indexed by Y | ; cg_sta_zpy A = zero-page pointer address, indexed by Y | ||||
| ; cg_adc_imm A = value (ADC #value) | |||||
| ; cg_adc_zp A = zero-page address (CLC/ADC (zp)) | |||||
| ; cg_sbc_zp A = zero-page address (SEC/SBC (zp)) | |||||
| ; (there is no LDX (zp),Y: use LDA/TAX for a high byte) | ; (there is no LDX (zp),Y: use LDA/TAX for a high byte) | ||||
| ; cg_lda_abs A/X = address | ; cg_lda_abs A/X = address | ||||
| ; cg_ldx_abs A/X = address | ; cg_ldx_abs A/X = address | ||||
| @@ -48,6 +51,7 @@ | |||||
| .export cg_set_count, cg_count | .export cg_set_count, cg_count | ||||
| .export cg_lda_imm, cg_ldx_imm, cg_ora_imm, cg_ldy_imm, cg_lda_abs, cg_ldx_abs | .export cg_lda_imm, cg_ldx_imm, cg_ora_imm, cg_ldy_imm, cg_lda_abs, cg_ldx_abs | ||||
| .export cg_lda_zp, cg_ldx_zp, cg_sta_zp, cg_stx_zp, cg_lda_zpy, cg_sta_zpy | .export cg_lda_zp, cg_ldx_zp, cg_sta_zp, cg_stx_zp, cg_lda_zpy, cg_sta_zpy | ||||
| .export cg_adc_zp, cg_sbc_zp, cg_adc_imm | |||||
| .export cg_sta_abs, cg_stx_abs, cg_adc_abs, cg_sbc_abs, cg_cmp_abs | .export cg_sta_abs, cg_stx_abs, cg_adc_abs, cg_sbc_abs, cg_cmp_abs | ||||
| .export cg_jsr_abs, cg_clc, cg_sec, cg_rts | .export cg_jsr_abs, cg_clc, cg_sec, cg_rts | ||||
| .export cg_code, cg_len, cg_err | .export cg_code, cg_len, cg_err | ||||
| @@ -151,6 +155,12 @@ cg_sta_zp: | |||||
| cg_stx_zp: | cg_stx_zp: | ||||
| ldy #$86 | ldy #$86 | ||||
| jmp cg_emit_zp | jmp cg_emit_zp | ||||
| cg_adc_zp: | |||||
| ldy #$65 | |||||
| jmp cg_emit_zp | |||||
| cg_sbc_zp: | |||||
| ldy #$E5 | |||||
| jmp cg_emit_zp | |||||
| cg_lda_zpy: | cg_lda_zpy: | ||||
| ldy #$B1 | ldy #$B1 | ||||
| jmp cg_emit_zp | jmp cg_emit_zp | ||||
| @@ -182,6 +192,17 @@ cg_sta_abs: | |||||
| cg_stx_abs: | cg_stx_abs: | ||||
| ldy #$8E | ldy #$8E | ||||
| jmp cg_emit_addr | jmp cg_emit_addr | ||||
| cg_adc_imm: | |||||
| pha | |||||
| lda #$69 | |||||
| jsr cg_put | |||||
| bcs @out | |||||
| pla | |||||
| jmp cg_put | |||||
| @out: pla | |||||
| sec | |||||
| rts | |||||
| cg_adc_abs: | cg_adc_abs: | ||||
| ldy #$6D | ldy #$6D | ||||
| jmp cg_emit_addr | jmp cg_emit_addr | ||||
| @@ -0,0 +1,447 @@ | |||||
| ; emit.s - statement emission from the parser (E04-S06). | |||||
| ; | |||||
| ; The parser calls emit.s while pass 2 parses a body, so statements emit as | |||||
| ; they are type-checked. Bytes go into the shared code buffer (codegen.s); | |||||
| ; the caller sets em_on and parse_file resets the emitter at the start of | |||||
| ; pass 2. | |||||
| ; | |||||
| ; Covered here: byte locals and parameters, number and char constants, name | |||||
| ; loads, byte `+` and `-`, assignment to a scalar local, print of one captured | |||||
| ; string or a byte, and the end of a function. Frame prologue sizing, control | |||||
| ; flow, calls, field access, word values, and arrays remain. | |||||
| ; | |||||
| ; Locals live at FP-1 downward and parameters at FP+2 upward (frames.inc), so | |||||
| ; a slot is addressed with LDY #offset / LDA (FRAME_FP),Y. Offsets are | |||||
| ; assigned in both passes, so pass 1 and pass 2 agree on every slot. | |||||
| ; | |||||
| ; Print output calls the runtime entry points directly (print.s), which the | |||||
| ; link step supplies. | |||||
| .setcpu "6502" | |||||
| .include "../common/tokens.inc" | |||||
| .include "../common/parser.inc" | |||||
| .include "../common/codegen.inc" | |||||
| .include "../common/frames.inc" | |||||
| .include "../common/emit.inc" | |||||
| .include "../common/members.inc" | |||||
| .import cg_init, cg_put, cg_lda_imm, cg_ldx_imm, cg_ldy_imm, cg_lda_zp, cg_lda_zpy, cg_sta_zpy | |||||
| .import cg_sta_zp, cg_adc_zp, cg_sbc_zp, cg_adc_imm, cg_clc, cg_sec, cg_jsr_abs, cg_rts | |||||
| .import gen_text, gen_text_len | |||||
| .export em_on, em_ctx, em_bad, em_last_kind | |||||
| .export em_reset, em_begin_func, em_begin_params, em_local, em_param | |||||
| .export em_expr_name, em_expr_const, em_push_left, em_bin | |||||
| .export em_stmt_begin, em_stmt_end, em_expr_off, em_begin_print | |||||
| .export em_target, em_store_target, em_print, em_end_func | |||||
| .export em_pr_text, em_pr_byte, em_pr_cr | |||||
| .assert EM_TMP + 1 < MB_SELF, error, "EM_TMP overlaps MB_SELF" | |||||
| .segment "CODE" | |||||
| ; Pass 2 start: empty code buffer, no errors, no function, no slots. | |||||
| em_reset: | |||||
| jsr cg_init | |||||
| lda #$00 | |||||
| sta em_bad | |||||
| sta em_ctx | |||||
| sta em_last_kind | |||||
| sta em_tgt | |||||
| sta em_size | |||||
| sta em_left_size | |||||
| sta em_locals | |||||
| sta em_par_bytes | |||||
| lda #$FF | |||||
| sta em_loc_next | |||||
| lda #$02 | |||||
| sta em_par_next | |||||
| rts | |||||
| ; A body starts: fresh local slots. Parameter offsets were assigned while the | |||||
| ; signature was parsed and are kept. | |||||
| em_begin_func: | |||||
| lda #$FF | |||||
| sta em_loc_next | |||||
| lda #$00 | |||||
| sta em_locals | |||||
| sta em_ctx | |||||
| sta em_last_kind | |||||
| sta em_tgt | |||||
| rts | |||||
| ; A signature starts: parameters begin at FP+2. | |||||
| em_begin_params: | |||||
| lda #$02 | |||||
| sta em_par_next | |||||
| sta em_par_bytes | |||||
| rts | |||||
| ; A = slot bytes, X = name ID of a local: assign the next local slot. | |||||
| em_local: | |||||
| ldy #EM_KIND_LOCAL | |||||
| jmp em_decl | |||||
| ; A = slot bytes, X = name ID of a parameter: assign the next parameter slot. | |||||
| em_param: | |||||
| ldy #EM_KIND_PARAM | |||||
| jmp em_decl | |||||
| ; A = slot bytes, X = name ID, Y = EM_KIND_*. A zero size means the type has | |||||
| ; no frame slot here; the name is simply not addressable. | |||||
| em_decl: | |||||
| stx em_nid | |||||
| sta em_size | |||||
| sty em_kind | |||||
| lda em_size | |||||
| beq @done | |||||
| lda em_kind | |||||
| bne @param | |||||
| lda em_loc_next ; locals grow down from FP-1 | |||||
| sec | |||||
| sbc em_size | |||||
| sta em_loc_next | |||||
| ldx em_nid | |||||
| sta em_off,x | |||||
| lda em_locals | |||||
| clc | |||||
| adc em_size | |||||
| sta em_locals | |||||
| jmp @size | |||||
| @param: ldx em_nid | |||||
| lda em_par_next ; parameters grow up from FP+2 | |||||
| sta em_off,x | |||||
| clc | |||||
| adc em_size | |||||
| sta em_par_next | |||||
| sta em_par_bytes | |||||
| @size: ldx em_nid | |||||
| lda em_size | |||||
| sta em_sz,x | |||||
| @done: rts | |||||
| ; A statement's value starts: operands emit. The target captured by | |||||
| ; em_target just before this call is kept until the statement ends. | |||||
| em_stmt_begin: | |||||
| lda #$01 | |||||
| sta em_ctx | |||||
| sta em_last_kind | |||||
| rts | |||||
| ; The statement is complete: no more operand emission. The target stays set | |||||
| ; until em_store_target consumes it. | |||||
| em_stmt_end: | |||||
| lda #$00 | |||||
| sta em_ctx | |||||
| rts | |||||
| em_expr_off: | |||||
| lda #$00 | |||||
| sta em_ctx | |||||
| rts | |||||
| ; Remember where the print text buffer stood, so em_print can tell a captured | |||||
| ; string literal from a value. | |||||
| em_begin_print: | |||||
| lda gen_text_len | |||||
| sta em_txt_before | |||||
| rts | |||||
| ; X = name ID. Record it as the last primary, and load it when emitting. | |||||
| em_expr_name: | |||||
| stx em_last_nid | |||||
| lda #$01 | |||||
| sta em_last_kind | |||||
| lda em_ctx | |||||
| beq @done | |||||
| lda em_sz,x | |||||
| beq @size | |||||
| cmp #$01 | |||||
| bne @size | |||||
| lda em_off,x | |||||
| jsr em_point_slot | |||||
| bcs @fail | |||||
| lda #EM_LP | |||||
| jsr cg_lda_zpy | |||||
| bcs @fail | |||||
| lda #$01 | |||||
| sta em_size | |||||
| @done: rts | |||||
| @size: lda #EM_ERR_SIZE | |||||
| sta em_bad | |||||
| clc | |||||
| rts | |||||
| @fail: jmp em_fail | |||||
| ; A/X = a constant's low/high byte. A two-byte constant does not fit a byte | |||||
| ; expression yet. | |||||
| em_expr_const: | |||||
| sta em_low | |||||
| stx em_high | |||||
| lda em_ctx | |||||
| beq @done | |||||
| lda em_high | |||||
| bne @size | |||||
| lda em_low | |||||
| jsr cg_lda_imm | |||||
| bcs @fail | |||||
| lda #$01 | |||||
| sta em_size | |||||
| @done: rts | |||||
| @size: lda #EM_ERR_SIZE | |||||
| sta em_bad | |||||
| clc | |||||
| rts | |||||
| @fail: jmp em_fail | |||||
| ; The left operand is complete (in A at run time): keep it on the stack while | |||||
| ; the right operand is emitted. | |||||
| em_push_left: | |||||
| lda em_ctx | |||||
| beq @done | |||||
| lda em_size | |||||
| beq @size | |||||
| cmp #$01 | |||||
| bne @size | |||||
| lda em_size | |||||
| sta em_left_size | |||||
| lda #$48 ; PHA | |||||
| jsr cg_put | |||||
| bcs @fail | |||||
| clc | |||||
| rts | |||||
| @size: lda #EM_ERR_SIZE | |||||
| sta em_bad | |||||
| clc | |||||
| rts | |||||
| @fail: jmp em_fail | |||||
| @done: rts | |||||
| ; A = operator token. The right operand is in A at run time and the left is on | |||||
| ; the hardware stack; combine byte values only. | |||||
| em_bin: | |||||
| sta em_op | |||||
| lda em_ctx | |||||
| beq @done | |||||
| lda em_left_size | |||||
| beq @size | |||||
| lda em_size | |||||
| beq @size | |||||
| lda #EM_TMP | |||||
| jsr cg_sta_zp | |||||
| bcs @fail | |||||
| lda #$68 ; PLA | |||||
| jsr cg_put | |||||
| bcs @fail | |||||
| lda em_op | |||||
| cmp #TOK_PLUS | |||||
| beq @plus | |||||
| cmp #TOK_MINUS | |||||
| beq @minus | |||||
| lda #EM_ERR_OP | |||||
| sta em_bad | |||||
| clc | |||||
| rts | |||||
| @plus: jsr cg_clc | |||||
| bcs @fail | |||||
| lda #EM_TMP | |||||
| jsr cg_adc_zp | |||||
| bcs @fail | |||||
| jmp @ok | |||||
| @minus: jsr cg_sec | |||||
| bcs @fail | |||||
| lda #EM_TMP | |||||
| jsr cg_sbc_zp | |||||
| bcs @fail | |||||
| @ok: lda #$01 | |||||
| sta em_size | |||||
| clc | |||||
| rts | |||||
| @size: lda #EM_ERR_SIZE | |||||
| sta em_bad | |||||
| clc | |||||
| rts | |||||
| @fail: jmp em_fail | |||||
| @done: rts | |||||
| ; A = slot offset: emit EM_LP = FRAME_FP + offset, sign extending a negative | |||||
| ; low byte, and leave Y = 0. Locals sit below FP, which indexed-indirect | |||||
| ; addressing cannot reach with a plain LDY #offset. | |||||
| em_point_slot: | |||||
| sta em_off_b | |||||
| lda #FRAME_FP | |||||
| jsr cg_lda_zp | |||||
| bcs @out | |||||
| jsr cg_clc | |||||
| bcs @out | |||||
| lda em_off_b | |||||
| jsr cg_adc_imm | |||||
| bcs @out | |||||
| lda #EM_LP | |||||
| jsr cg_sta_zp | |||||
| bcs @out | |||||
| lda #FRAME_FP + 1 | |||||
| jsr cg_lda_zp | |||||
| bcs @out | |||||
| lda em_off_b | |||||
| bmi @negative | |||||
| lda #$00 | |||||
| jmp @sign | |||||
| @negative: | |||||
| lda #$FF | |||||
| @sign: jsr cg_adc_imm | |||||
| bcs @out | |||||
| lda #EM_LP + 1 | |||||
| jsr cg_sta_zp | |||||
| bcs @out | |||||
| lda #$00 | |||||
| jmp cg_ldy_imm | |||||
| @out: sec | |||||
| rts | |||||
| ; The assignment target is the statement's first primary (em_last_kind / | |||||
| ; em_last_nid). Accept a one-byte local only. | |||||
| em_target: | |||||
| lda em_on | |||||
| beq @done | |||||
| lda em_last_kind | |||||
| cmp #$01 | |||||
| bne @bad | |||||
| ldx em_last_nid | |||||
| beq @bad | |||||
| lda em_sz,x | |||||
| beq @bad | |||||
| cmp #$01 | |||||
| bne @bad | |||||
| stx em_tgt | |||||
| clc | |||||
| rts | |||||
| @bad: lda #EM_ERR_TARGET | |||||
| sta em_bad | |||||
| @done: clc | |||||
| rts | |||||
| ; Store the value in A into the captured target. | |||||
| em_store_target: | |||||
| lda em_on | |||||
| beq @done | |||||
| lda em_tgt | |||||
| beq @done | |||||
| lda #$48 ; PHA: em_point_slot clobbers A | |||||
| jsr cg_put | |||||
| bcs @fail | |||||
| lda em_tgt | |||||
| tax | |||||
| lda em_off,x | |||||
| jsr em_point_slot | |||||
| bcs @fail | |||||
| lda #$68 ; PLA: the value, kept over the pointer | |||||
| jsr cg_put | |||||
| bcs @fail | |||||
| lda #EM_LP | |||||
| jsr cg_sta_zpy | |||||
| bcs @fail | |||||
| lda #$00 | |||||
| sta em_tgt | |||||
| @done: clc | |||||
| rts | |||||
| @fail: jmp em_fail | |||||
| ; Print the value just parsed: the captured string literal when this statement | |||||
| ; captured one, else the byte in A. | |||||
| em_print: | |||||
| lda em_on | |||||
| beq @done | |||||
| lda gen_text_len | |||||
| cmp em_txt_before | |||||
| beq @byte | |||||
| lda em_pr_text | |||||
| ora em_pr_text+1 | |||||
| beq @state | |||||
| lda #<gen_text | |||||
| jsr cg_lda_imm | |||||
| bcs @fail | |||||
| lda #>gen_text | |||||
| jsr cg_ldx_imm | |||||
| bcs @fail | |||||
| lda gen_text_len | |||||
| jsr cg_ldy_imm | |||||
| bcs @fail | |||||
| lda #<em_tramp_text | |||||
| ldx #>em_tramp_text | |||||
| jsr cg_jsr_abs | |||||
| bcs @fail | |||||
| jmp @cr | |||||
| @byte: lda em_pr_byte | |||||
| ora em_pr_byte+1 | |||||
| beq @state | |||||
| lda #<em_tramp_byte | |||||
| ldx #>em_tramp_byte | |||||
| jsr cg_jsr_abs | |||||
| bcs @fail | |||||
| @cr: lda em_pr_cr | |||||
| ora em_pr_cr+1 | |||||
| beq @state | |||||
| lda #<em_tramp_cr | |||||
| ldx #>em_tramp_cr | |||||
| jsr cg_jsr_abs | |||||
| bcs @fail | |||||
| clc | |||||
| rts | |||||
| @state: lda #EM_ERR_STATE | |||||
| sta em_bad | |||||
| clc | |||||
| rts | |||||
| @done: clc | |||||
| rts | |||||
| @fail: jmp em_fail | |||||
| ; The runtime print entry points live in cells the caller fills, so a test | |||||
| ; can record what the generated code asked to print. | |||||
| em_tramp_text: jmp (em_pr_text) | |||||
| em_tramp_byte: jmp (em_pr_byte) | |||||
| em_tramp_cr: jmp (em_pr_cr) | |||||
| ; End of a body: return to the caller. | |||||
| em_end_func: | |||||
| lda em_on | |||||
| beq @done | |||||
| jsr cg_rts | |||||
| bcs @fail | |||||
| @done: clc | |||||
| rts | |||||
| @fail: jmp em_fail | |||||
| ; An emitter failure is the caller's signal; cg_err holds the detail. | |||||
| em_fail: | |||||
| lda #EM_ERR_STATE | |||||
| sta em_bad | |||||
| clc | |||||
| rts | |||||
| .segment "BSS" | |||||
| .align 2 ; jmp (cell) must not straddle a page | |||||
| em_pr_text: .res 2 | |||||
| em_pr_byte: .res 2 | |||||
| em_pr_cr: .res 2 | |||||
| em_on: .res 1 | |||||
| em_ctx: .res 1 | |||||
| em_bad: .res 1 | |||||
| em_last_kind: .res 1 | |||||
| em_last_nid: .res 1 | |||||
| em_tgt: .res 1 | |||||
| em_nid: .res 1 | |||||
| em_kind: .res 1 | |||||
| em_size: .res 1 | |||||
| em_left_size: .res 1 | |||||
| em_loc_next: .res 1 | |||||
| em_par_next: .res 1 | |||||
| em_locals: .res 1 | |||||
| em_par_bytes: .res 1 | |||||
| em_low: .res 1 | |||||
| em_high: .res 1 | |||||
| em_op: .res 1 | |||||
| em_txt_before: .res 1 | |||||
| em_off_b: .res 1 | |||||
| em_off: .res 256 | |||||
| em_sz: .res 256 | |||||
| @@ -56,6 +56,11 @@ | |||||
| .import ty_assign, ty_binary, ty_unary | .import ty_assign, ty_binary, ty_unary | ||||
| .import ty_arg_sc1, ty_arg_sc2, ty_arg_name1, ty_arg_num2 | .import ty_arg_sc1, ty_arg_sc2, ty_arg_name1, ty_arg_num2 | ||||
| .import assign_slots, check_interfaces | .import assign_slots, check_interfaces | ||||
| .import em_reset, em_on, em_ctx, em_last_kind | |||||
| .import em_begin_func, em_begin_params, em_local, em_param | |||||
| .import em_stmt_begin, em_stmt_end, em_expr_off, em_begin_print | |||||
| .import em_expr_name, em_expr_const, em_push_left, em_bin | |||||
| .import em_target, em_store_target, em_print, em_end_func | |||||
| .export parse_file, tb_get, tb_set, mem_count, par_count | .export parse_file, tb_get, tb_set, mem_count, par_count | ||||
| .export gen_text, gen_text_len, gen_print_count, gen_bad | .export gen_text, gen_text_len, gen_print_count, gen_bad | ||||
| @@ -112,6 +117,7 @@ parse_file: | |||||
| rts | rts | ||||
| : lda #$02 | : lda #$02 | ||||
| sta pass | sta pass | ||||
| jsr em_reset | |||||
| jsr parse_pass | jsr parse_pass | ||||
| rts | rts | ||||
| @@ -403,6 +409,7 @@ parse_program: | |||||
| ; C=1 if the header had an error (the line has been skipped). | ; C=1 if the header had an error (the line has been skipped). | ||||
| parse_signature: | parse_signature: | ||||
| jsr scope_clear | jsr scope_clear | ||||
| jsr em_begin_params | |||||
| lda #$00 | lda #$00 | ||||
| sta sig_params | sta sig_params | ||||
| sta ret_kind ; RET_UNKNOWN until the header parses | sta ret_kind ; RET_UNKNOWN until the header parses | ||||
| @@ -478,6 +485,9 @@ parse_signature: | |||||
| bcs @skip | bcs @skip | ||||
| jsr no_array_sig | jsr no_array_sig | ||||
| jsr set_local_type | jsr set_local_type | ||||
| jsr type_bytes | |||||
| ldx last_local | |||||
| jsr em_param | |||||
| jsr add_param | jsr add_param | ||||
| IFTOK TOK_COMMA, @more | IFTOK TOK_COMMA, @more | ||||
| IFTOK TOK_RPAREN, @close | IFTOK TOK_RPAREN, @close | ||||
| @@ -921,6 +931,7 @@ parse_body: | |||||
| lda #$01 | lda #$01 | ||||
| sta depth | sta depth | ||||
| sta refs_on ; names resolve, except in the methods of | sta refs_on ; names resolve, except in the methods of | ||||
| jsr em_begin_func | |||||
| lda block_stack ; a class whose name failed to parse | lda block_stack ; a class whose name failed to parse | ||||
| cmp #KW_METHOD | cmp #KW_METHOD | ||||
| bne @loop | bne @loop | ||||
| @@ -978,7 +989,9 @@ parse_body: | |||||
| sta depth | sta depth | ||||
| @matched: | @matched: | ||||
| dec depth | dec depth | ||||
| jsr close_block | |||||
| bne :+ | |||||
| jsr em_end_func | |||||
| : jsr close_block | |||||
| jsr next | jsr next | ||||
| jsr expect_eol | jsr expect_eol | ||||
| lda depth | lda depth | ||||
| @@ -1088,6 +1101,9 @@ parse_var: | |||||
| bcc :+ | bcc :+ | ||||
| jmp skip_line | jmp skip_line | ||||
| : jsr set_local_type | : jsr set_local_type | ||||
| jsr type_bytes | |||||
| ldx last_local | |||||
| jsr em_local | |||||
| jmp expect_eol | jmp expect_eol | ||||
| ; ----------------------------------------------------------- statements | ; ----------------------------------------------------------- statements | ||||
| @@ -1133,6 +1149,17 @@ parse_statement: | |||||
| sta gen_in_print | sta gen_in_print | ||||
| lda gen_text_len | lda gen_text_len | ||||
| sta gen_len_before | sta gen_len_before | ||||
| lda em_on | |||||
| beq @print_value | |||||
| jsr em_begin_print | |||||
| jsr em_stmt_begin | |||||
| jsr value | |||||
| jsr em_stmt_end | |||||
| jsr check_print | |||||
| lda #$00 | |||||
| sta gen_in_print | |||||
| jsr em_print | |||||
| jmp end_stmt | |||||
| @print_value: | @print_value: | ||||
| jsr value | jsr value | ||||
| jsr check_print | jsr check_print | ||||
| @@ -1241,6 +1268,7 @@ end_stmt: | |||||
| ; Assignment `target = expr` or a call `expr.method(args)`. | ; Assignment `target = expr` or a call `expr.method(args)`. | ||||
| stmt_expr: | stmt_expr: | ||||
| jsr em_expr_off ; the target is not a value | |||||
| jsr parse_postfix | jsr parse_postfix | ||||
| lda tok_kind | lda tok_kind | ||||
| cmp #TOK_EQ | cmp #TOK_EQ | ||||
| @@ -1260,8 +1288,23 @@ stmt_expr: | |||||
| jmp syn_error_col | jmp syn_error_col | ||||
| : ldx #$00 ; slot 0: the target's type | : ldx #$00 ; slot 0: the target's type | ||||
| jsr save_slot | jsr save_slot | ||||
| lda pass ; pass 1 only checks the types | |||||
| cmp #$02 | |||||
| bne @assign_pass1 | |||||
| jsr em_target | |||||
| jsr em_stmt_begin | |||||
| jsr value | |||||
| jsr em_stmt_end | |||||
| jmp @assign_typed | |||||
| @assign_pass1: | |||||
| jsr value | jsr value | ||||
| @assign_typed: | |||||
| jsr check_slot0 | jsr check_slot0 | ||||
| lda pass | |||||
| cmp #$02 | |||||
| bne @assign_done | |||||
| jsr em_store_target | |||||
| @assign_done: | |||||
| jmp end_stmt | jmp end_stmt | ||||
| ; Skip the keyword or `=` and parse the expression after it (val_col). | ; Skip the keyword or `=` and parse the expression after it (val_col). | ||||
| @@ -1449,6 +1492,7 @@ parse_expr: | |||||
| sta ex_prec,x | sta ex_prec,x | ||||
| jsr note_op | jsr note_op | ||||
| jsr save_slot ; the left operand | jsr save_slot ; the left operand | ||||
| jsr em_push_left | |||||
| jsr next | jsr next | ||||
| ldx ex_depth | ldx ex_depth | ||||
| lda ex_prec,x | lda ex_prec,x | ||||
| @@ -1460,6 +1504,9 @@ parse_expr: | |||||
| ora #TR_BINARY | ora #TR_BINARY | ||||
| jsr trace_byte | jsr trace_byte | ||||
| ldx ex_depth | ldx ex_depth | ||||
| lda ex_op,x | |||||
| jsr em_bin | |||||
| ldx ex_depth | |||||
| jsr load_tt | jsr load_tt | ||||
| ldx ex_depth | ldx ex_depth | ||||
| lda ex_op,x | lda ex_op,x | ||||
| @@ -1549,7 +1596,9 @@ parse_postfix: | |||||
| jsr chain_none | jsr chain_none | ||||
| lda tok_kind | lda tok_kind | ||||
| cmp #TOK_LPAREN ; a group is traced by its contents | cmp #TOK_LPAREN ; a group is traced by its contents | ||||
| beq @group | |||||
| bne :+ | |||||
| jmp @group | |||||
| : | |||||
| lda tok_col ; trace: the operand, by its column | lda tok_col ; trace: the operand, by its column | ||||
| jsr trace_byte | jsr trace_byte | ||||
| lda tok_kind | lda tok_kind | ||||
| @@ -1588,7 +1637,25 @@ parse_postfix: | |||||
| jsr gen_capture | jsr gen_capture | ||||
| pla | pla | ||||
| tax | tax | ||||
| : lda lit_types,x | |||||
| : lda #$00 ; a literal ends a name chain | |||||
| sta em_last_kind | |||||
| lda em_ctx | |||||
| beq @lit_type | |||||
| lda tok_kind | |||||
| cmp #TOK_NUMBER | |||||
| beq @lit_const | |||||
| cmp #TOK_CHAR | |||||
| bne @lit_type | |||||
| @lit_const: | |||||
| txa | |||||
| pha | |||||
| lda tok_value | |||||
| ldx tok_value+1 | |||||
| jsr em_expr_const | |||||
| pla | |||||
| tax | |||||
| @lit_type: | |||||
| lda lit_types,x | |||||
| sta sc_base | sta sc_base | ||||
| lda tok_value ; a number's value (others ignore it) | lda tok_value ; a number's value (others ignore it) | ||||
| sta sc_val | sta sc_val | ||||
| @@ -1599,12 +1666,18 @@ parse_postfix: | |||||
| @name: jsr resolve_name | @name: jsr resolve_name | ||||
| lda #EX_PLACE | lda #EX_PLACE | ||||
| sta ex_last | sta ex_last | ||||
| ldx rn_id | |||||
| jsr em_expr_name | |||||
| jsr next | jsr next | ||||
| jmp @post | jmp @post | ||||
| @self: jsr self_ref | |||||
| @self: lda #$00 | |||||
| sta em_last_kind | |||||
| jsr self_ref | |||||
| jsr next | jsr next | ||||
| jmp @post | jmp @post | ||||
| @group: jsr next ; the group has its contents' type | |||||
| @group: lda #$00 | |||||
| sta em_last_kind | |||||
| jsr next ; the group has its contents' type | |||||
| jsr parse_full | jsr parse_full | ||||
| lda #EX_OTHER | lda #EX_OTHER | ||||
| sta ex_last | sta ex_last | ||||
| @@ -1615,7 +1688,9 @@ parse_postfix: | |||||
| jmp syn_error | jmp syn_error | ||||
| : jsr next | : jsr next | ||||
| jmp @post | jmp @post | ||||
| @new: jsr next ; new Name(args) | |||||
| @new: lda #$00 | |||||
| sta em_last_kind | |||||
| jsr next ; new Name(args) | |||||
| lda tok_kind | lda tok_kind | ||||
| cmp #TOK_IDENT | cmp #TOK_IDENT | ||||
| beq :+ | beq :+ | ||||
| @@ -1642,6 +1717,8 @@ parse_postfix: | |||||
| beq @index | beq @index | ||||
| rts | rts | ||||
| @member: | @member: | ||||
| lda #$00 | |||||
| sta em_last_kind | |||||
| jsr next | jsr next | ||||
| lda tok_kind ; member names may be keywords (D121) | lda tok_kind ; member names may be keywords (D121) | ||||
| cmp #TOK_IDENT | cmp #TOK_IDENT | ||||
| @@ -1659,7 +1736,9 @@ parse_postfix: | |||||
| lda #EX_PLACE | lda #EX_PLACE | ||||
| sta ex_last | sta ex_last | ||||
| jmp @post | jmp @post | ||||
| @call: ldx ex_depth ; sc = the result, kept over the arguments | |||||
| @call: lda #$00 | |||||
| sta em_last_kind | |||||
| ldx ex_depth ; sc = the result, kept over the arguments | |||||
| jsr save_slot | jsr save_slot | ||||
| jsr parse_args | jsr parse_args | ||||
| ldx ex_depth | ldx ex_depth | ||||
| @@ -1667,7 +1746,9 @@ parse_postfix: | |||||
| lda #EX_CALL | lda #EX_CALL | ||||
| sta ex_last | sta ex_last | ||||
| jmp @post | jmp @post | ||||
| @index: jsr index_base ; sc = the array, or unknown | |||||
| @index: lda #$00 | |||||
| sta em_last_kind | |||||
| jsr index_base ; sc = the array, or unknown | |||||
| ldx ex_depth | ldx ex_depth | ||||
| jsr save_slot | jsr save_slot | ||||
| jsr next | jsr next | ||||
| @@ -2439,6 +2520,30 @@ set_local_type: | |||||
| sta loc_tlen_hi,y | sta loc_tlen_hi,y | ||||
| @done: rts | @done: rts | ||||
| ; A = frame slot bytes for ty_*: byte-sized types take one, words two, and | |||||
| ; everything else (arrays, references) none here. | |||||
| type_bytes: | |||||
| lda ty_len | |||||
| ora ty_len+1 | |||||
| bne @zero | |||||
| lda ty_base | |||||
| cmp #TY_BOOLEAN | |||||
| beq @one | |||||
| cmp #TY_BYTE | |||||
| beq @one | |||||
| cmp #TY_CHAR | |||||
| beq @one | |||||
| cmp #TY_INTEGER | |||||
| beq @two | |||||
| cmp #TY_WORD | |||||
| beq @two | |||||
| @zero: lda #$00 | |||||
| rts | |||||
| @one: lda #$01 | |||||
| rts | |||||
| @two: lda #$02 | |||||
| rts | |||||
| ; A = name ID. Y = scope byte, X = bit; A = that bit if set (Z=0) else 0. | ; A = name ID. Y = scope byte, X = bit; A = that bit if set (Z=0) else 0. | ||||
| scope_test: | scope_test: | ||||
| tax | tax | ||||
| @@ -0,0 +1,227 @@ | |||||
| ; E04-S06: parse statements.n8, execute the code the parser emitted, and | |||||
| ; report the resulting locals and printed bytes. | |||||
| ; | |||||
| ; The fixture runs in the compiler mapping, loads STMTS.N8, parses it with | |||||
| ; emission on, and JSRs cg_code in place. The emitted body reaches its locals | |||||
| ; through FRAME_FP, so the fixture points FP at its own buffer. Print calls go | |||||
| ; through the em_pr_* cells, which the fixture fills with recorders: the test | |||||
| ; sees exactly which bytes the generated code asked to print, without | |||||
| ; depending on CHROUT or the screen mapping. | |||||
| ; | |||||
| ; Mailbox data: +0 diagnostics, +1 em_bad, +2 a, +3 b, +4 cg_len, +5 cg_err, | |||||
| ; +6 recorded length. FAIL code is the stage: 1 load, 2 diagnostics, 3 emitter, | |||||
| ; 4 cg. | |||||
| ; | |||||
| ; The frame prologue is not emitted yet, so the fixture owns FP; E04-S07's | |||||
| ; sizing pass will emit frame_enter with the measured local bytes. | |||||
| .setcpu "6502" | |||||
| .include "../../src/common/c128.inc" | |||||
| .include "../../src/common/mailbox.inc" | |||||
| .include "../../src/common/formats.inc" | |||||
| .include "../../src/common/frames.inc" | |||||
| .import mmu_init, mmu_bank0, mmu_shutdown | |||||
| .import src_load, src_dos_code, src_kernal_err | |||||
| .import names_init | |||||
| .import diag_init, diag_finish, diag_screen | |||||
| .import parse_file | |||||
| .import em_on, em_bad, em_pr_text, em_pr_byte, em_pr_cr | |||||
| .import cg_code, cg_len, cg_err | |||||
| ; A conditional branch to a far error path: near branch over an absolute JMP. | |||||
| .macro ON_ZERO target | |||||
| beq :+ | |||||
| jmp target | |||||
| : | |||||
| .endmacro | |||||
| .segment "LOADADDR" | |||||
| .word $1C01 | |||||
| .segment "EXEHDR" | |||||
| .word basic_end | |||||
| .word 10 | |||||
| .byte $9E | |||||
| .byte "7181" | |||||
| .byte $00 | |||||
| basic_end: | |||||
| .word $0000 | |||||
| .segment "MAILBOX" | |||||
| .res MB_SIZE | |||||
| .segment "STARTUP" | |||||
| .export test_entry | |||||
| test_entry: | |||||
| php | |||||
| sei | |||||
| ldx #MB_SIZE - 1 | |||||
| lda #$00 | |||||
| @clr: sta MB_BASE,x | |||||
| dex | |||||
| bpl @clr | |||||
| lda #MB_MAGIC0_VALUE | |||||
| sta MB_MAGIC0 | |||||
| lda #MB_MAGIC1_VALUE | |||||
| sta MB_MAGIC1 | |||||
| lda #MB_PROD_COMPILER | |||||
| sta MB_PRODUCER | |||||
| lda #MB_ST_RUNNING | |||||
| sta MB_STATUS | |||||
| jsr mmu_init | |||||
| jsr mmu_bank0 | |||||
| lda #<name_src | |||||
| ldx #>name_src | |||||
| ldy #name_src_len | |||||
| jsr src_load | |||||
| cmp #$00 | |||||
| ON_ZERO @fail_load | |||||
| jsr names_init | |||||
| lda #<name_src | |||||
| ldx #>name_src | |||||
| ldy #name_src_len | |||||
| jsr diag_init | |||||
| lda #$00 | |||||
| sta diag_screen | |||||
| lda #$01 | |||||
| sta em_on ; pass 2 emits statements | |||||
| lda #<rec_text ; record what the program prints | |||||
| sta em_pr_text | |||||
| lda #>rec_text | |||||
| sta em_pr_text+1 | |||||
| lda #<rec_byte | |||||
| sta em_pr_byte | |||||
| lda #>rec_byte | |||||
| sta em_pr_byte+1 | |||||
| lda #<rec_cr | |||||
| sta em_pr_cr | |||||
| lda #>rec_cr | |||||
| sta em_pr_cr+1 | |||||
| lda #$00 | |||||
| sta rec_len | |||||
| jsr parse_file | |||||
| jsr diag_finish | |||||
| sta diag_count | |||||
| lda diag_count | |||||
| sta MB_DATA+0 | |||||
| lda em_bad | |||||
| sta MB_DATA+1 | |||||
| lda cg_len | |||||
| sta MB_DATA+4 | |||||
| lda cg_err | |||||
| sta MB_DATA+5 | |||||
| lda rec_len | |||||
| sta MB_DATA+6 | |||||
| lda diag_count | |||||
| ON_ZERO @fail_diag | |||||
| lda em_bad | |||||
| ON_ZERO @fail_emit | |||||
| lda cg_err | |||||
| ON_ZERO @fail_cg | |||||
| lda #<em_fp ; the fixture owns the frame base | |||||
| sta FRAME_FP | |||||
| lda #>em_fp | |||||
| sta FRAME_FP+1 | |||||
| jsr cg_code ; run the emitted body | |||||
| lda em_fp - 2 ; a: the first local (FP-2) | |||||
| sta MB_DATA+2 | |||||
| lda em_fp - 3 ; b: the second local (FP-3) | |||||
| sta MB_DATA+3 | |||||
| lda rec_len | |||||
| sta MB_DATA+6 | |||||
| lda #MB_ST_PASS | |||||
| sta MB_STATUS | |||||
| jmp @done | |||||
| @fail_load: | |||||
| sta MB_DATA+5 ; src_load result | |||||
| lda src_dos_code | |||||
| sta MB_DATA+6 | |||||
| lda src_kernal_err | |||||
| sta MB_DATA+7 | |||||
| lda #1 | |||||
| jmp @fail | |||||
| @fail_diag: | |||||
| lda #2 | |||||
| jmp @fail | |||||
| @fail_emit: | |||||
| lda #3 | |||||
| jmp @fail | |||||
| @fail_cg: | |||||
| lda #4 | |||||
| @fail: sta MB_CODE | |||||
| lda #$00 | |||||
| sta MB_CODE+1 | |||||
| lda #MB_ST_FAIL | |||||
| sta MB_STATUS | |||||
| @done: jsr mmu_shutdown | |||||
| plp | |||||
| rts | |||||
| ; ------------------------------------------------------------- recorders | |||||
| ; A/X = address, Y = length: record the literal bytes. | |||||
| rec_text: | |||||
| sta rec_ptr | |||||
| stx rec_ptr+1 | |||||
| sty rec_cnt | |||||
| ldy #$00 | |||||
| @loop: cpy rec_cnt | |||||
| beq @done | |||||
| lda (rec_ptr),y | |||||
| sty rec_y | |||||
| jsr rec_log | |||||
| ldy rec_y | |||||
| iny | |||||
| bne @loop | |||||
| @done: rts | |||||
| ; A = byte value: record $FF and the value, so a value print is distinct from | |||||
| ; text. | |||||
| rec_byte: | |||||
| pha | |||||
| lda #$FF | |||||
| jsr rec_log | |||||
| pla | |||||
| jmp rec_log | |||||
| ; A newline follows every print statement. | |||||
| rec_cr: | |||||
| lda #$0D | |||||
| jmp rec_log | |||||
| ; A = byte to append. | |||||
| rec_log: | |||||
| ldy rec_len | |||||
| cpy #32 | |||||
| bcs @full | |||||
| sta rec_buf,y | |||||
| inc rec_len | |||||
| @full: rts | |||||
| .segment "ZEROPAGE" | |||||
| rec_ptr: .res 2 | |||||
| .segment "RODATA" | |||||
| name_src: .byte "STMTS.N8" | |||||
| name_src_len = * - name_src | |||||
| .segment "BSS" | |||||
| diag_count: .res 1 | |||||
| rec_cnt: .res 1 | |||||
| rec_y: .res 1 | |||||
| .export rec_len | |||||
| rec_len: .res 1 | |||||
| .export rec_buf | |||||
| rec_buf: .res 32 | |||||
| em_frame: .res 128 | |||||
| em_fp = em_frame + 64 | |||||
| .export em_fp | |||||
| @@ -0,0 +1,10 @@ | |||||
| // E04-S06 emission fixture: byte locals, assignment, arithmetic, print. | |||||
| program stmts | |||||
| var a as byte | |||||
| var b as byte | |||||
| a = 7 | |||||
| b = a | |||||
| a = a + 3 | |||||
| print "sum" | |||||
| print a | |||||
| end program | |||||
| @@ -435,14 +435,16 @@ count_fields: | |||||
| dump_trace: | dump_trace: | ||||
| lda #$FB | lda #$FB | ||||
| jsr emit | jsr emit | ||||
| sec | |||||
| sec ; both bytes before emit: it clears C | |||||
| lda ex_tp | lda ex_tp | ||||
| sbc #<trace_buf | sbc #<trace_buf | ||||
| sta trace_len | sta trace_len | ||||
| jsr emit | |||||
| lda ex_tp+1 | lda ex_tp+1 | ||||
| sbc #>trace_buf | sbc #>trace_buf | ||||
| sta trace_len+1 | sta trace_len+1 | ||||
| lda trace_len | |||||
| jsr emit | |||||
| lda trace_len+1 | |||||
| jsr emit | jsr emit | ||||
| lda #<trace_buf | lda #<trace_buf | ||||
| sta fx_tmp | sta fx_tmp | ||||
| @@ -0,0 +1,11 @@ | |||||
| work/ | |||||
| out/ | |||||
| opt/ | |||||
| optlib/ | |||||
| build/ | |||||
| res_*/ | |||||
| lib/ | |||||
| *.pet | |||||
| *.png | |||||
| POWERc-opt.d81 | |||||
| __pycache__/ | |||||
| @@ -0,0 +1 @@ | |||||
| e7f60c3277cab57332cb61910a263d5709c52b1a62dadc89dafc22af85b4c1a8 *reference/POWERc.d81 | |||||
| @@ -0,0 +1,112 @@ | |||||
| # Power C — reverse engineering and improvements | |||||
| Goal: faster code and smaller programs from the Spinnaker **Power C** compiler on | |||||
| `reference/POWERc.d81`, **without touching the original**. Everything here works on copies | |||||
| (`reference/POWERc.d81` is only ever read; its SHA-256 is in `POWERc.d81.sha256`). | |||||
| Result (all verified in VICE, programs print identical output before/after): | |||||
| | program | original | improved | | | |||||
| |---|---|---|---| | |||||
| | `vim` editor (5 units) | 22,506 bytes | 19,793 bytes | −12.1 % | | |||||
| | `tests/sem.c` (semantics test) | 6,428 | 5,943 | −7.5 % | | |||||
| | `tests/bench.c` loop benchmark, jiffies (lower is better) | calls 28, array 27, branch 20, arith 30 | calls 26, array 17, branch 11, arith 18 | −7 % / −37 % / −45 % / −40 % | | |||||
| It also found and fixed a **bug in the original runtime**: the unsigned `>=` helper (`c0077.obj`) | |||||
| has a wrong opcode (`$80` instead of `$90`), so `unsigned a, b; r = a >= b;` yields 1 for 1 >= 5 | |||||
| (`tests/uge.c`). Comparisons inside `if` were unaffected; value contexts were. | |||||
| ## Quick start | |||||
| ``` | |||||
| python reference\powerc-re\tools\setup.py # once: extract lib objects, build optlib\ (add --disk for POWERc-opt.d81) | |||||
| powershell -NoProfile -File reference\powerc-re\tools\pcbuild.ps1 -Src hello.c -Name hello -Run '\n' | |||||
| python reference\powerc-re\tools\regress.py # optimizer on/off, compares the screens | |||||
| ``` | |||||
| `pcbuild.ps1` compiles with the original `cc` inside VICE, runs `pcopt.py` on every `.o`, links with | |||||
| the original linker using the improved runtime objects, and (with `-Run`) starts the program and | |||||
| saves the final screen. `-NoOpt` gives the original behaviour for comparison. Sources are plain | |||||
| ASCII; the scripts convert to PETSCII. Remember Power C is K&R C and cannot type `||` | |||||
| (see `skills/powerc/SKILL.md`). | |||||
| ## How the toolchain works (reverse engineered) | |||||
| * `cc.sh` runs two passes through temp files: `compiler` (source → intermediate) and `translator` | |||||
| (intermediate → `.o`). The result is **native 6502 code**, generated by naive template expansion: | |||||
| every value is a 16-bit pair in X (low) / Y (high); temporaries live in a software "temp stack" | |||||
| at `$033C` up; locals live in zero page from `$2B`; every operator on non-trivial values calls a | |||||
| tiny runtime helper `C$00nn` that works on that temp stack. | |||||
| * Calls use a software frame (`$14/$15` frame pointer, `$16/$17` argument pointer, `$1A/$1B` push | |||||
| stack). `C$105` (prolog) and `C$106` (epilogue) in `c105.obj` copy an 8-byte header plus all of | |||||
| the callee's zero-page locals each call — about 450 + 530 cycles, which dominates call-heavy code. | |||||
| * `C$RETURN` (inside `c105.obj`) is the 5-byte return-value slot; `C$0112` copies a value into it. | |||||
| * The linker pulls objects by name from the `stdlib.l` / `syslib.l` index files (`symbol NUL file NUL` | |||||
| pairs), so replacing e.g. `cmulti.obj` on a disk copy replaces that routine in every program. | |||||
| ### Object file format (`.o` / `.obj`) — `tools/objfmt.py` round-trips every object on the disk | |||||
| ``` | |||||
| u16 code_len ; code[] native code, data (globals, strings) embedded in it | |||||
| u16 n ; u16 off[n] opcode offsets of instructions whose 16-bit operand is a code-relative | |||||
| address (linker adds the load base): local jmp/jsr, pointers to data | |||||
| u16 n ; { name\0 u8 kind u16 off } definitions (functions, globals, string constants) | |||||
| u16 n ; { name\0 u16 kind u16 site } references to other objects; site = opcode offset | |||||
| kind 0 = abs16, 1 = high byte (#>), 2 = low byte (#<), 4 = abs16 + 1 | |||||
| u16 n ; { name\0 u16 size } reserved (uninitialised) data blocks | |||||
| ``` | |||||
| Relocation placeholders in the code are the opcode byte repeated (`20 20 20` = `jsr extern`). | |||||
| The linker overwrites them. Names may contain odd bytes (string constants are `<2 garbage>S$<n>`). | |||||
| ## The tools | |||||
| | file | purpose | | |||||
| |---|---| | |||||
| | `tools/pcopt.py` | peephole optimizer for `.o` files (see below) | | |||||
| | `tools/objfmt.py` | object format reader/writer; `python objfmt.py x.obj` checks the round trip | | |||||
| | `tools/pcasm.py` | tiny assembler that writes Power C objects directly (used for `src/cmulti.s`) | | |||||
| | `tools/cpu6502.py`, `helpertest.py`, `t_*.py`, `classify.py` | 6502 emulator + harnesses that prove a rewritten helper behaves exactly like the original (random and edge-case inputs, cycle counts) | | |||||
| | `tools/dis.py` | disassemble an object with its relocations/references | | |||||
| | `tools/pc-compile.ps1`, `pc-run.ps1`, `pcbuild.ps1` | drive the original `cc`/`link` in VICE on throw-away disk copies | | |||||
| | `src/cmulti.s`, `src/div_patch.py` | the replacement runtime routines | | |||||
| ### What `pcopt.py` does | |||||
| It decodes each compiled function (found via the `sta $FB` prolog and the `jmp C$106` epilogue), | |||||
| rewrites it with a liveness analysis (A/X/Y/C/N/Z/V, plus temp-stack memory) and re-emits the object | |||||
| with every offset, branch, relocation and definition fixed up. Anything it cannot decode with | |||||
| certainty is left untouched; with `--no-rules` it reproduces the input byte for byte. | |||||
| Rules: inline the `<`, `>`, `<=`, `>=`, `==`, `!=` helpers (signed and unsigned) when they feed an | |||||
| `if`/loop test, including `&&` chains; return values stored straight into `C$RETURN`; reload | |||||
| elimination (`stx/sty` then `ldx/ldy` of the same slot); `ldx #K;ldy #K;stx;sty` → `lda #K;sta;sta`; | |||||
| 16-bit `+1` / `+K`; jump-to-next and branch-over-jump removal; `==`/`!=` against a constant as two | |||||
| branches instead of a branch plus `jmp`. | |||||
| ### Faster runtime routines (`optlib/`, built by `setup.py`) | |||||
| * `cmulti.obj` — multiply that needs only 8 shift/add steps when an operand is < 256 | |||||
| (the other 8 steps of the original merely shifted the 32-bit register by one byte); | |||||
| ~40 % faster for small factors, +69 bytes of runtime when `*` is used. | |||||
| * `c0047.obj` — divide/remainder that normalises the divisor only as far as the dividend needs; | |||||
| same quotient and remainder, e.g. 100/7 is ~2× faster. (30000/3 is ~10 % slower.) | |||||
| * `c0077.obj` — the opcode fix described above. | |||||
| ## Safety | |||||
| Equivalence of the replaced runtime routines is checked in the emulator against the originals over | |||||
| thousands of operand pairs (`tools/t_multi.py`, `t_div.py`). The optimizer's rewrites rely on the | |||||
| compiler's temp-stack discipline (operand slots are dead after the operation that consumed them) and | |||||
| on the calling convention (A/X/Y are arguments, flags never are); programs are validated end to end | |||||
| (`regress.py`, the `vim` editing scenario). A bug found that way (an `&&` chain read a | |||||
| result slot the inlined compare no longer wrote) is covered by `tests/sem.c`. | |||||
| ## Not done / ideas | |||||
| * Prolog/epilogue (`c105.obj`) — dominates call cost but its frame layout is shared with | |||||
| `setjmp`/`longjmp`; micro-optimising the copy loops would buy roughly 10–15 %. | |||||
| * A real fix needs a better code generator (patching `translator`): keep values in registers, | |||||
| stop saving zero-page locals on every call. | |||||
| * Inline expansion of `C$1102` (push) and the pointer load/store helpers (`C$1021…`): faster but larger. | |||||
| * Arrays/long expressions: only the idioms above are matched, so gains shrink for code that | |||||
| is mostly helper calls. | |||||
| @@ -0,0 +1,61 @@ | |||||
| ; C$MULTI - 16x16 shift/add multiply, drop-in replacement for the original. | |||||
| ; in : $5A/$5B multiplicand, $5C/$5D multiplier, $5E/$5F accumulator (callers pass 0) | |||||
| ; out: product in $5D:$5C (low word); $5A/$5B preserved | |||||
| ; When the multiplier (or, with an empty accumulator, the multiplicand) is below 256, | |||||
| ; only 8 shift/add passes are needed: the other 8 passes of the original just moved the | |||||
| ; 32 bit register right by one byte, which is done directly instead. | |||||
| .def C$MULTI | |||||
| entry: ldy #1 | |||||
| ldx #8 | |||||
| lda $5d | |||||
| beq loop | |||||
| lda $5b | |||||
| bne full | |||||
| lda $5e | |||||
| ora $5f | |||||
| bne full | |||||
| lda $5a ; multiplicand < 256: swap the operands, run, swap back | |||||
| pha | |||||
| ldx $5c | |||||
| ldy $5d | |||||
| lda $5a | |||||
| sta $5c | |||||
| lda #0 | |||||
| sta $5d | |||||
| stx $5a | |||||
| sty $5b | |||||
| jsr entry | |||||
| pla | |||||
| sta $5a | |||||
| lda #0 | |||||
| sta $5b | |||||
| rts | |||||
| full: ldy #0 | |||||
| ldx #16 | |||||
| loop: lda $5c | |||||
| and #1 | |||||
| beq skip | |||||
| clc | |||||
| lda $5e | |||||
| adc $5a | |||||
| sta $5e | |||||
| lda $5f | |||||
| adc $5b | |||||
| sta $5f | |||||
| skip: lsr $5f | |||||
| ror $5e | |||||
| ror $5d | |||||
| ror $5c | |||||
| dex | |||||
| bne loop | |||||
| tya | |||||
| beq done | |||||
| lda $5d | |||||
| sta $5c | |||||
| lda $5e | |||||
| sta $5d | |||||
| lda $5f | |||||
| sta $5e | |||||
| lda #0 | |||||
| sta $5f | |||||
| done: rts | |||||
| @@ -0,0 +1,81 @@ | |||||
| """Build a faster c0047.obj (division / remainder helpers) by patching the original's shift/subtract loop. | |||||
| The original normalises the divisor all the way up to bit 15 and then runs one subtract step per | |||||
| shift, so even 100/7 costs ~15 wasted steps. The new loop normalises only as far as the dividend | |||||
| needs (2*D <= N); the extra leading steps of the original only ever produced zero quotient bits. | |||||
| Quotient ($53/$54) and remainder ($51/$52) are identical. Run: python src/div_patch.py in.obj out.obj | |||||
| """ | |||||
| import sys, os | |||||
| sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'tools')) | |||||
| from objfmt import Obj | |||||
| from pcasm import assemble | |||||
| START, END = 0xC7, 0xF5 | |||||
| REGION = """ | |||||
| ldx #1 | |||||
| norm: lda $50 | |||||
| bmi go | |||||
| lda $4f | |||||
| asl a | |||||
| tay | |||||
| lda $50 | |||||
| rol a | |||||
| cmp $52 | |||||
| bcc dosh | |||||
| bne go | |||||
| cpy $51 | |||||
| beq dosh | |||||
| bcs go | |||||
| dosh: sty $4f | |||||
| sta $50 | |||||
| inx | |||||
| bne norm | |||||
| go: asl $53 | |||||
| rol $54 | |||||
| sec | |||||
| lda $51 | |||||
| sbc $4f | |||||
| tay | |||||
| lda $52 | |||||
| sbc $50 | |||||
| bcc nosub | |||||
| sty $51 | |||||
| sta $52 | |||||
| inc $53 | |||||
| nosub: lsr $50 | |||||
| ror $4f | |||||
| dex | |||||
| bne go | |||||
| rts | |||||
| """ | |||||
| def patch(o): | |||||
| c = bytes(o.code) | |||||
| assert c[START] == 0xA2 and c[END - 1] == 0x60, 'unexpected layout' | |||||
| new, _ = assemble(REGION, base=START) | |||||
| delta = len(new.code) - (END - START) | |||||
| def fix(v): | |||||
| assert not (START <= v < END), 'reference into the replaced region' | |||||
| return v + delta if v >= END else v | |||||
| o2 = Obj() | |||||
| o2.code = bytearray(c[:START]) + new.code + bytearray(c[END:]) | |||||
| reloc = [] | |||||
| for off in o.reloc: | |||||
| if START <= off < END: | |||||
| continue # the old jmp norm | |||||
| n = fix(off) | |||||
| reloc.append(n) | |||||
| # fix the 16 bit operand value (code relative) in the moved copy | |||||
| v = o2.code[n + 1] | o2.code[n + 2] << 8 | |||||
| v = fix(v) if v >= END else v | |||||
| o2.code[n + 1] = v & 255; o2.code[n + 2] = v >> 8 | |||||
| o2.reloc = sorted(reloc + [r for r in new.reloc]) | |||||
| o2.defs = [[nm, k, fix(off)] for nm, k, off in o.defs] | |||||
| o2.refs = [[nm, k, fix(off)] for nm, k, off in o.refs] | |||||
| o2.res = o.res; o2.trailing = o.trailing | |||||
| return o2 | |||||
| if __name__ == '__main__': | |||||
| o = Obj.parse(open(sys.argv[1], 'rb').read()) | |||||
| open(sys.argv[2], 'wb').write(patch(o).build()) | |||||
| @@ -0,0 +1,48 @@ | |||||
| #include <stdio.h> | |||||
| int arr[64]; | |||||
| char *jp; | |||||
| unsigned now() | |||||
| { | |||||
| return (jp[0] << 8) + jp[1]; | |||||
| } | |||||
| add(a, b) | |||||
| int a, b; | |||||
| { | |||||
| return a + b; | |||||
| } | |||||
| main() | |||||
| { | |||||
| unsigned t0, t1; | |||||
| int i, j, s; | |||||
| jp = (char *) 0xa1; | |||||
| t0 = now(); | |||||
| s = 0; | |||||
| for (i = 0; i < 400; i++) | |||||
| s = add(s, i); | |||||
| t1 = now(); | |||||
| printf("calls %u s=%d\n", t1 - t0, s); | |||||
| t0 = now(); | |||||
| for (j = 0; j < 20; j++) | |||||
| for (i = 0; i < 64; i++) | |||||
| arr[i] = arr[i] + i + j; | |||||
| t1 = now(); | |||||
| printf("array %u a=%d\n", t1 - t0, arr[10]); | |||||
| t0 = now(); | |||||
| s = 0; | |||||
| for (i = 0; i < 1000; i++) { | |||||
| if (i > 500) s = s + 1; | |||||
| else s = s + 2; | |||||
| } | |||||
| t1 = now(); | |||||
| printf("branch %u s=%d\n", t1 - t0, s); | |||||
| t0 = now(); | |||||
| s = 1; | |||||
| for (i = 0; i < 300; i++) | |||||
| s = (s * 3 + i) % 1000; | |||||
| t1 = now(); | |||||
| printf("arith %u s=%d\n", t1 - t0, s); | |||||
| } | |||||
| @@ -0,0 +1,80 @@ | |||||
| #include <stdio.h> | |||||
| int sv[8]; | |||||
| unsigned uv[8]; | |||||
| char buf[40]; | |||||
| int cnt[16]; | |||||
| int fib(n) | |||||
| int n; | |||||
| { | |||||
| if (n < 2) return n; | |||||
| return fib(n - 1) + fib(n - 2); | |||||
| } | |||||
| int sgn(a) | |||||
| int a; | |||||
| { | |||||
| if (a < 0) return 0 - 1; | |||||
| if (a == 0) return 0; | |||||
| return 1; | |||||
| } | |||||
| unsigned umax(a, b) | |||||
| unsigned a, b; | |||||
| { | |||||
| if (a > b) return a; | |||||
| return b; | |||||
| } | |||||
| strl(s) | |||||
| char *s; | |||||
| { | |||||
| int n; | |||||
| n = 0; | |||||
| while (*s) { n++; s++; } | |||||
| return n; | |||||
| } | |||||
| main() | |||||
| { | |||||
| int i, j, k, a, b; | |||||
| unsigned int uu, vv; | |||||
| int t; | |||||
| sv[0] = 0 - 32767; sv[1] = 0 - 5; sv[2] = 0 - 1; sv[3] = 0; | |||||
| sv[4] = 1; sv[5] = 5; sv[6] = 255; sv[7] = 32767; | |||||
| uv[0] = 0; uv[1] = 1; uv[2] = 5; uv[3] = 255; uv[4] = 256; uv[5] = 32767; uv[6] = 32768; uv[7] = 65535; | |||||
| for (k = 0; k < 16; k++) cnt[k] = 0; | |||||
| for (i = 0; i < 8; i++) | |||||
| for (j = 0; j < 8; j++) { | |||||
| a = sv[i]; b = sv[j]; | |||||
| if (a < b) cnt[0]++; | |||||
| if (a > b) cnt[1]++; | |||||
| if (a <= b) cnt[2]++; | |||||
| if (a >= b) cnt[3]++; | |||||
| if (a == b) cnt[4]++; | |||||
| if (a != b) cnt[5]++; | |||||
| uu = uv[i]; vv = uv[j]; | |||||
| if (uu < vv) cnt[6]++; | |||||
| if (uu > vv) cnt[7]++; | |||||
| if (uu <= vv) cnt[8]++; | |||||
| if (uu == vv) cnt[10]++; | |||||
| if (uu != vv) cnt[11]++; | |||||
| if (a < b && uu < vv) cnt[12]++; | |||||
| if (a > b) cnt[13]++; else if (uu > vv) cnt[13]++; | |||||
| if (a < 5) cnt[14]++; | |||||
| if (uu > 255) cnt[15]++; | |||||
| } | |||||
| for (k = 0; k < 16; k++) printf("%d ", cnt[k]); | |||||
| printf("\n"); | |||||
| t = 0; | |||||
| for (i = 0; i < 8; i++) t = t + sgn(sv[i]) * (i + 1); | |||||
| printf("sgn %d fib %d umax %u\n", t, fib(10), umax(40000, 3)); | |||||
| strcpy(buf, "hello world"); | |||||
| printf("len %d %s\n", strl(buf), buf); | |||||
| t = 0; | |||||
| for (i = 0; i < 100; i++) { | |||||
| switch (i % 5) { | |||||
| case 0: t = t + 1; break; | |||||
| case 1: t = t + 2; break; | |||||
| case 3: t = t - 1; break; | |||||
| default: t = t + 3; | |||||
| } | |||||
| } | |||||
| printf("sw %d mul %d %d %d div %d %d\n", t, 123 * 45, 7 * (0 - 9), 300 * 200, 1000 / 7, 1000 % 7); | |||||
| } | |||||
| @@ -0,0 +1,7 @@ | |||||
| int g; | |||||
| int h; | |||||
| char c; | |||||
| f1() | |||||
| { | |||||
| g = 5; | |||||
| } | |||||
| @@ -0,0 +1,11 @@ | |||||
| int g; | |||||
| extern int ext; | |||||
| f2(a) | |||||
| int a; | |||||
| { | |||||
| int i; | |||||
| i = a + 1; | |||||
| g = i; | |||||
| if (i > 3) g = 0; | |||||
| ext = g; | |||||
| } | |||||
| @@ -0,0 +1,15 @@ | |||||
| #include <stdio.h> | |||||
| extern int ext2[]; | |||||
| int big[100]; | |||||
| char buf[300]; | |||||
| int init = 7; | |||||
| char *msg = "hi there"; | |||||
| f3(a) | |||||
| int a; | |||||
| { | |||||
| ext2[2] = a; | |||||
| big[a] = ext2[3]; | |||||
| printf("val %d\n", a); | |||||
| buf[1] = 5; | |||||
| return init; | |||||
| } | |||||
| @@ -0,0 +1,5 @@ | |||||
| #include <stdio.h> | |||||
| main() | |||||
| { | |||||
| printf("hello\n"); | |||||
| } | |||||
| @@ -0,0 +1,42 @@ | |||||
| /* powerc-hello.c - first program for Power C on the C64 | |||||
| * | |||||
| * Prints a small table and demonstrates 16-bit int wrap-around, | |||||
| * so it exercises printf(), a counting loop and int arithmetic. | |||||
| * | |||||
| * Build on the Power C disk (examples/powerc-hello.d81 already holds | |||||
| * the compiled result): | |||||
| * | |||||
| * cc hello.c | |||||
| * link -s | |||||
| * > hello.o | |||||
| * > ^ (pull in stdlib.l + syslib.l) | |||||
| * > (empty RETURN) | |||||
| * output file name: hello | |||||
| * | |||||
| * This file is plain ASCII; Power C needs PETSCII source with CR line | |||||
| * endings, so convert it before writing it to the image: | |||||
| * | |||||
| * c1541 powerc-hello.d81 -write hello.pet "hello.c,s" | |||||
| * | |||||
| * Then run it with -autostart "powerc-hello.d81:hello", or from BASIC: | |||||
| * LOAD"HELLO",8 : RUN | |||||
| */ | |||||
| #include <stdio.h> | |||||
| main() | |||||
| { | |||||
| int i, v; | |||||
| printf("power c on the c64\n"); | |||||
| printf("==================\n\n"); | |||||
| for (i = 1; i <= 5; i++) { | |||||
| v = i * i; | |||||
| printf("%d squared is %d\n", i, v); | |||||
| } | |||||
| v = 32767; | |||||
| v = v + 1; | |||||
| printf("\nint is 16-bit: 32767 + 1 = %d\n", v); | |||||
| } | |||||
| @@ -0,0 +1,13 @@ | |||||
| #include <stdio.h> | |||||
| unsigned a, b; | |||||
| main() | |||||
| { | |||||
| int r1, r2, r3; | |||||
| a = 1; b = 5; | |||||
| r1 = a >= b; | |||||
| r2 = b >= a; | |||||
| r3 = a >= a; | |||||
| printf("uge: 1>=5=%d 5>=1=%d 1>=1=%d\n", r1, r2, r3); | |||||
| if (a >= b) printf("BUG: 1>=5 taken\n"); | |||||
| else printf("ok: 1>=5 not taken\n"); | |||||
| } | |||||
| @@ -0,0 +1,49 @@ | |||||
| import sys, os, glob, itertools, random | |||||
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | |||||
| from helpertest import * | |||||
| from objfmt import Obj, pn | |||||
| from cpu6502 import CPU | |||||
| OPS = { | |||||
| 'lt': lambda a,b: int(a<b), 'gt': lambda a,b: int(a>b), 'le': lambda a,b: int(a<=b), 'ge': lambda a,b: int(a>=b), | |||||
| 'eq': lambda a,b: int(a==b), 'ne': lambda a,b: int(a!=b), | |||||
| 'add': lambda a,b: (a+b)&0xFFFF, 'sub': lambda a,b: (a-b)&0xFFFF, 'and': lambda a,b: a&b, 'or': lambda a,b: a|b, 'xor': lambda a,b: a^b, | |||||
| 'mul': lambda a,b: (a*b)&0xFFFF, | |||||
| } | |||||
| def s16(v): return v-65536 if v>=32768 else v | |||||
| vals=[0,1,2,3,5,100,255,256,257,1000,32767,32768,40000,65535,65534,12345] | |||||
| def run(h, name, a, b): | |||||
| cpu = CPU(); h.install(cpu) | |||||
| cpu.m[0x14]=0; cpu.m[0x15]=0x80; cpu.sp=0xF0 | |||||
| for i,v in enumerate((a&255,a>>8,b&255,b>>8)): cpu.m[0x33C+i]=v | |||||
| cpu.a=4; cpu.x=0; cpu.y=0 | |||||
| cpu.call(h.addr(name)) | |||||
| return cpu.m[0x33C]|cpu.m[0x33D]<<8, cpu | |||||
| for f in sorted(glob.glob('lib/c*.obj')): | |||||
| o = Obj.parse(open(f,'rb').read()) | |||||
| if f.endswith('c105.obj') or 'cstart' in f or 'conv' in f or 'localdefs' in f: continue | |||||
| if any(bytes(n).startswith(b'C$') is False for n,k,off in o.defs): pass | |||||
| ext={} | |||||
| try: | |||||
| h = Helper(o, 0x6000, {nm.decode('latin1') if False else bytes(nm): 0xD000 for nm,k,off in o.refs}) | |||||
| except Exception as ex: | |||||
| print(os.path.basename(f), 'load fail', ex); continue | |||||
| for nm,k,off in o.defs: | |||||
| if not bytes(nm).startswith(b'C$'): continue | |||||
| res = {} | |||||
| try: | |||||
| for signed in (False, True): | |||||
| match = {} | |||||
| for opn,fn in OPS.items(): | |||||
| ok = True | |||||
| for a,b in itertools.product(vals, vals): | |||||
| r,_ = run(h, nm, a, b) | |||||
| A,B = (s16(a),s16(b)) if signed else (a,b) | |||||
| exp = fn(A,B) | |||||
| exp &= 0xFFFF | |||||
| if r != exp: ok = False; break | |||||
| if ok: match[opn]=True | |||||
| if match: res[signed] = list(match) | |||||
| except Exception as ex: | |||||
| res = 'ERR %s' % ex | |||||
| print('%-14s %-10s size %3d %s' % (os.path.basename(f), pn(nm), len(o.code), res)) | |||||
| @@ -0,0 +1,204 @@ | |||||
| """Minimal NMOS 6502 emulator (official opcodes, binary-mode ADC/SBC) with cycle counts. | |||||
| Used to check that a rewritten runtime helper behaves exactly like the original one | |||||
| (same zero page / memory / registers afterwards) and to compare their cycle costs. | |||||
| """ | |||||
| import sys, os | |||||
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | |||||
| from pcopt import DEC, SIZE | |||||
| BASECYC = { | |||||
| 'imp': 2, 'acc': 2, 'imm': 2, 'zp': 3, 'zpx': 4, 'zpy': 4, 'abs': 4, 'abx': 4, 'aby': 4, | |||||
| 'izx': 6, 'izy': 5, 'rel': 2, 'ind': 5, | |||||
| } | |||||
| # read-modify-write and store extra cycles | |||||
| RMW = {'asl', 'lsr', 'rol', 'ror', 'inc', 'dec'} | |||||
| STORE = {'sta', 'stx', 'sty'} | |||||
| class CPU: | |||||
| def __init__(s): | |||||
| s.m = bytearray(65536) | |||||
| s.a = s.x = s.y = 0 | |||||
| s.sp = 0xFF | |||||
| s.pc = 0 | |||||
| s.c = s.z = s.n = s.v = 0 | |||||
| s.cyc = 0 | |||||
| s.i = 0; s.d = 0 | |||||
| s.watch = None | |||||
| # --- helpers | |||||
| def rd(s, a): | |||||
| return s.m[a & 0xFFFF] | |||||
| def wr(s, a, v): | |||||
| s.m[a & 0xFFFF] = v & 255 | |||||
| def push(s, v): | |||||
| s.m[0x100 + s.sp] = v & 255 | |||||
| s.sp = (s.sp - 1) & 255 | |||||
| def pull(s): | |||||
| s.sp = (s.sp + 1) & 255 | |||||
| return s.m[0x100 + s.sp] | |||||
| def nz(s, v): | |||||
| v &= 255 | |||||
| s.z = 1 if v == 0 else 0 | |||||
| s.n = v >> 7 | |||||
| return v | |||||
| def flags(s): | |||||
| return s.c | s.z << 1 | s.i << 2 | s.d << 3 | 0x30 | s.v << 6 | s.n << 7 | |||||
| def setflags(s, p): | |||||
| s.c = p & 1; s.z = p >> 1 & 1; s.i = p >> 2 & 1; s.d = p >> 3 & 1; s.v = p >> 6 & 1; s.n = p >> 7 & 1 | |||||
| def ea(s, md, pc): | |||||
| """effective address, extra-cycle flag""" | |||||
| m = s.m | |||||
| if md == 'zp': return m[pc + 1], 0 | |||||
| if md == 'zpx': return (m[pc + 1] + s.x) & 255, 0 | |||||
| if md == 'zpy': return (m[pc + 1] + s.y) & 255, 0 | |||||
| base = m[pc + 1] | m[pc + 2] << 8 | |||||
| if md == 'abs': return base, 0 | |||||
| if md == 'abx': | |||||
| a = (base + s.x) & 0xFFFF; return a, (a >> 8) != (base >> 8) | |||||
| if md == 'aby': | |||||
| a = (base + s.y) & 0xFFFF; return a, (a >> 8) != (base >> 8) | |||||
| if md == 'izx': | |||||
| p = (m[pc + 1] + s.x) & 255 | |||||
| return m[p] | m[(p + 1) & 255] << 8, 0 | |||||
| if md == 'izy': | |||||
| p = m[pc + 1] | |||||
| b = m[p] | m[(p + 1) & 255] << 8 | |||||
| a = (b + s.y) & 0xFFFF; return a, (a >> 8) != (b >> 8) | |||||
| raise ValueError(md) | |||||
| def step(s): | |||||
| pc = s.pc | |||||
| op = s.m[pc] | |||||
| if op == 0x80: # undocumented NOP #imm (2 bytes, 2 cycles) on the NMOS 6502 | |||||
| s.pc = (pc + 2) & 0xFFFF | |||||
| s.cyc += 2 | |||||
| return | |||||
| if op not in DEC: | |||||
| raise ValueError('bad opcode %02x at %04x' % (op, pc)) | |||||
| mn, md = DEC[op] | |||||
| n = SIZE[md] | |||||
| cyc = BASECYC[md] | |||||
| s.pc = (pc + n) & 0xFFFF | |||||
| if md == 'imm': | |||||
| val = s.m[pc + 1]; addr = None | |||||
| elif md in ('imp', 'acc', 'rel', 'ind'): | |||||
| val = None; addr = None | |||||
| else: | |||||
| addr, cross = s.ea(md, pc) | |||||
| val = None | |||||
| if mn in RMW or mn in STORE: | |||||
| if md in ('abx', 'aby', 'izy'): cyc += 1 # always penalised | |||||
| if mn in RMW: cyc += 2 | |||||
| if mn in STORE and md == 'abs': pass | |||||
| elif cross: | |||||
| cyc += 1 | |||||
| if mn in RMW and md in ('zp', 'zpx', 'abs', 'abx'): | |||||
| pass | |||||
| rdv = lambda: s.m[addr] if addr is not None else val | |||||
| if mn == 'lda': s.a = s.nz(rdv()) | |||||
| elif mn == 'ldx': s.x = s.nz(rdv()) | |||||
| elif mn == 'ldy': s.y = s.nz(rdv()) | |||||
| elif mn == 'sta': s.wr(addr, s.a) | |||||
| elif mn == 'stx': s.wr(addr, s.x) | |||||
| elif mn == 'sty': s.wr(addr, s.y) | |||||
| elif mn == 'tax': s.x = s.nz(s.a) | |||||
| elif mn == 'tay': s.y = s.nz(s.a) | |||||
| elif mn == 'txa': s.a = s.nz(s.x) | |||||
| elif mn == 'tya': s.a = s.nz(s.y) | |||||
| elif mn == 'tsx': s.x = s.nz(s.sp) | |||||
| elif mn == 'txs': s.sp = s.x | |||||
| elif mn == 'adc': | |||||
| b = rdv(); t = s.a + b + s.c | |||||
| s.v = 1 if (~(s.a ^ b) & (s.a ^ t) & 0x80) else 0 | |||||
| s.c = 1 if t > 255 else 0 | |||||
| s.a = s.nz(t) | |||||
| elif mn == 'sbc': | |||||
| b = rdv() ^ 255; t = s.a + b + s.c | |||||
| s.v = 1 if (~(s.a ^ b) & (s.a ^ t) & 0x80) else 0 | |||||
| s.c = 1 if t > 255 else 0 | |||||
| s.a = s.nz(t) | |||||
| elif mn == 'and': s.a = s.nz(s.a & rdv()) | |||||
| elif mn == 'ora': s.a = s.nz(s.a | rdv()) | |||||
| elif mn == 'eor': s.a = s.nz(s.a ^ rdv()) | |||||
| elif mn in ('cmp', 'cpx', 'cpy'): | |||||
| r = {'cmp': s.a, 'cpx': s.x, 'cpy': s.y}[mn] | |||||
| t = r - rdv() | |||||
| s.c = 1 if t >= 0 else 0 | |||||
| s.nz(t) | |||||
| elif mn == 'bit': | |||||
| b = rdv(); s.z = 1 if (s.a & b) == 0 else 0; s.n = b >> 7; s.v = b >> 6 & 1 | |||||
| elif mn in ('asl', 'lsr', 'rol', 'ror'): | |||||
| v = s.a if md == 'acc' else s.m[addr] | |||||
| if mn == 'asl': c = v >> 7; v = v << 1 | |||||
| elif mn == 'lsr': c = v & 1; v = v >> 1 | |||||
| elif mn == 'rol': c = v >> 7; v = v << 1 | s.c | |||||
| else: c = v & 1; v = v >> 1 | s.c << 7 | |||||
| s.c = c; v = s.nz(v) | |||||
| if md == 'acc': s.a = v | |||||
| else: s.wr(addr, v) | |||||
| elif mn == 'inc': s.wr(addr, s.nz(s.m[addr] + 1)) | |||||
| elif mn == 'dec': s.wr(addr, s.nz(s.m[addr] - 1)) | |||||
| elif mn == 'inx': s.x = s.nz(s.x + 1) | |||||
| elif mn == 'dex': s.x = s.nz(s.x - 1) | |||||
| elif mn == 'iny': s.y = s.nz(s.y + 1) | |||||
| elif mn == 'dey': s.y = s.nz(s.y - 1) | |||||
| elif mn == 'clc': s.c = 0 | |||||
| elif mn == 'sec': s.c = 1 | |||||
| elif mn == 'cld': s.d = 0 | |||||
| elif mn == 'sed': s.d = 1 | |||||
| elif mn == 'cli': s.i = 0 | |||||
| elif mn == 'sei': s.i = 1 | |||||
| elif mn == 'clv': s.v = 0 | |||||
| elif mn == 'nop': pass | |||||
| elif mn == 'pha': s.push(s.a); cyc = 3 | |||||
| elif mn == 'pla': s.a = s.nz(s.pull()); cyc = 4 | |||||
| elif mn == 'php': s.push(s.flags()); cyc = 3 | |||||
| elif mn == 'plp': s.setflags(s.pull()); cyc = 4 | |||||
| elif mn in ('bpl', 'bmi', 'bvc', 'bvs', 'bcc', 'bcs', 'bne', 'beq'): | |||||
| cond = {'bpl': not s.n, 'bmi': s.n, 'bvc': not s.v, 'bvs': s.v, 'bcc': not s.c, 'bcs': s.c, | |||||
| 'bne': not s.z, 'beq': s.z}[mn] | |||||
| if cond: | |||||
| off = s.m[pc + 1] | |||||
| if off > 127: off -= 256 | |||||
| t = (s.pc + off) & 0xFFFF | |||||
| cyc += 1 + ((t >> 8) != (s.pc >> 8)) | |||||
| s.pc = t | |||||
| elif mn == 'jmp': | |||||
| if md == 'abs': | |||||
| s.pc = s.m[pc + 1] | s.m[pc + 2] << 8; cyc = 3 | |||||
| else: | |||||
| p = s.m[pc + 1] | s.m[pc + 2] << 8 | |||||
| s.pc = s.m[p] | s.m[(p & 0xFF00) | ((p + 1) & 0xFF)] << 8 | |||||
| elif mn == 'jsr': | |||||
| r = (pc + 2) & 0xFFFF | |||||
| s.push(r >> 8); s.push(r & 255) | |||||
| s.pc = s.m[pc + 1] | s.m[pc + 2] << 8; cyc = 6 | |||||
| elif mn == 'rts': | |||||
| lo = s.pull(); hi = s.pull() | |||||
| s.pc = ((hi << 8 | lo) + 1) & 0xFFFF; cyc = 6 | |||||
| else: | |||||
| raise ValueError('unsupported ' + mn) | |||||
| s.cyc += cyc | |||||
| def call(s, addr, maxsteps=200000): | |||||
| """JSR addr and run until it returns; returns cycles used (excluding the JSR itself)""" | |||||
| SENT = 0xFFF0 | |||||
| s.push((SENT - 1) >> 8); s.push((SENT - 1) & 255) | |||||
| s.pc = addr | |||||
| c0 = s.cyc | |||||
| steps = 0 | |||||
| while s.pc != SENT: | |||||
| s.step() | |||||
| steps += 1 | |||||
| if steps > maxsteps: | |||||
| raise RuntimeError('runaway') | |||||
| return s.cyc - c0 | |||||
| @@ -0,0 +1,25 @@ | |||||
| import sys, os | |||||
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | |||||
| from objfmt import * | |||||
| from pcopt import DEC, SIZE | |||||
| def dis(o, a=0, b=None): | |||||
| c = o.code; b = len(c) if b is None else b | |||||
| refs = {off: (pn(n), k) for n, k, off in o.refs} | |||||
| names = {off: pn(n) for n, k, off in o.defs} | |||||
| p = a | |||||
| while p < b: | |||||
| if p in names: print('%s:' % names[p]) | |||||
| op = c[p] | |||||
| if op not in DEC: print('%04x %02x .byte' % (p, op)); p += 1; continue | |||||
| mn, md = DEC[op]; n = SIZE[md]; bs = c[p:p+n] | |||||
| v = bs[1] if n == 2 else (bs[1] | bs[2] << 8 if n == 3 else 0) | |||||
| if md == 'rel': v = p + 2 + (v - 256 if v > 127 else v) | |||||
| opnd = {'imp': '', 'acc': 'a', 'imm': '#$%02x' % v, 'zp': '$%02x' % v, 'zpx': '$%02x,x' % v, 'zpy': '$%02x,y' % v, 'abs': '$%04x' % v, 'abx': '$%04x,x' % v, 'aby': '$%04x,y' % v, 'izx': '($%02x,x)' % v, 'izy': '($%02x),y' % v, 'rel': 'L%04x' % v, 'ind': '($%04x)' % v}[md] | |||||
| tag = '' | |||||
| if p in refs: tag += ' ; ref %s' % (refs[p],) | |||||
| if p in o.reloc: tag += ' ; reloc' | |||||
| print('%04x %-9s %s %s%s' % (p, bs.hex(' '), mn, opnd, tag)) | |||||
| p += n | |||||
| if __name__ == '__main__': | |||||
| o = Obj.parse(open(sys.argv[1], 'rb').read()) | |||||
| dis(o) | |||||
| @@ -0,0 +1,10 @@ | |||||
| import sys | |||||
| def show(f): | |||||
| d=open(f,'rb').read() | |||||
| n=d[0]|d[1]<<8 | |||||
| print(f,'len',n,'file',len(d)) | |||||
| print('code:',d[2:2+n].hex(' ')) | |||||
| t=d[2+n:] | |||||
| print('tail:',t.hex(' ')) | |||||
| print('asc :',''.join(chr(b) if 32<=b<127 else '.' for b in t)) | |||||
| for f in sys.argv[1:]: show(f) | |||||
| @@ -0,0 +1,55 @@ | |||||
| """Load Power C runtime-helper objects into the 6502 emulator and compare implementations. | |||||
| usage (library): h = Helper(obj, base=0x6000, ext={'C$HIGHMEM': 0xC000, ...}) | |||||
| h.addr('C$105') | |||||
| """ | |||||
| import sys, os, random | |||||
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | |||||
| from objfmt import Obj | |||||
| from cpu6502 import CPU | |||||
| class Helper: | |||||
| def __init__(self, obj, base, ext): | |||||
| self.o = obj | |||||
| self.base = base | |||||
| self.code = bytearray(obj.code) | |||||
| for off in obj.reloc: # local absolute operands | |||||
| v = self.code[off + 1] | self.code[off + 2] << 8 | |||||
| v = (v + base) & 0xFFFF | |||||
| self.code[off + 1] = v & 255; self.code[off + 2] = v >> 8 | |||||
| syms = {nm: base + off for nm, k, off in obj.defs} | |||||
| syms.update({k.encode() if isinstance(k, str) else k: v for k, v in ext.items()}) | |||||
| for nm, kind, off in obj.refs: | |||||
| a = syms[bytes(nm)] | |||||
| if kind == 0: | |||||
| self.code[off + 1] = a & 255; self.code[off + 2] = a >> 8 | |||||
| elif kind == 4: | |||||
| a += 1; self.code[off + 1] = a & 255; self.code[off + 2] = a >> 8 | |||||
| elif kind == 2: | |||||
| self.code[off + 1] = a & 255 | |||||
| elif kind == 1: | |||||
| self.code[off + 1] = a >> 8 | |||||
| else: | |||||
| raise ValueError('kind %d' % kind) | |||||
| self.syms = syms | |||||
| def addr(self, name): | |||||
| return self.syms[name.encode() if isinstance(name, str) else name] | |||||
| def install(self, cpu): | |||||
| cpu.m[self.base:self.base + len(self.code)] = self.code | |||||
| def snapshot(cpu, skip=()): | |||||
| m = bytes(cpu.m) | |||||
| return m | |||||
| def diff_mem(a, b, skip): | |||||
| out = [] | |||||
| for i in range(65536): | |||||
| if a[i] != b[i] and not any(lo <= i < hi for lo, hi in skip): | |||||
| out.append((i, a[i], b[i])) | |||||
| if len(out) > 8: break | |||||
| return out | |||||
| @@ -0,0 +1,57 @@ | |||||
| """Power C object file (.o/.obj) reader/writer. | |||||
| u16 code_len; code | |||||
| u16 n_reloc; u16 off[n] (u16 at off+1 is code-relative, += load base) | |||||
| u16 n_def; {name\0 u8 kind u16 off} | |||||
| u16 n_ref; {name\0 u16 kind u16 site} kind 0=abs16 1=hi 2=lo 4=abs+1 (site = opcode offset) | |||||
| u16 n_res; {name\0 u16 size} | |||||
| """ | |||||
| import struct | |||||
| class Obj: | |||||
| def __init__(s): | |||||
| s.code=bytearray(); s.reloc=[]; s.defs=[]; s.refs=[]; s.res=[] | |||||
| @staticmethod | |||||
| def parse(data): | |||||
| o=Obj(); p=0 | |||||
| def u16(): | |||||
| nonlocal p | |||||
| v=data[p]|data[p+1]<<8; p+=2; return v | |||||
| def name(): | |||||
| nonlocal p | |||||
| e=data.index(0,p); n=bytes(data[p:e]); p=e+1; return n | |||||
| n=u16(); o.code=bytearray(data[p:p+n]); p+=n | |||||
| k=u16(); o.reloc=[u16() for _ in range(k)] | |||||
| k=u16() | |||||
| for _ in range(k): | |||||
| nm=name(); kind=data[p]; p+=1; off=u16(); o.defs.append([nm,kind,off]) | |||||
| k=u16() | |||||
| for _ in range(k): | |||||
| nm=name(); kind=u16(); off=u16(); o.refs.append([nm,kind,off]) | |||||
| k=u16() | |||||
| for _ in range(k): | |||||
| nm=name(); sz=u16(); o.res.append([nm,sz]) | |||||
| o.trailing=bytes(data[p:]) | |||||
| return o | |||||
| def build(o): | |||||
| out=bytearray() | |||||
| def u16(v): out.extend((v&255,v>>8&255)) | |||||
| u16(len(o.code)); out+=o.code | |||||
| u16(len(o.reloc)); [u16(x) for x in o.reloc] | |||||
| u16(len(o.defs)) | |||||
| for nm,kind,off in o.defs: out+=nm+b'\0'+bytes([kind]); u16(off) | |||||
| u16(len(o.refs)) | |||||
| for nm,kind,off in o.refs: out+=nm+b'\0'; u16(kind); u16(off) | |||||
| u16(len(o.res)) | |||||
| for nm,sz in o.res: out+=nm+b'\0'; u16(sz) | |||||
| out+=o.trailing | |||||
| return bytes(out) | |||||
| def pn(b): return ''.join(chr(c) if 32<=c<127 else '<%02x>'%c for c in b) | |||||
| if __name__=='__main__': | |||||
| import sys | |||||
| for f in sys.argv[1:]: | |||||
| d=open(f,'rb').read(); o=Obj.parse(d) | |||||
| assert o.build()==d,'roundtrip mismatch '+f | |||||
| print(f,'code',len(o.code),'reloc',len(o.reloc),'defs',[(pn(a),b,hex(c)) for a,b,c in o.defs],'refs',len(o.refs),'res',o.res,'trail',len(o.trailing)) | |||||
| @@ -0,0 +1,61 @@ | |||||
| # pc-compile.ps1 - compile C sources with the ORIGINAL Power C in VICE, on a throwaway | |||||
| # copy of reference/POWERc.d81 (the original image is never written). | |||||
| # pc-compile.ps1 -Src a.c,b.c -Out outdir [-Link name] [-Run keys] | |||||
| # Produces outdir\a.o ... ; with -Link, links all objects (+libs) into outdir\<name>.prg | |||||
| param([string[]]$Src, [string]$Out, [string]$Link = "", [int]$Delay = 700, [int]$Pad = 20) | |||||
| $ErrorActionPreference = "Continue" | |||||
| $vice = "C:\Program Files\GTK3VICE-3.10-win64\bin" | |||||
| $x64 = Join-Path $vice "x64sc.exe"; $c1541 = Join-Path $vice "c1541.exe" | |||||
| $Src = @($Src | % { $_ -split "," } | % { (Resolve-Path $_).Path }) | |||||
| New-Item -ItemType Directory -Force -Path $Out | Out-Null | |||||
| $Out = (Resolve-Path $Out).Path | |||||
| $here = $PSScriptRoot | |||||
| $base = Join-Path $here "..\..\POWERc.d81" | |||||
| $wd = Join-Path $here "..\work" | |||||
| New-Item -ItemType Directory -Force -Path $wd, $Out | Out-Null | |||||
| Set-Location $wd | |||||
| $img = "t.d81" | |||||
| Copy-Item $base $img -Force | |||||
| function ConvertTo-Petscii([string]$in, [string]$out) { | |||||
| $bytes = [IO.File]::ReadAllBytes($in); $o = New-Object System.Collections.Generic.List[byte] | |||||
| foreach ($b in $bytes) { | |||||
| if ($b -eq 13) { continue } | |||||
| if ($b -eq 10) { $o.Add([byte]13); continue } | |||||
| if ($b -ge 0x61 -and $b -le 0x7A) { $o.Add([byte]($b - 0x20)); continue } | |||||
| if ($b -ge 0x41 -and $b -le 0x5A) { $o.Add([byte]($b + 0x80)); continue } | |||||
| if ($b -ge 0x7B -and $b -le 0x7F) { $o.Add([byte]($b + 0x60)); continue } | |||||
| $o.Add([byte]$b) | |||||
| } | |||||
| [IO.File]::WriteAllBytes($out, $o.ToArray()) | |||||
| } | |||||
| function Quote-Arg([string]$a) { | |||||
| if ($a -ne "" -and $a -notmatch '[\s"]') { return $a } | |||||
| $e = $a -replace '(\\*)"', '$1$1\"'; $e = $e -replace '(\\+)$', '$1$1'; return '"' + $e + '"' | |||||
| } | |||||
| $kb = ""; $names = @() | |||||
| foreach ($s in $Src) { | |||||
| $n = [IO.Path]::GetFileNameWithoutExtension($s); $names += $n | |||||
| $pet = Join-Path $wd "$n.pet" | |||||
| ConvertTo-Petscii $s $pet | |||||
| $null = & $c1541 $img -delete "$n.c" "$n.o" 2>$null | |||||
| $null = & $c1541 $img -write $pet "$n.c,s" 2>$null | |||||
| $kb += "cc $n.c\n" + ("\n" * 3) + ("\n" * $Pad) | |||||
| } | |||||
| if ($Link -ne "") { | |||||
| $kb += "link -s\n" | |||||
| foreach ($n in $names) { $kb += "$n.o\n" } | |||||
| $kb += "^\n\n$Link\n" + ("\n" * 8) | |||||
| } | |||||
| $shot = "last.png"; if (Test-Path $shot) { Remove-Item $shot -Force } | |||||
| $cycles = [long](($kb.Length * $Delay / 1000.0 + 20) * 13000000); if ($cycles -gt 2000000000) { $cycles = 2000000000 } | |||||
| $argv = @("-8", $img, "-autostart", "${img}:shell", "-autostart-warp", "-warp", "-sounddev", "dummy", | |||||
| "-keybuf-delay", "$Delay", "-keybuf", $kb, "-limitcycles", "$cycles", "-exitscreenshot", $shot) | |||||
| $psi = New-Object System.Diagnostics.ProcessStartInfo | |||||
| $psi.WorkingDirectory = (Get-Location).Path; $psi.FileName = $x64; $psi.Arguments = ($argv | % { Quote-Arg $_ }) -join ' '; $psi.UseShellExecute = $false | |||||
| $p = [Diagnostics.Process]::Start($psi); $p.WaitForExit(); Start-Sleep -Milliseconds 900 | |||||
| foreach ($n in $names) { | |||||
| & $c1541 $img -read "$n.o,s" (Join-Path $Out "$n.o") 2>$null | Out-Null | |||||
| if (Test-Path (Join-Path $Out "$n.o")) { Write-Host "ok $n.o" } else { Write-Host "FAIL $n.o (see work\last.png)" } | |||||
| } | |||||
| if ($Link -ne "") { & $c1541 $img -read $Link (Join-Path $Out "$Link.prg") 2>$null | Out-Null } | |||||
| @@ -0,0 +1,47 @@ | |||||
| # pc-run.ps1 - link given object files with the ORIGINAL Power C linker (copy of POWERc.d81), | |||||
| # optionally run the program and type keys at it. The original disk image is never written. | |||||
| # pc-run.ps1 -Obj a.o,b.o -Name prog -Dir outdir [-Keys 'text\n'] [-Data file1,file2] | |||||
| param([string[]]$Obj, [string]$Name = "prog", [string]$Dir, [string]$Keys = "", [string[]]$Data = @(), [string[]]$LibObj = @(), | |||||
| [int]$Delay = 900, [int]$Tail = 8, [int]$Settle = 15, [string]$Shot = "run.png") | |||||
| $ErrorActionPreference = "Continue" | |||||
| $vice = "C:\Program Files\GTK3VICE-3.10-win64\bin" | |||||
| $x64 = Join-Path $vice "x64sc.exe"; $c1541 = Join-Path $vice "c1541.exe" | |||||
| $Obj = @($Obj | % { $_ -split "," } | % { (Resolve-Path $_).Path }) | |||||
| $LibObj = @($LibObj | ? { $_ } | % { $_ -split "," } | % { (Resolve-Path $_).Path }) | |||||
| $Data = @($Data | ? { $_ } | % { $_ -split "," } | % { (Resolve-Path $_).Path }) | |||||
| New-Item -ItemType Directory -Force -Path $Dir | Out-Null | |||||
| $Dir = (Resolve-Path $Dir).Path | |||||
| $here = $PSScriptRoot | |||||
| $base = (Resolve-Path (Join-Path $here "..\..\POWERc.d81")).Path | |||||
| $wd = Join-Path $here "..\work"; New-Item -ItemType Directory -Force -Path $wd | Out-Null; $wd = (Resolve-Path $wd).Path | |||||
| Set-Location $wd | |||||
| $img = "r.d81"; Copy-Item $base $img -Force | |||||
| $names = @() | |||||
| foreach ($o in $Obj) { | |||||
| $n = [IO.Path]::GetFileNameWithoutExtension($o); $names += $n | |||||
| $null = & $c1541 $img -delete "$n.o" 2>$null | |||||
| $null = & $c1541 $img -write $o "$n.o,s" 2>$null | |||||
| } | |||||
| foreach ($l in $LibObj) { $ln = [IO.Path]::GetFileName($l); $null = & $c1541 $img -delete $ln 2>$null; $null = & $c1541 $img -write $l "$ln,s" 2>$null } | |||||
| foreach ($d in $Data) { $null = & $c1541 $img -write $d ([IO.Path]::GetFileName($d) + ",s") 2>$null } | |||||
| function Quote-Arg([string]$a) { | |||||
| if ($a -ne "" -and $a -notmatch '[\s"]') { return $a } | |||||
| $e = $a -replace '(\\*)"', '$1$1\"'; $e = $e -replace '(\\+)$', '$1$1'; return '"' + $e + '"' | |||||
| } | |||||
| $kb = "link -s\n" | |||||
| foreach ($n in $names) { $kb += "$n.o\n" } | |||||
| $kb += "^\n\n$Name\n" + ("\n" * $Tail) | |||||
| $haveKeys = $Keys -ne "" | |||||
| if ($haveKeys) { | |||||
| $kb += "bye\n" + ("\n" * 6) + 'load"' + $Name + '",8,1' + "\n" + ("\n" * 10) + "run\n" + $Keys | |||||
| } | |||||
| $shotp = Join-Path $Dir $Shot; if (Test-Path $shotp) { Remove-Item $shotp -Force } | |||||
| $cycles = [long](($kb.Length * $Delay / 1000.0 + $Settle) * 13000000); if ($cycles -gt 2000000000) { $cycles = 2000000000 } | |||||
| $argv = @("-8", $img, "-autostart", "${img}:shell", "-autostart-warp", "-warp", "-sounddev", "dummy", | |||||
| "-keybuf-delay", "$Delay", "-keybuf", $kb, "-limitcycles", "$cycles", "-exitscreenshot", $shotp) | |||||
| $psi = New-Object System.Diagnostics.ProcessStartInfo | |||||
| $psi.WorkingDirectory = $wd; $psi.FileName = $x64; $psi.Arguments = ($argv | % { Quote-Arg $_ }) -join ' '; $psi.UseShellExecute = $false | |||||
| $p = [Diagnostics.Process]::Start($psi); $p.WaitForExit(); Start-Sleep -Milliseconds 900 | |||||
| $prg = Join-Path $Dir "$Name.prg"; if (Test-Path $prg) { Remove-Item $prg -Force } | |||||
| & $c1541 $img -read $Name $prg 2>$null | Out-Null | |||||
| if (Test-Path $prg) { Write-Host ("linked {0}: {1} bytes" -f $Name, (Get-Item $prg).Length) } else { Write-Host "LINK FAILED" } | |||||
| @@ -0,0 +1,161 @@ | |||||
| """Tiny assembler that writes Power C object files (.o/.obj) directly. | |||||
| Syntax (one statement per line, ';' comments): | |||||
| label: local label | |||||
| .def NAME export NAME at this offset (def kind 1) | |||||
| .byte 1,2,$ff raw bytes | |||||
| lda #$12 lda $14 lda $0123,x lda ($14),y lda ($14,x) | |||||
| bne label jmp label jsr label (jmp/jsr/abs operands on local labels are relocated) | |||||
| jsr ext:NAME reference to an external symbol (absolute) | |||||
| lda ext:NAME+1 external symbol + 1 | |||||
| ldx #<ext:NAME low byte of an external symbol (#> for the high byte) | |||||
| lda label,x absolute operand on a local label (relocated) | |||||
| """ | |||||
| import re, sys, os | |||||
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | |||||
| from objfmt import Obj | |||||
| from pcopt import ENC, SIZE, BRANCH | |||||
| def num(t): | |||||
| t = t.strip() | |||||
| if t.startswith('$'): | |||||
| return int(t[1:], 16) | |||||
| return int(t, 0) | |||||
| def parse_operand(s): | |||||
| """returns (mode, value-or-label, ext) ; ext = (name, kind) for external symbols""" | |||||
| s = s.strip() | |||||
| if not s: | |||||
| return 'imp', None, None | |||||
| if s == 'a': | |||||
| return 'acc', None, None | |||||
| if s.startswith('#'): | |||||
| t = s[1:] | |||||
| part = None | |||||
| if t.startswith('<') or t.startswith('>'): | |||||
| part = t[0] | |||||
| t = t[1:] | |||||
| if t.startswith('ext:'): | |||||
| return 'imm', 0, (t[4:].encode(), 2 if part == '<' else 1) | |||||
| v = num(t) | |||||
| if part == '<': | |||||
| v &= 255 | |||||
| elif part == '>': | |||||
| v = v >> 8 & 255 | |||||
| return 'imm', v, None | |||||
| m = re.match(r'^\((.+),x\)$', s) | |||||
| if m: | |||||
| return 'izx', num(m.group(1)), None | |||||
| m = re.match(r'^\((.+)\),y$', s) | |||||
| if m: | |||||
| return 'izy', num(m.group(1)), None | |||||
| idx = '' | |||||
| m = re.match(r'^(.*),([xy])$', s) | |||||
| if m: | |||||
| s, idx = m.group(1), m.group(2) | |||||
| if s.startswith('ext:'): | |||||
| t = s[4:] | |||||
| kind = 0 | |||||
| m = re.match(r'^(.*)\+1$', t) | |||||
| if m: | |||||
| t, kind = m.group(1), 4 | |||||
| return ('abs' + idx if idx else 'abs'), 0, (t.encode(), kind) | |||||
| try: | |||||
| v = num(s) | |||||
| md = 'zp' if v < 256 else 'abs' | |||||
| return (md + idx if idx else md), v, None | |||||
| except ValueError: | |||||
| return ('abs' + idx if idx else 'abs'), s, None # local label | |||||
| def assemble(text, base=0): | |||||
| lines = [] | |||||
| for ln in text.splitlines(): | |||||
| ln = ln.split(';')[0].strip() | |||||
| m = re.match(r'^([A-Za-z_]\w*):\s*(.*)$', ln) | |||||
| while m: | |||||
| lines.append(m.group(1) + ':') | |||||
| ln = m.group(2).strip() | |||||
| m = re.match(r'^([A-Za-z_]\w*):\s*(.*)$', ln) | |||||
| if ln: | |||||
| lines.append(ln) | |||||
| # pass 1: sizes | |||||
| items = [] # ('label',name) ('def',name) ('bytes',[...]) ('ins',mn,md,val,ext) | |||||
| for ln in lines: | |||||
| if ln.endswith(':'): | |||||
| items.append(('label', ln[:-1])) | |||||
| continue | |||||
| if ln.startswith('.def'): | |||||
| items.append(('def', ln.split()[1])) | |||||
| continue | |||||
| if ln.startswith('.byte'): | |||||
| items.append(('bytes', [num(x) for x in ln[5:].split(',')])) | |||||
| continue | |||||
| parts = ln.split(None, 1) | |||||
| mn = parts[0].lower() | |||||
| md, val, ext = parse_operand(parts[1] if len(parts) > 1 else '') | |||||
| if mn in BRANCH: | |||||
| md = 'rel' | |||||
| elif mn in ('jmp', 'jsr'): | |||||
| md = 'abs' | |||||
| elif md == 'zp' and (mn, 'zp') not in ENC and (mn, 'abs') in ENC: | |||||
| md = 'abs' | |||||
| elif md == 'zpx' and (mn, 'zpx') not in ENC and (mn, 'abx') in ENC: | |||||
| md = 'abx' | |||||
| elif md == 'zpy' and (mn, 'zpy') not in ENC and (mn, 'aby') in ENC: | |||||
| md = 'aby' | |||||
| if (mn, md) not in ENC: | |||||
| raise ValueError('bad instruction: ' + ln) | |||||
| items.append(('ins', mn, md, val, ext)) | |||||
| labels, defs = {}, [] | |||||
| pos = base | |||||
| for it in items: | |||||
| if it[0] == 'label': | |||||
| labels[it[1]] = pos | |||||
| elif it[0] == 'def': | |||||
| defs.append([it[1].encode(), 1, pos]) | |||||
| elif it[0] == 'bytes': | |||||
| pos += len(it[1]) | |||||
| else: | |||||
| pos += SIZE[it[2]] | |||||
| code = bytearray() | |||||
| reloc, refs = [], [] | |||||
| for it in items: | |||||
| if it[0] == 'bytes': | |||||
| code += bytes(it[1]) | |||||
| elif it[0] == 'ins': | |||||
| _, mn, md, val, ext = it | |||||
| at = base + len(code) | |||||
| code.append(ENC[(mn, md)]) | |||||
| n = SIZE[md] | |||||
| if md == 'rel': | |||||
| t = labels[val] | |||||
| rel = t - (at + 2) | |||||
| if not -128 <= rel <= 127: | |||||
| raise ValueError('branch out of range at %x' % at) | |||||
| code.append(rel & 255) | |||||
| elif n == 2: | |||||
| if ext: | |||||
| refs.append([ext[0], ext[1], at]) | |||||
| val = ENC[(mn, md)] | |||||
| code.append(val & 255) | |||||
| elif n == 3: | |||||
| if ext: | |||||
| refs.append([ext[0], ext[1], at]) | |||||
| v = ENC[(mn, md)] * 0x101 | |||||
| elif isinstance(val, str): | |||||
| v = labels[val] | |||||
| reloc.append(at) | |||||
| else: | |||||
| v = val | |||||
| code += bytes((v & 255, v >> 8 & 255)) | |||||
| o = Obj() | |||||
| o.code = code | |||||
| o.reloc = reloc | |||||
| o.defs = defs | |||||
| o.refs = sorted(refs, key=lambda r: r[2]) | |||||
| o.res = [] | |||||
| o.trailing = b'' | |||||
| return o, labels | |||||
| @@ -0,0 +1,41 @@ | |||||
| # pcbuild.ps1 - build a Power C program with the improved toolchain. | |||||
| # | |||||
| # powershell -NoProfile -File reference\powerc-re\tools\pcbuild.ps1 -Src a.c,b.c -Name prog [-Dir build] [-Run 'keys\n'] [-NoOpt] | |||||
| # | |||||
| # 1. compiles every source with the ORIGINAL Power C compiler running in VICE (copy of POWERc.d81) | |||||
| # 2. runs pcopt.py on each object file (peephole optimizer; -NoOpt skips this) | |||||
| # 3. links with the original linker, using the improved runtime objects from optlib\ | |||||
| # (faster multiply / divide, fixed unsigned >=), again on a copy of the disk | |||||
| # 4. with -Run, loads the program in BASIC, runs it and types the given keys; the final | |||||
| # screen is saved as <Dir>\run.png | |||||
| # Prerequisite (once): python reference\powerc-re\tools\setup.py | |||||
| # The original reference\POWERc.d81 is never written. | |||||
| param([string[]]$Src, [string]$Name = "prog", [string]$Dir = "build", [string]$Run = "", [switch]$NoOpt, | |||||
| [int]$Settle = 30) | |||||
| $ErrorActionPreference = "Stop" | |||||
| $here = $PSScriptRoot | |||||
| $root = (Resolve-Path (Join-Path $here "..")).Path | |||||
| $Src = @($Src | % { $_ -split "," } | % { (Resolve-Path $_).Path }) | |||||
| New-Item -ItemType Directory -Force -Path $Dir | Out-Null | |||||
| $Dir = (Resolve-Path $Dir).Path | |||||
| $obj = Join-Path $Dir "obj"; $opt = Join-Path $Dir "opt" | |||||
| New-Item -ItemType Directory -Force -Path $obj, $opt | Out-Null | |||||
| if (-not (Test-Path (Join-Path $root "optlib\cmulti.obj"))) { throw "run tools\setup.py first" } | |||||
| & (Join-Path $here "pc-compile.ps1") -Src $Src -Out $obj | Where-Object { $_ -match "^(ok|FAIL)" } | |||||
| $names = $Src | % { [IO.Path]::GetFileNameWithoutExtension($_) } | |||||
| foreach ($n in $names) { if (-not (Test-Path (Join-Path $obj "$n.o"))) { throw "$n.c did not compile - see work\last.png" } } | |||||
| $final = @() | |||||
| foreach ($n in $names) { | |||||
| $in = Join-Path $obj "$n.o" | |||||
| if ($NoOpt) { $final += $in; continue } | |||||
| $out = Join-Path $opt "$n.o" | |||||
| & python (Join-Path $here "pcopt.py") $in $out --lib (Join-Path $root "lib") | |||||
| $final += $out | |||||
| } | |||||
| $lib = (Get-ChildItem (Join-Path $root "optlib") -Filter *.obj | % { $_.FullName }) -join "," | |||||
| $p = @{ Obj = ($final -join ","); Name = $Name; Dir = $Dir; Settle = $Settle } | |||||
| if (-not $NoOpt) { $p.LibObj = $lib } | |||||
| if ($Run -ne "") { $p.Keys = $Run } | |||||
| & (Join-Path $here "pc-run.ps1") @p | Where-Object { $_ -match "linked|FAILED" } | |||||
| @@ -0,0 +1,915 @@ | |||||
| #!/usr/bin/env python3 | |||||
| """pcopt - peephole optimizer for Power C object files (.o). | |||||
| Reads an object produced by the original Power C `cc`, decodes every compiled | |||||
| function into 6502 instructions, applies rewrites that are checked against a | |||||
| register/flag liveness analysis, and writes a smaller/faster object with all | |||||
| offsets, relocations, labels and branches fixed up. Anything it cannot decode | |||||
| with certainty is left untouched. The original compiler, linker and libraries | |||||
| are never modified. | |||||
| usage: pcopt.py in.o out.o [--lib libdir] [--no-rules] [-v] | |||||
| """ | |||||
| import sys, os, glob | |||||
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | |||||
| from objfmt import Obj, pn | |||||
| # ------------------------------------------------------------------ 6502 table | |||||
| _T = """ | |||||
| 00 brk imp;01 ora izx;05 ora zp;06 asl zp;08 php imp;09 ora imm;0a asl acc;0d ora abs;0e asl abs; | |||||
| 10 bpl rel;11 ora izy;15 ora zpx;16 asl zpx;18 clc imp;19 ora aby;1d ora abx;1e asl abx; | |||||
| 20 jsr abs;21 and izx;24 bit zp;25 and zp;26 rol zp;28 plp imp;29 and imm;2a rol acc;2c bit abs;2d and abs;2e rol abs; | |||||
| 30 bmi rel;31 and izy;35 and zpx;36 rol zpx;38 sec imp;39 and aby;3d and abx;3e rol abx; | |||||
| 40 rti imp;41 eor izx;45 eor zp;46 lsr zp;48 pha imp;49 eor imm;4a lsr acc;4c jmp abs;4d eor abs;4e lsr abs; | |||||
| 50 bvc rel;51 eor izy;55 eor zpx;56 lsr zpx;58 cli imp;59 eor aby;5d eor abx;5e lsr abx; | |||||
| 60 rts imp;61 adc izx;65 adc zp;66 ror zp;68 pla imp;69 adc imm;6a ror acc;6c jmp ind;6d adc abs;6e ror abs; | |||||
| 70 bvs rel;71 adc izy;75 adc zpx;76 ror zpx;78 sei imp;79 adc aby;7d adc abx;7e ror abx; | |||||
| 81 sta izx;84 sty zp;85 sta zp;86 stx zp;88 dey imp;8a txa imp;8c sty abs;8d sta abs;8e stx abs; | |||||
| 90 bcc rel;91 sta izy;94 sty zpx;95 sta zpx;96 stx zpy;98 tya imp;99 sta aby;9a txs imp;9d sta abx; | |||||
| a0 ldy imm;a1 lda izx;a2 ldx imm;a4 ldy zp;a5 lda zp;a6 ldx zp;a8 tay imp;a9 lda imm;aa tax imp;ac ldy abs;ad lda abs;ae ldx abs; | |||||
| b0 bcs rel;b1 lda izy;b4 ldy zpx;b5 lda zpx;b6 ldx zpy;b8 clv imp;b9 lda aby;ba tsx imp;bc ldy abx;bd lda abx;be ldx aby; | |||||
| c0 cpy imm;c1 cmp izx;c4 cpy zp;c5 cmp zp;c6 dec zp;c8 iny imp;c9 cmp imm;ca dex imp;cc cpy abs;cd cmp abs;ce dec abs; | |||||
| d0 bne rel;d1 cmp izy;d5 cmp zpx;d6 dec zpx;d8 cld imp;d9 cmp aby;dd cmp abx;de dec abx; | |||||
| e0 cpx imm;e1 sbc izx;e4 cpx zp;e5 sbc zp;e6 inc zp;e8 inx imp;e9 sbc imm;ea nop imp;ec cpx abs;ed sbc abs;ee inc abs; | |||||
| f0 beq rel;f1 sbc izy;f5 sbc zpx;f6 inc zpx;f8 sed imp;f9 sbc aby;fd sbc abx;fe inc abx | |||||
| """ | |||||
| DEC = {} # opcode -> (mnemonic, mode) | |||||
| ENC = {} # (mnemonic, mode) -> opcode | |||||
| for _item in _T.replace('\n', '').split(';'): | |||||
| _item = _item.strip() | |||||
| if not _item: | |||||
| continue | |||||
| _c, _mn, _md = _item.split() | |||||
| DEC[int(_c, 16)] = (_mn, _md) | |||||
| ENC[(_mn, _md)] = int(_c, 16) | |||||
| SIZE = {'imp': 1, 'acc': 1, 'imm': 2, 'zp': 2, 'zpx': 2, 'zpy': 2, 'izx': 2, 'izy': 2, 'rel': 2, | |||||
| 'abs': 3, 'abx': 3, 'aby': 3, 'ind': 3} | |||||
| BRANCH = {'bpl', 'bmi', 'bvc', 'bvs', 'bcc', 'bcs', 'bne', 'beq'} | |||||
| INVERT = {'bpl': 'bmi', 'bmi': 'bpl', 'bvc': 'bvs', 'bvs': 'bvc', 'bcc': 'bcs', 'bcs': 'bcc', 'bne': 'beq', 'beq': 'bne'} | |||||
| REGS = {'A', 'X', 'Y', 'C', 'N', 'Z', 'V'} | |||||
| NZ = {'N', 'Z'} | |||||
| class I: | |||||
| """one instruction. tgt = target instruction for branches and local jmp/jsr""" | |||||
| __slots__ = ('mn', 'md', 'val', 'ref', 'reloc', 'tgt', 'old', 'new') | |||||
| def __init__(s, mn, md, val=0, ref=None, reloc=False, tgt=None, old=None): | |||||
| s.mn, s.md, s.val, s.ref, s.reloc, s.tgt, s.old = mn, md, val, ref, reloc, tgt, old | |||||
| s.new = None | |||||
| @property | |||||
| def size(s): | |||||
| return SIZE[s.md] | |||||
| def __repr__(s): | |||||
| return '%s %s %s%s%s' % (s.mn, s.md, hex(s.val), ' ref=%s' % (s.ref,) if s.ref else '', ' R' if s.reloc else '') | |||||
| def replace(insts, k, n, new): | |||||
| """replace insts[k:k+n] by the list new, retargeting anything that pointed at the first old one""" | |||||
| first = insts[k] | |||||
| if new: | |||||
| for x in insts: | |||||
| if x.tgt is first: | |||||
| x.tgt = new[0] | |||||
| else: | |||||
| nxt = insts[k + n] if k + n < len(insts) else None | |||||
| for x in insts: | |||||
| if x.tgt is first: | |||||
| x.tgt = nxt | |||||
| insts[k:k + n] = new | |||||
| # ------------------------------------------------------------------ effects | |||||
| def effects(i): | |||||
| """(reads, writes) over A X Y C N Z V for one non-flow-ending instruction""" | |||||
| mn, md = i.mn, i.md | |||||
| r, w = set(), set() | |||||
| if md in ('abx', 'zpx', 'izx'): | |||||
| r.add('X') | |||||
| if md in ('aby', 'zpy', 'izy'): | |||||
| r.add('Y') | |||||
| if mn == 'lda': w |= {'A', 'N', 'Z'} | |||||
| elif mn == 'ldx': w |= {'X', 'N', 'Z'} | |||||
| elif mn == 'ldy': w |= {'Y', 'N', 'Z'} | |||||
| elif mn == 'sta': r.add('A') | |||||
| elif mn == 'stx': r.add('X') | |||||
| elif mn == 'sty': r.add('Y') | |||||
| elif mn == 'tax': r.add('A'); w |= {'X', 'N', 'Z'} | |||||
| elif mn == 'tay': r.add('A'); w |= {'Y', 'N', 'Z'} | |||||
| elif mn == 'txa': r.add('X'); w |= {'A', 'N', 'Z'} | |||||
| elif mn == 'tya': r.add('Y'); w |= {'A', 'N', 'Z'} | |||||
| elif mn in ('adc', 'sbc'): r |= {'A', 'C'}; w |= {'A', 'C', 'N', 'Z', 'V'} | |||||
| elif mn in ('and', 'ora', 'eor'): r.add('A'); w |= {'A', 'N', 'Z'} | |||||
| elif mn == 'cmp': r.add('A'); w |= {'C', 'N', 'Z'} | |||||
| elif mn == 'cpx': r.add('X'); w |= {'C', 'N', 'Z'} | |||||
| elif mn == 'cpy': r.add('Y'); w |= {'C', 'N', 'Z'} | |||||
| elif mn == 'bit': r.add('A'); w |= {'N', 'Z', 'V'} | |||||
| elif mn in ('asl', 'lsr', 'rol', 'ror'): | |||||
| if md == 'acc': | |||||
| r.add('A'); w.add('A') | |||||
| if mn in ('rol', 'ror'): | |||||
| r.add('C') | |||||
| w |= {'C', 'N', 'Z'} | |||||
| elif mn in ('inc', 'dec'): w |= NZ | |||||
| elif mn in ('inx', 'dex'): r.add('X'); w |= {'X', 'N', 'Z'} | |||||
| elif mn in ('iny', 'dey'): r.add('Y'); w |= {'Y', 'N', 'Z'} | |||||
| elif mn in ('clc', 'sec'): w.add('C') | |||||
| elif mn in ('bne', 'beq'): r.add('Z') | |||||
| elif mn in ('bpl', 'bmi'): r.add('N') | |||||
| elif mn in ('bcc', 'bcs'): r.add('C') | |||||
| elif mn in ('bvc', 'bvs'): r.add('V') | |||||
| elif mn == 'pha': r.add('A') | |||||
| elif mn == 'pla': w |= {'A', 'N', 'Z'} | |||||
| elif mn == 'nop': pass | |||||
| else: | |||||
| raise ValueError('effects: unhandled ' + mn) | |||||
| return r, w | |||||
| TEMP = 0x033C | |||||
| HELPER_READS = {} # runtime symbol -> registers it may read on entry | |||||
| CALL_READS = {'A', 'X', 'Y'} | |||||
| def helper_reads(code, start, refs_at=None, reloc=None): | |||||
| """registers a runtime helper may read (on some path) before writing them. | |||||
| Explores all paths through the helper; jsr/jmp to unknown code counts as a read of A,X,Y.""" | |||||
| result = set() | |||||
| for r in REGS: | |||||
| seen = set() | |||||
| stack = [start] | |||||
| live = False | |||||
| steps = 0 | |||||
| while stack and not live: | |||||
| p = stack.pop() | |||||
| while True: | |||||
| steps += 1 | |||||
| if steps > 3000 or p >= len(code) or p in seen: | |||||
| live = live or (steps > 3000 or p >= len(code)) | |||||
| break | |||||
| seen.add(p) | |||||
| op = code[p] | |||||
| if op not in DEC: | |||||
| live = True | |||||
| break | |||||
| mn, md = DEC[op] | |||||
| n = SIZE[md] | |||||
| if mn == 'rts': | |||||
| break | |||||
| if mn in ('jmp', 'jsr'): | |||||
| if md == 'ind': | |||||
| live = True | |||||
| break | |||||
| tgt = code[p + 1] | code[p + 2] << 8 | |||||
| if refs_at is not None and p in refs_at: | |||||
| if r in CALL_READS: | |||||
| live = True | |||||
| if mn == 'jmp': | |||||
| break | |||||
| p += n | |||||
| continue | |||||
| if reloc is not None and p in reloc: # a jump/call inside this object | |||||
| if mn == 'jmp': | |||||
| p = tgt | |||||
| continue | |||||
| if mn == 'jsr': # nested call: A,X,Y are its arguments, flags never are | |||||
| if r in CALL_READS: | |||||
| live = True | |||||
| break | |||||
| p += n | |||||
| continue | |||||
| live = True # unresolved target: be conservative | |||||
| break | |||||
| if mn in ('rti', 'brk', 'php', 'plp', 'tsx', 'txs', 'sei', 'cli', 'sed', 'cld') or md == 'ind': | |||||
| live = True | |||||
| break | |||||
| try: | |||||
| rr, ww = effects(I(mn, md)) | |||||
| except ValueError: | |||||
| live = True | |||||
| break | |||||
| if r in rr: | |||||
| live = True | |||||
| break | |||||
| if r in ww: | |||||
| break | |||||
| if mn in BRANCH: | |||||
| off = code[p + 1] | |||||
| stack.append(p + 2 + (off - 256 if off > 127 else off)) | |||||
| p += n | |||||
| if live: | |||||
| result.add(r) | |||||
| return result | |||||
| def load_lib(libdir): | |||||
| for f in glob.glob(os.path.join(libdir, '*.obj')): | |||||
| try: | |||||
| o = Obj.parse(open(f, 'rb').read()) | |||||
| except Exception: | |||||
| continue | |||||
| for nm, kind, off in o.defs: | |||||
| if nm.startswith(b'C$') and kind == 1 and off < len(o.code): | |||||
| HELPER_READS[bytes(nm)] = helper_reads(o.code, off, set(x[2] for x in o.refs), set(o.reloc)) | |||||
| # ------------------------------------------------------------------ function discovery / decode | |||||
| def find_functions(o): | |||||
| code = o.code | |||||
| starts = sorted(set(off for nm, k, off in o.defs | |||||
| if off + 1 < len(code) and code[off] == 0x85 and code[off + 1] == 0xfb)) | |||||
| ret_sites = sorted(off for nm, k, off in o.refs if bytes(nm) == b'C$106' and k == 0) | |||||
| funcs = [] | |||||
| for n, s in enumerate(starts): | |||||
| limit = starts[n + 1] if n + 1 < len(starts) else len(code) | |||||
| cand = [r for r in ret_sites if s <= r < limit] | |||||
| if not cand: | |||||
| continue | |||||
| end = cand[-1] + 3 | |||||
| if end > limit: | |||||
| continue | |||||
| funcs.append((s, end)) | |||||
| return funcs | |||||
| def decode_func(o, start, end): | |||||
| code = o.code | |||||
| refs_at = {} | |||||
| for nm, kind, off in o.refs: | |||||
| if start <= off < end: | |||||
| refs_at.setdefault(off, []).append((bytes(nm), kind)) | |||||
| reloc_at = set(x for x in o.reloc if start <= x < end) | |||||
| insts, pos, at = [], start, {} | |||||
| while pos < end: | |||||
| op = code[pos] | |||||
| if op not in DEC: | |||||
| return None | |||||
| mn, md = DEC[op] | |||||
| n = SIZE[md] | |||||
| if pos + n > end: | |||||
| return None | |||||
| v = 0 | |||||
| if n == 2: v = code[pos + 1] | |||||
| elif n == 3: v = code[pos + 1] | code[pos + 2] << 8 | |||||
| refs = refs_at.get(pos, []) | |||||
| if len(refs) > 1: | |||||
| return None | |||||
| ref = refs[0] if refs else None | |||||
| if md == 'ind' or mn in ('rti', 'brk', 'php', 'plp', 'pha', 'pla', 'txs', 'tsx', 'sei', 'cli', 'sed', 'cld'): | |||||
| return None | |||||
| i = I(mn, md, v, ref, pos in reloc_at, None, pos) | |||||
| if ref and md not in ('abs', 'abx', 'aby', 'imm'): | |||||
| return None | |||||
| if i.reloc and md not in ('abs', 'abx', 'aby'): | |||||
| return None | |||||
| at[pos] = i | |||||
| insts.append(i) | |||||
| pos += n | |||||
| if pos != end: | |||||
| return None | |||||
| for p in refs_at: | |||||
| if p not in at: return None | |||||
| for p in reloc_at: | |||||
| if p not in at: return None | |||||
| for i in insts: | |||||
| if i.md == 'rel': | |||||
| t = i.old + 2 + (i.val - 256 if i.val > 127 else i.val) | |||||
| if t not in at: return None | |||||
| i.tgt = at[t] | |||||
| elif i.reloc and i.mn in ('jmp', 'jsr'): | |||||
| if start <= i.val < end: | |||||
| if i.val not in at: return None | |||||
| i.tgt = at[i.val] | |||||
| elif i.reloc and start <= i.val < end: | |||||
| return None | |||||
| return insts | |||||
| # ------------------------------------------------------------------ analysis | |||||
| def targets_of(insts): | |||||
| return set(id(i.tgt) for i in insts if i.tgt is not None) | |||||
| def dead_after(insts, k, regs, tset=None): | |||||
| """subset of regs that no path leaving insts[k] can read before it is rewritten (conservative: | |||||
| unknown control flow counts as a read)""" | |||||
| return dead_from(insts, [k + 1], regs) | |||||
| def dead_from(insts, starts, regs): | |||||
| """like dead_after but from explicit start indices (all of them must be dead)""" | |||||
| index = {id(x): n for n, x in enumerate(insts)} | |||||
| out = set() | |||||
| for r in regs: | |||||
| seen = set() | |||||
| stack = list(starts) | |||||
| live = False | |||||
| steps = 0 | |||||
| while stack and not live: | |||||
| j = stack.pop() | |||||
| while True: | |||||
| steps += 1 | |||||
| if steps > 4000: | |||||
| live = True | |||||
| break | |||||
| if j >= len(insts): | |||||
| live = True | |||||
| break | |||||
| if j in seen: | |||||
| break | |||||
| seen.add(j) | |||||
| i = insts[j] | |||||
| mn = i.mn | |||||
| if mn == 'rts': | |||||
| live = True | |||||
| break | |||||
| if mn == 'jmp': | |||||
| if i.tgt is not None: | |||||
| j = index[id(i.tgt)] | |||||
| continue | |||||
| rd = HELPER_READS.get(i.ref[0]) if (i.ref and i.ref[1] == 0) else None | |||||
| if rd is None: | |||||
| rd = CALL_READS # unknown callee: A,X,Y are arguments, flags never are | |||||
| if r in rd: | |||||
| live = True | |||||
| break | |||||
| if mn == 'jsr': | |||||
| rd = HELPER_READS.get(i.ref[0]) if (i.ref and i.ref[1] == 0) else None | |||||
| if rd is None: | |||||
| rd = CALL_READS | |||||
| if r in rd: | |||||
| live = True | |||||
| break | |||||
| j += 1 | |||||
| continue | |||||
| rr, ww = effects(i) | |||||
| if r in rr: | |||||
| live = True | |||||
| break | |||||
| if r in ww: | |||||
| break # killed on this path | |||||
| if mn in BRANCH: | |||||
| stack.append(index[id(i.tgt)]) | |||||
| j += 1 | |||||
| if not live: | |||||
| out.add(r) | |||||
| return out | |||||
| def temp_dead_after(insts, k, lo, hi): | |||||
| """is temp-stack memory [lo,hi) provably not read on any path after insts[k]? | |||||
| Reaching the function epilogue (jmp C$106) rewrites the temp stack, so that ends a path.""" | |||||
| return temp_dead_from(insts, [k + 1], lo, hi) | |||||
| def temp_dead_from(insts, starts, lo, hi): | |||||
| index = {id(x): n for n, x in enumerate(insts)} | |||||
| seen = set() | |||||
| stack = list(starts) | |||||
| steps = 0 | |||||
| while stack: | |||||
| j = stack.pop() | |||||
| while True: | |||||
| steps += 1 | |||||
| if steps > 4000 or j >= len(insts): | |||||
| return False | |||||
| if j in seen: | |||||
| break | |||||
| seen.add(j) | |||||
| i = insts[j] | |||||
| mn = i.mn | |||||
| if mn == 'jmp': | |||||
| if i.tgt is not None: | |||||
| j = index[id(i.tgt)] | |||||
| continue | |||||
| if i.ref and i.ref[0] == b'C$106' and i.ref[1] == 0: | |||||
| break | |||||
| return False | |||||
| if mn == 'rts': | |||||
| return False | |||||
| if mn == 'jsr': | |||||
| # a helper/function reads only the top A bytes of the temp stack | |||||
| prev = insts[j - 1] if j > 0 else None | |||||
| if (prev is not None and prev.mn == 'lda' and prev.md == 'imm' and not prev.ref | |||||
| and TEMP + prev.val <= lo): | |||||
| j += 1 | |||||
| continue | |||||
| return False | |||||
| if i.md in ('abs', 'abx', 'aby') and not i.ref and not i.reloc and lo <= i.val < hi: | |||||
| if i.mn in ('sta', 'stx', 'sty') and i.md == 'abs': | |||||
| break # overwritten before any read on this path | |||||
| return False | |||||
| if i.md in ('abx', 'aby') and not i.ref and not i.reloc and i.val < hi and i.val + 255 >= lo: | |||||
| return False | |||||
| if mn in BRANCH: | |||||
| stack.append(index[id(i.tgt)]) | |||||
| j += 1 | |||||
| return True | |||||
| def mem_key(i): | |||||
| return (i.md, i.val, i.ref, i.reloc) | |||||
| # ------------------------------------------------------------------ rules | |||||
| def rule_forward(insts, stats): | |||||
| """X/Y already hold the value of a memory slot: drop the reload / use txa,tya""" | |||||
| tset = targets_of(insts) | |||||
| xmem, ymem = set(), set() | |||||
| for k, i in enumerate(insts): | |||||
| if id(i) in tset: | |||||
| xmem.clear(); ymem.clear() | |||||
| mn, md = i.mn, i.md | |||||
| if md in ('abs', 'zp') and not i.ref and not i.reloc: | |||||
| key = mem_key(i) | |||||
| if mn == 'ldx' and key in xmem or mn == 'ldy' and key in ymem: | |||||
| if dead_after(insts, k, NZ, tset) == NZ: | |||||
| replace(insts, k, 1, []) | |||||
| stats['reload'] += 1 | |||||
| return True | |||||
| if mn == 'lda' and (key in xmem or key in ymem): | |||||
| n = I('txa' if key in xmem else 'tya', 'imp', old=i.old) | |||||
| replace(insts, k, 1, [n]) | |||||
| stats['lda->t'] += 1 | |||||
| return True | |||||
| if mn in ('jsr', 'jmp'): | |||||
| xmem.clear(); ymem.clear() | |||||
| continue | |||||
| if mn in ('sta', 'stx', 'sty') and md in ('izx', 'izy', 'abx', 'aby', 'zpx', 'zpy'): | |||||
| xmem.clear(); ymem.clear() # may alias anything | |||||
| continue | |||||
| r, w = effects(i) | |||||
| if 'X' in w: xmem.clear() | |||||
| if 'Y' in w: ymem.clear() | |||||
| if mn in ('stx', 'sty', 'sta', 'inc', 'dec', 'asl', 'lsr', 'rol', 'ror') and md in ('abs', 'zp'): | |||||
| key = mem_key(i) | |||||
| xmem.discard(key); ymem.discard(key) | |||||
| if not i.ref and not i.reloc: | |||||
| if mn == 'stx': xmem.add(key) | |||||
| if mn == 'sty': ymem.add(key) | |||||
| elif mn in ('stx', 'sty') or i.reloc or i.ref: | |||||
| # a relocated store to a variable: the slot is still a fixed address | |||||
| if mn == 'stx': xmem.add(key) | |||||
| if mn == 'sty': ymem.add(key) | |||||
| return False | |||||
| def rule_jmp_next(insts, stats): | |||||
| for k in range(len(insts) - 1): | |||||
| i = insts[k] | |||||
| if i.mn == 'jmp' and i.tgt is not None and i.tgt is insts[k + 1] and not i.ref: | |||||
| replace(insts, k, 1, []) | |||||
| stats['jmp-next'] += 1 | |||||
| return True | |||||
| return False | |||||
| def rule_zero_pair(insts, stats): | |||||
| """ldx #K; ldy #K; stx M; sty N (X,Y,A dead after) -> lda #K; sta M; sta N""" | |||||
| tset = targets_of(insts) | |||||
| for k in range(len(insts) - 3): | |||||
| a, b, c, d = insts[k:k + 4] | |||||
| if not (a.mn == 'ldx' and a.md == 'imm' and b.mn == 'ldy' and b.md == 'imm' and not a.ref and not b.ref): | |||||
| continue | |||||
| if a.val != b.val: | |||||
| continue | |||||
| if not (c.mn == 'stx' and d.mn == 'sty' and c.md in ('abs', 'zp') and d.md in ('abs', 'zp')): | |||||
| continue | |||||
| if any(id(x) in tset for x in (b, c, d)): | |||||
| continue | |||||
| want = {'A', 'X', 'Y', 'N', 'Z'} | |||||
| if dead_after(insts, k + 3, want, tset) != want: | |||||
| continue | |||||
| new = [I('lda', 'imm', a.val, old=a.old), | |||||
| I('sta', c.md, c.val, c.ref, c.reloc, old=c.old), | |||||
| I('sta', d.md, d.val, d.ref, d.reloc, old=d.old)] | |||||
| # c and d may be branch targets only if they were in tset (checked); a was handled by replace() | |||||
| replace(insts, k, 4, new) | |||||
| stats['zero-pair'] += 1 | |||||
| return True | |||||
| return False | |||||
| def rule_inc16(insts, stats): | |||||
| """clc;txa;adc #1;tax;tya;adc #0;tay -> inx;bne +1;iny (A,C,N,Z,V dead after)""" | |||||
| pat = [('clc', 'imp'), ('txa', 'imp'), ('adc', 'imm'), ('tax', 'imp'), ('tya', 'imp'), ('adc', 'imm'), ('tay', 'imp')] | |||||
| tset = targets_of(insts) | |||||
| for k in range(len(insts) - 7): | |||||
| w = insts[k:k + 7] | |||||
| if any((x.mn, x.md) != p for x, p in zip(w, pat)): | |||||
| continue | |||||
| if w[2].val != 1 or w[5].val != 0 or any(x.ref or x.reloc for x in w): | |||||
| continue | |||||
| if any(id(x) in tset for x in w[1:]): | |||||
| continue | |||||
| want = {'A', 'C', 'N', 'Z', 'V'} | |||||
| if dead_after(insts, k + 6, want, tset) != want: | |||||
| continue | |||||
| nxt = insts[k + 7] | |||||
| inx = I('inx', 'imp', old=w[0].old) | |||||
| iny = I('iny', 'imp') | |||||
| bne = I('bne', 'rel', 0, tgt=nxt) | |||||
| replace(insts, k, 7, [inx, bne, iny]) | |||||
| stats['inc16'] += 1 | |||||
| return True | |||||
| return False | |||||
| def rule_dup_ldy(insts, stats): | |||||
| tset = targets_of(insts) | |||||
| for k in range(len(insts) - 1): | |||||
| a, b = insts[k], insts[k + 1] | |||||
| if (a.mn == b.mn and a.mn in ('ldy', 'ldx') and a.md == b.md == 'imm' and a.val == b.val | |||||
| and not a.ref and not b.ref and id(b) not in tset): | |||||
| replace(insts, k + 1, 1, []) | |||||
| stats['dup-ld'] += 1 | |||||
| return True | |||||
| return False | |||||
| def layout_of(insts, base): | |||||
| lay, p = {}, base | |||||
| for i in insts: | |||||
| lay[id(i)] = p | |||||
| p += i.size | |||||
| return lay | |||||
| def rule_branch_over_jmp(insts, base, stats): | |||||
| """bXX L; jmp T; L: -> b!XX T when T is in branch range""" | |||||
| tset = targets_of(insts) | |||||
| lay = layout_of(insts, base) | |||||
| for k in range(len(insts) - 2): | |||||
| b, j, l = insts[k], insts[k + 1], insts[k + 2] | |||||
| if b.mn in BRANCH and b.tgt is l and j.mn == 'jmp' and j.tgt is not None and not j.ref and id(j) not in tset: | |||||
| rel = lay[id(j.tgt)] - (lay[id(b)] + 2) | |||||
| if -120 <= rel <= 120: | |||||
| nb = I(INVERT[b.mn], 'rel', 0, tgt=j.tgt, old=b.old) | |||||
| replace(insts, k, 2, [nb]) | |||||
| stats['bra-over-jmp'] += 1 | |||||
| return True | |||||
| return False | |||||
| # ------------------------------------------------------------------ inline the compare helpers | |||||
| CMP = {b'C$0063': ('s', 'lt'), b'C$0067': ('s', 'gt'), b'C$0071': ('s', 'le'), b'C$0075': ('s', 'ge'), | |||||
| b'C$0065': ('u', 'lt'), b'C$0069': ('u', 'gt'), b'C$0073': ('u', 'le'), b'C$0077': ('u', 'ge'), | |||||
| b'C$0079': ('e', 'eq'), b'C$0082': ('e', 'ne')} | |||||
| ALLREGS = {'A', 'X', 'Y', 'C', 'N', 'Z', 'V'} | |||||
| def opd(i): | |||||
| """operand descriptor of a load instruction""" | |||||
| return (i.md, i.val, i.ref, i.reloc) | |||||
| def E(mn, d): | |||||
| return I(mn, d[0], d[1], d[2], d[3]) | |||||
| def imm_src(i): | |||||
| """a source that is stable (not temp-stack memory)""" | |||||
| if i.md == 'imm': | |||||
| return True | |||||
| if i.md in ('abs', 'zp'): | |||||
| return i.ref is not None or i.reloc or not (TEMP <= i.val < 0x0400) | |||||
| return False | |||||
| def emit_compare(kind, op, A, B, T, F): | |||||
| """inline code: falls into T when the comparison holds, else jumps to F""" | |||||
| def jf(): | |||||
| return I('jmp', 'abs', 0, None, True, F) | |||||
| if kind == 'e': | |||||
| if op == 'eq': | |||||
| j = jf() | |||||
| return [E('lda', A[0]), E('cmp', B[0]), I('bne', 'rel', 0, tgt=j), E('lda', A[1]), E('cmp', B[1]), | |||||
| I('beq', 'rel', 0, tgt=T), j] | |||||
| return [E('lda', A[0]), E('cmp', B[0]), I('bne', 'rel', 0, tgt=T), E('lda', A[1]), E('cmp', B[1]), | |||||
| I('bne', 'rel', 0, tgt=T), jf()] | |||||
| swap = op in ('gt', 'le') | |||||
| p, q = (B, A) if swap else (A, B) | |||||
| out = [E('lda', p[0]), E('cmp', q[0]), E('lda', p[1]), E('sbc', q[1])] | |||||
| if kind == 's': | |||||
| br = I('bmi' if op in ('lt', 'gt') else 'bpl', 'rel', 0, tgt=T) | |||||
| out += [I('bvc', 'rel', 0, tgt=br), I('eor', 'imm', 0x80), br, jf()] | |||||
| else: | |||||
| br = I('bcc' if op in ('lt', 'gt') else 'bcs', 'rel', 0, tgt=T) | |||||
| out += [br, jf()] | |||||
| return out | |||||
| def match_consumer(insts, c): | |||||
| """the ldx/ldy/txa/bne.. idioms that test a 0/1 result stored at some base; (base, length) or None""" | |||||
| def slot(i, mn): | |||||
| return i.mn == mn and i.md == 'abs' and not i.ref and not i.reloc | |||||
| n = len(insts) | |||||
| if c + 7 < n: | |||||
| w = insts[c:c + 7] | |||||
| if (slot(w[0], 'ldx') and slot(w[1], 'ldy') and w[1].val == w[0].val + 1 and w[2].mn == 'txa' | |||||
| and w[3].mn == 'bne' and w[4].mn == 'tya' and w[5].mn == 'bne' and w[6].mn == 'jmp' | |||||
| and w[3].tgt is w[5].tgt and w[6].tgt is not None and not w[6].ref | |||||
| and w[3].tgt is insts[c + 7]): | |||||
| return w[0].val, 7 | |||||
| if c + 5 < n: | |||||
| w = insts[c:c + 5] | |||||
| if (slot(w[0], 'lda') and slot(w[2], 'lda') and w[0].val == w[2].val + 1 and w[1].mn == 'bne' | |||||
| and w[3].mn == 'bne' and w[4].mn == 'jmp' and w[1].tgt is w[3].tgt and w[4].tgt is not None | |||||
| and not w[4].ref and w[1].tgt is insts[c + 5]): | |||||
| return w[2].val, 5 | |||||
| return None | |||||
| def push_pair(w, s0): | |||||
| """ldx src; ldy src; stx s0; sty s0+1 with stable sources -> (lo, hi) operand descriptors""" | |||||
| if not (w[0].mn == 'ldx' and w[1].mn == 'ldy' and w[2].mn == 'stx' and w[3].mn == 'sty'): | |||||
| return None | |||||
| if not (w[2].md == 'abs' and not w[2].ref and not w[2].reloc and w[2].val == s0 and | |||||
| w[3].md == 'abs' and not w[3].ref and not w[3].reloc and w[3].val == s0 + 1): | |||||
| return None | |||||
| if not (imm_src(w[0]) and imm_src(w[1])): | |||||
| return None | |||||
| return opd(w[0]), opd(w[1]) | |||||
| def rule_compare(insts, stats): | |||||
| tset = targets_of(insts) | |||||
| for j, jm in enumerate(insts): | |||||
| if jm.mn != 'jsr' or not jm.ref or jm.ref[1] != 0 or jm.ref[0] not in CMP or j == 0: | |||||
| continue | |||||
| ld = insts[j - 1] | |||||
| if not (ld.mn == 'lda' and ld.md == 'imm' and not ld.ref and ld.val >= 4): | |||||
| continue | |||||
| base = TEMP + ld.val - 4 | |||||
| m = match_consumer(insts, j + 1) | |||||
| if not m or m[0] != base: | |||||
| continue | |||||
| last = j + m[1] # index of the final jmp F | |||||
| T = insts[last + 1] | |||||
| F = insts[last].tgt | |||||
| if id(jm) in tset or any(id(x) in tset for x in insts[j + 2:last + 1]): | |||||
| continue | |||||
| keep = id(insts[j + 1]) in tset # the result test is a join point (&&, ||): keep it | |||||
| kind, op = CMP[jm.ref[0]] | |||||
| start = j - 1 # the lda #n | |||||
| A = [('abs', base, None, False), ('abs', base + 1, None, False)] | |||||
| B = [('abs', base + 2, None, False), ('abs', base + 3, None, False)] | |||||
| combined = False | |||||
| if j - 1 >= 8: | |||||
| pre = insts[j - 9:j - 1] | |||||
| pa = push_pair(pre[0:4], base) | |||||
| pb = push_pair(pre[4:8], base + 2) | |||||
| if pa and pb and not any(id(x) in tset for x in pre[1:]): | |||||
| A, B = list(pa), list(pb) | |||||
| start = j - 9 | |||||
| combined = True | |||||
| new = emit_compare(kind, op, A, B, T, F) | |||||
| idxT = last + 1 | |||||
| idxF = [n for n, x in enumerate(insts) if x is F][0] | |||||
| if dead_from(insts, [idxT, idxF], ALLREGS) != ALLREGS: | |||||
| continue | |||||
| # the 0/1 result is no longer stored in the temp stack: nobody may read it afterwards | |||||
| if not temp_dead_from(insts, [idxT, idxF], base, base + 2): | |||||
| continue | |||||
| replace(insts, start, (j if keep else last) + 1 - start, new) | |||||
| stats['cmp-inline' if combined else 'cmp-slots'] += 1 | |||||
| if keep: | |||||
| stats['cmp-keep'] += 1 | |||||
| return True | |||||
| return False | |||||
| RET_SYM = b'C$RETURN' | |||||
| def rule_return_copy(insts, stats): | |||||
| """stx T; sty T+1; lda #2; ldx #2; jsr C$0112 (copy to the return slot) -> stx RET; sty RET+1""" | |||||
| tset = targets_of(insts) | |||||
| for k in range(len(insts) - 4): | |||||
| w = insts[k:k + 5] | |||||
| if not (w[0].mn == 'stx' and w[1].mn == 'sty' and w[2].mn == 'lda' and w[3].mn == 'ldx' and w[4].mn == 'jsr'): | |||||
| continue | |||||
| if not (w[4].ref and w[4].ref[0] == b'C$0112' and w[4].ref[1] == 0): | |||||
| continue | |||||
| if not (w[2].md == 'imm' and w[2].val == 2 and not w[2].ref and w[3].md == 'imm' and w[3].val == 2 and not w[3].ref): | |||||
| continue | |||||
| if not all(x.md == 'abs' and not x.ref and not x.reloc for x in (w[0], w[1])): | |||||
| continue | |||||
| if w[0].val != TEMP or w[1].val != TEMP + 1: | |||||
| continue | |||||
| if any(id(x) in tset for x in w[1:]): | |||||
| continue | |||||
| if dead_after(insts, k + 4, ALLREGS) != ALLREGS: | |||||
| continue | |||||
| if not temp_dead_after(insts, k + 4, TEMP, TEMP + 0x40): | |||||
| continue | |||||
| replace(insts, k, 5, [I('stx', 'abs', 0, (RET_SYM, 0), False), I('sty', 'abs', 0, (RET_SYM, 4), False)]) | |||||
| stats['ret-copy'] += 1 | |||||
| return True | |||||
| return False | |||||
| def rule_eq_branch(insts, base, stats): | |||||
| """16 bit ==/!= against a constant: shorten | |||||
| cx; bne J; cy; beq T; J: jmp F; T: -> cx; bne F; cy; bne F; T: | |||||
| cx; bne T; cy; bne T; jmp F; T: -> cx; bne T; cy; beq F; T: (F within branch range)""" | |||||
| tset = targets_of(insts) | |||||
| lay = layout_of(insts, base) | |||||
| CMPS = ('cpx', 'cpy', 'cmp') | |||||
| for k in range(len(insts) - 5): | |||||
| w = insts[k:k + 6] | |||||
| if not (w[0].mn in CMPS and w[1].mn == 'bne' and w[2].mn in CMPS and w[3].mn in ('beq', 'bne') | |||||
| and w[4].mn == 'jmp' and w[4].tgt is not None and not w[4].ref): | |||||
| continue | |||||
| F = w[4].tgt | |||||
| if id(w[2]) in tset or id(w[4]) in tset and w[1].tgt is not w[4]: | |||||
| continue | |||||
| rel_ok = lambda src: -120 <= lay[id(F)] - (lay[id(src)] + 2) <= 120 | |||||
| if w[3].mn == 'beq' and w[1].tgt is w[4] and w[3].tgt is w[5]: | |||||
| # the jmp is only reachable through the first bne | |||||
| if sum(1 for x in insts if x.tgt is w[4]) != 1 or not (rel_ok(w[1]) and rel_ok(w[3])): | |||||
| continue | |||||
| nb1 = I('bne', 'rel', 0, tgt=F, old=w[1].old) | |||||
| nb3 = I('bne', 'rel', 0, tgt=F, old=w[3].old) | |||||
| replace(insts, k + 1, 4, [nb1, w[2], nb3]) | |||||
| # w[2] kept in place; fix up the replaced slice: [bne, cy, bne] | |||||
| stats['eq-branch'] += 1 | |||||
| return True | |||||
| if w[3].mn == 'bne' and w[1].tgt is w[5] and w[3].tgt is w[5] and id(w[4]) not in tset: | |||||
| if not rel_ok(w[3]): | |||||
| continue | |||||
| nb3 = I('beq', 'rel', 0, tgt=F, old=w[3].old) | |||||
| replace(insts, k + 3, 2, [nb3]) | |||||
| stats['ne-branch'] += 1 | |||||
| return True | |||||
| return False | |||||
| def rule_add_const(insts, stats): | |||||
| """clc;txa;adc #K;tax;tya;adc #0;tay -> clc;txa;adc #K;tax;bcc +1;iny (A,C,N,Z,V dead after)""" | |||||
| pat = [('clc', 'imp'), ('txa', 'imp'), ('adc', 'imm'), ('tax', 'imp'), ('tya', 'imp'), ('adc', 'imm'), ('tay', 'imp')] | |||||
| tset = targets_of(insts) | |||||
| for k in range(len(insts) - 7): | |||||
| w = insts[k:k + 7] | |||||
| if any((x.mn, x.md) != p for x, p in zip(w, pat)): | |||||
| continue | |||||
| if w[5].val != 0 or w[5].ref or w[2].ref or w[2].reloc or w[2].val == 1: | |||||
| continue | |||||
| if any(id(x) in tset for x in w[1:]): | |||||
| continue | |||||
| if dead_after(insts, k + 6, {'A', 'C', 'N', 'Z', 'V'}) != {'A', 'C', 'N', 'Z', 'V'}: | |||||
| continue | |||||
| nxt = insts[k + 7] | |||||
| bcc = I('bcc', 'rel', 0, tgt=nxt) | |||||
| iny = I('iny', 'imp') | |||||
| replace(insts, k + 4, 3, [bcc, iny]) | |||||
| stats['add-const'] += 1 | |||||
| return True | |||||
| return False | |||||
| def optimize_func(insts, base, stats): | |||||
| while True: | |||||
| if rule_compare(insts, stats): continue | |||||
| if rule_return_copy(insts, stats): continue | |||||
| if rule_forward(insts, stats): continue | |||||
| if rule_jmp_next(insts, stats): continue | |||||
| if rule_zero_pair(insts, stats): continue | |||||
| if rule_inc16(insts, stats): continue | |||||
| if rule_dup_ldy(insts, stats): continue | |||||
| if rule_eq_branch(insts, base, stats): continue | |||||
| if rule_add_const(insts, stats): continue | |||||
| if rule_branch_over_jmp(insts, base, stats): continue | |||||
| break | |||||
| # ------------------------------------------------------------------ object rewrite | |||||
| def map_addr(newpos, v): | |||||
| """new value of a code-relative address operand; tolerates small negative/positive addends (array-1)""" | |||||
| if v in newpos: | |||||
| return newpos[v] | |||||
| for d in (1, 2, 3, 4): # address = target - d (e.g. a[0]-1 indexed with x) | |||||
| if (v + d) & 0xFFFF in newpos: | |||||
| return (newpos[(v + d) & 0xFFFF] - d) & 0xFFFF | |||||
| for d in (1, 2, 3, 4): # address = target + d | |||||
| if v - d in newpos: | |||||
| return newpos[v - d] + d | |||||
| raise ValueError('cannot map address %x' % v) | |||||
| def optimize_obj(o, rules=True, verbose=False): | |||||
| stats = {k: 0 for k in ('reload', 'lda->t', 'jmp-next', 'zero-pair', 'inc16', 'dup-ld', 'bra-over-jmp', | |||||
| 'cmp-inline', 'cmp-slots', 'cmp-keep', 'ret-copy', 'eq-branch', 'ne-branch', 'add-const')} | |||||
| code = o.code | |||||
| plan_in = [] | |||||
| for s, e in find_functions(o): | |||||
| insts = decode_func(o, s, e) | |||||
| if insts is None: | |||||
| if verbose: print(' cannot decode function at', hex(s)) | |||||
| continue | |||||
| plan_in.append((s, e, insts)) | |||||
| if not plan_in: | |||||
| return o, stats | |||||
| for s, e, insts in plan_in: | |||||
| if rules: | |||||
| optimize_func(insts, s, stats) | |||||
| # lay out the new code | |||||
| newcode = bytearray() | |||||
| newpos = {} | |||||
| plan = [] | |||||
| pos = 0 | |||||
| for s, e, insts in plan_in: | |||||
| for b in range(pos, s): | |||||
| newpos[b] = len(newcode) + (b - pos) | |||||
| newcode += code[pos:s] | |||||
| base = len(newcode) | |||||
| p = base | |||||
| for i in insts: | |||||
| i.new = p | |||||
| p += i.size | |||||
| for i in insts: | |||||
| if i.old is not None: | |||||
| newpos.setdefault(i.old, i.new) | |||||
| plan.append((s, e, insts, base)) | |||||
| newcode += bytes(p - base) | |||||
| pos = e | |||||
| for b in range(pos, len(code)): | |||||
| newpos[b] = len(newcode) + (b - pos) | |||||
| newcode += code[pos:] | |||||
| newpos[len(code)] = len(newcode) | |||||
| # old instruction starts whose instruction vanished map to the next surviving one | |||||
| for s, e, insts, base in plan: | |||||
| pass | |||||
| inside = lambda off: any(s <= off < e for s, e, _, _ in plan) | |||||
| new_reloc, new_refs = [], [] | |||||
| for s, e, insts, base in plan: | |||||
| for i in insts: | |||||
| b = bytearray([ENC[(i.mn, i.md)]]) | |||||
| if i.md == 'rel': | |||||
| rel = i.tgt.new - (i.new + 2) | |||||
| if not -128 <= rel <= 127: | |||||
| raise ValueError('branch out of range') | |||||
| b.append(rel & 255) | |||||
| elif i.size == 2: | |||||
| b.append(i.val & 255) | |||||
| elif i.size == 3: | |||||
| v = i.val | |||||
| if i.reloc: | |||||
| v = i.tgt.new if i.tgt is not None else map_addr(newpos, v) | |||||
| b += bytes((v & 255, v >> 8 & 255)) | |||||
| newcode[i.new:i.new + len(b)] = b | |||||
| if i.reloc: | |||||
| new_reloc.append(i.new) | |||||
| if i.ref: | |||||
| new_refs.append([i.ref[0], i.ref[1], i.new]) | |||||
| # relocated words that live in data (pointers into the code) | |||||
| for off in o.reloc: | |||||
| if inside(off): | |||||
| continue | |||||
| n_off = newpos[off] | |||||
| old_v = code[off + 1] | code[off + 2] << 8 | |||||
| nv = newpos[old_v] | |||||
| newcode[n_off + 1] = nv & 255 | |||||
| newcode[n_off + 2] = nv >> 8 | |||||
| new_reloc.append(n_off) | |||||
| for nm, kind, off in o.refs: | |||||
| if not inside(off): | |||||
| new_refs.append([nm, kind, newpos[off]]) | |||||
| n = Obj() | |||||
| n.code = newcode | |||||
| n.reloc = sorted(new_reloc) | |||||
| n.defs = [[nm, k, newpos[off]] for nm, k, off in o.defs] | |||||
| n.refs = sorted(new_refs, key=lambda r: r[2]) | |||||
| n.res = o.res | |||||
| n.trailing = o.trailing | |||||
| return n, stats | |||||
| def main(): | |||||
| args = sys.argv[1:] | |||||
| verbose = '-v' in args | |||||
| args = [a for a in args if a != '-v'] | |||||
| rules = '--no-rules' not in args | |||||
| args = [a for a in args if a != '--no-rules'] | |||||
| if '--lib' in args: | |||||
| k = args.index('--lib') | |||||
| load_lib(args[k + 1]) | |||||
| del args[k:k + 2] | |||||
| src, dst = args[0], args[1] | |||||
| o = Obj.parse(open(src, 'rb').read()) | |||||
| try: | |||||
| n, st = optimize_obj(o, rules, verbose) | |||||
| except Exception as ex: | |||||
| print('%s: UNCHANGED (%s)' % (os.path.basename(src), ex)) | |||||
| open(dst, 'wb').write(open(src, 'rb').read()) | |||||
| return | |||||
| open(dst, 'wb').write(n.build()) | |||||
| tot = {k: v for k, v in st.items() if v} | |||||
| print('%s: code %d -> %d bytes (%d saved) %s' % (os.path.basename(src), len(o.code), len(n.code), | |||||
| len(o.code) - len(n.code), tot)) | |||||
| if __name__ == '__main__': | |||||
| main() | |||||
| @@ -0,0 +1,29 @@ | |||||
| #!/usr/bin/env python3 | |||||
| """regress.py - build each test program with and without the optimizer, run both in VICE and | |||||
| compare the final screens (ignoring the blinking cursor cell). Needs PIL (pip install pillow). | |||||
| python tools/regress.py [tests/sem.c tests/uge.c ...] | |||||
| """ | |||||
| import os, sys, subprocess | |||||
| from PIL import Image, ImageChops | |||||
| HERE = os.path.dirname(os.path.abspath(__file__)) | |||||
| ROOT = os.path.normpath(os.path.join(HERE, '..')) | |||||
| tests = sys.argv[1:] or [os.path.join(ROOT, 'tests', t) for t in ('sem.c',)] | |||||
| ok = True | |||||
| for src in tests: | |||||
| name = os.path.splitext(os.path.basename(src))[0] | |||||
| shots = {} | |||||
| for mode in ('orig', 'opt'): | |||||
| d = os.path.join(ROOT, 'work', 'regress_%s_%s' % (name, mode)) | |||||
| cmd = ['powershell', '-NoProfile', '-File', os.path.join(HERE, 'pcbuild.ps1'), '-Src', src, '-Name', name, | |||||
| '-Dir', d, '-Run', '\n', '-Settle', '60'] + (['-NoOpt'] if mode == 'orig' else []) | |||||
| r = subprocess.run(cmd, capture_output=True, text=True) | |||||
| size = [l for l in r.stdout.splitlines() if 'linked' in l] | |||||
| shots[mode] = (os.path.join(d, 'run.png'), size[-1] if size else 'LINK FAILED') | |||||
| a = Image.open(shots['orig'][0]).convert('RGB'); b = Image.open(shots['opt'][0]).convert('RGB') | |||||
| diff = ImageChops.difference(a, b).getbbox() | |||||
| # the blinking cursor is a single 8x8 cell | |||||
| same = diff is None or (diff[2] - diff[0] <= 8 and diff[3] - diff[1] <= 8) | |||||
| print('%-8s orig: %-24s opt: %-24s screens %s' % (name, shots['orig'][1], shots['opt'][1], 'IDENTICAL' if same else 'DIFFER %s' % (diff,))) | |||||
| ok &= same | |||||
| sys.exit(0 if ok else 1) | |||||
| @@ -0,0 +1,75 @@ | |||||
| #!/usr/bin/env python3 | |||||
| """setup.py - prepare the Power C improvement toolchain (the original disk is only read). | |||||
| python tools/setup.py extract the runtime library objects into lib/ and build optlib/ | |||||
| python tools/setup.py --disk also write POWERc-opt.d81 (original + improved runtime objects) | |||||
| optlib/ gets drop-in replacements (same file names as on POWERc.d81): | |||||
| cmulti.obj faster 16x16 multiply | |||||
| c0047.obj faster divide / remainder (same results, fewer shift steps) | |||||
| c0077.obj the unsigned >= helper with its broken branch opcode ($80 -> $90) fixed | |||||
| """ | |||||
| import os, sys, subprocess, shutil, hashlib | |||||
| HERE = os.path.dirname(os.path.abspath(__file__)) | |||||
| ROOT = os.path.normpath(os.path.join(HERE, '..')) | |||||
| sys.path.insert(0, HERE) | |||||
| from objfmt import Obj | |||||
| from pcasm import assemble | |||||
| sys.path.insert(0, os.path.join(ROOT, 'src')) | |||||
| import div_patch | |||||
| C1541 = r'C:\Program Files\GTK3VICE-3.10-win64\bin\c1541.exe' | |||||
| ORIG = os.path.normpath(os.path.join(ROOT, '..', 'POWERc.d81')) | |||||
| def run(*a): | |||||
| return subprocess.run([C1541] + list(a), capture_output=True, text=True) | |||||
| def extract(): | |||||
| os.makedirs(os.path.join(ROOT, 'lib'), exist_ok=True) | |||||
| out = run(ORIG, '-list').stdout | |||||
| names = [] | |||||
| for ln in out.splitlines(): | |||||
| if '"' in ln and ln.rstrip().endswith('seq'): | |||||
| nm = ln.split('"')[1] | |||||
| if nm.endswith(('.obj', '.o', '.l')): | |||||
| names.append(nm) | |||||
| args = [] | |||||
| for nm in sorted(set(names)): | |||||
| args += ['-read', nm + ',s', os.path.join(ROOT, 'lib', nm)] | |||||
| run(ORIG, *args) | |||||
| print('extracted %d files into lib/' % len(set(names))) | |||||
| def build(): | |||||
| os.makedirs(os.path.join(ROOT, 'optlib'), exist_ok=True) | |||||
| cm, _ = assemble(open(os.path.join(ROOT, 'src', 'cmulti.s')).read()) | |||||
| open(os.path.join(ROOT, 'optlib', 'cmulti.obj'), 'wb').write(cm.build()) | |||||
| o = Obj.parse(open(os.path.join(ROOT, 'lib', 'c0047.obj'), 'rb').read()) | |||||
| open(os.path.join(ROOT, 'optlib', 'c0047.obj'), 'wb').write(div_patch.patch(o).build()) | |||||
| o = Obj.parse(open(os.path.join(ROOT, 'lib', 'c0077.obj'), 'rb').read()) | |||||
| assert o.code[0x1d] == 0x80, 'c0077.obj is not the expected original' | |||||
| o.code[0x1d] = 0x90 | |||||
| open(os.path.join(ROOT, 'optlib', 'c0077.obj'), 'wb').write(o.build()) | |||||
| print('built optlib/cmulti.obj c0047.obj c0077.obj') | |||||
| def disk(): | |||||
| dst = os.path.join(ROOT, 'POWERc-opt.d81') | |||||
| shutil.copyfile(ORIG, dst) | |||||
| for nm in ('cmulti.obj', 'c0047.obj', 'c0077.obj'): | |||||
| run(dst, '-delete', nm) | |||||
| run(dst, '-write', os.path.join(ROOT, 'optlib', nm), nm + ',s') | |||||
| print('wrote', dst) | |||||
| if __name__ == '__main__': | |||||
| h = hashlib.sha256(open(ORIG, 'rb').read()).hexdigest() | |||||
| exp = open(os.path.join(ROOT, 'POWERc.d81.sha256')).read().split()[0] | |||||
| if h != exp: | |||||
| print('WARNING: reference/POWERc.d81 differs from the recorded original hash') | |||||
| extract() | |||||
| build() | |||||
| if '--disk' in sys.argv: | |||||
| disk() | |||||
| @@ -0,0 +1,39 @@ | |||||
| import sys, os, random | |||||
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | |||||
| from helpertest import * | |||||
| from objfmt import Obj | |||||
| def make(objfile, base=0x6000): | |||||
| o = Obj.parse(open(objfile, 'rb').read()) | |||||
| return Helper(o, base, {'C$HIGHMEM': 0xC000, 'C$ERROR': 0xFFF0}) | |||||
| def setup(seed, cpu): | |||||
| rnd = random.Random(seed) | |||||
| for i in range(0x0000, 0x0800): cpu.m[i] = rnd.randrange(256) | |||||
| for i in range(0x8000, 0x8400): cpu.m[i] = rnd.randrange(256) | |||||
| cpu.m[0xC000] = 0x00; cpu.m[0xC001] = 0xC0 # HIGHMEM = $C000 | |||||
| fp = 0x8000 + rnd.randrange(0, 0x100) # $14/15 frame pointer | |||||
| cpu.m[0x14] = fp & 255; cpu.m[0x15] = fp >> 8 | |||||
| sp1 = 0x9000 + rnd.randrange(1, 0x100) | |||||
| cpu.m[0x1A] = sp1 & 255; cpu.m[0x1B] = sp1 >> 8 | |||||
| cpu.m[sp1 - 1] = rnd.randrange(0, 8) * 2 # popped byte $FC | |||||
| cpu.m[0xFB] = rnd.choice([0, 2, 4, 6, 8, 10]) | |||||
| cpu.sp = 0xF0 | |||||
| cpu.a = rnd.choice([0, 2, 4, 6, 8, 12, 20]) # locals | |||||
| cpu.x = rnd.choice([0, 2, 4, 10]); cpu.y = rnd.choice([0, 0, 1]) # frame size | |||||
| return rnd | |||||
| if __name__ == '__main__': | |||||
| f = sys.argv[1] | |||||
| h = make(f) | |||||
| tot_p = tot_e = 0; N = 200 | |||||
| for seed in range(N): | |||||
| cpu = CPU(); h.install(cpu); setup(seed, cpu) | |||||
| a0,x0,y0=cpu.a,cpu.x,cpu.y | |||||
| c = cpu.call(h.addr('C$105')) | |||||
| tot_p += c | |||||
| # epilogue: A = locals, X,Y = frame size as in prolog (caller passes the same numbers) | |||||
| cpu.a, cpu.x, cpu.y = a0, x0, y0 | |||||
| c2 = cpu.call(h.addr('C$106')) | |||||
| tot_e += c2 | |||||
| print('prolog avg cycles %.0f epilogue avg cycles %.0f' % (tot_p / N, tot_e / N)) | |||||
| @@ -0,0 +1,36 @@ | |||||
| import sys, os, random | |||||
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | |||||
| from helpertest import * | |||||
| from objfmt import Obj | |||||
| orig = Obj.parse(open('lib/c0047.obj','rb').read()); new = Obj.parse(open('optlib/c0047.obj','rb').read()) | |||||
| ho = Helper(orig, 0x6000, {}); hn = Helper(new, 0x6000, {}) | |||||
| rnd = random.Random(5) | |||||
| vals = [0,1,2,3,7,9,10,100,255,256,257,1000,12345,32767,32768,40000,65535] | |||||
| cases = [(a,b) for a in vals for b in vals if b != 0] | |||||
| cases += [(rnd.randrange(65536), rnd.randrange(1,65536)) for _ in range(500)] | |||||
| cases += [(rnd.randrange(65536), rnd.randrange(1,256)) for _ in range(500)] | |||||
| entries = ['C$0047','C$0049','C$0051','C$0053'] | |||||
| tot = {e:[0,0] for e in entries}; bad = 0 | |||||
| for e in entries: | |||||
| for a,b in cases: | |||||
| res = [] | |||||
| for h in (ho, hn): | |||||
| cpu = CPU(); h.install(cpu) | |||||
| for i,v in enumerate((a&255,a>>8,b&255,b>>8)): cpu.m[0x33C+i]=v | |||||
| cpu.m[0x14]=0; cpu.m[0x15]=0x80 | |||||
| cpu.a = 4; cpu.x=0; cpu.y=0 | |||||
| c = cpu.call(h.addr(e), maxsteps=400000) | |||||
| res.append((c, bytes(cpu.m[0x33C:0x350]), cpu)) | |||||
| tot[e][0]+=res[0][0]; tot[e][1]+=res[1][0] | |||||
| if res[0][1] != res[1][1]: | |||||
| bad += 1 | |||||
| if bad < 6: print('MISMATCH', e, a, b, res[0][1][:4].hex(), res[1][1][:4].hex()) | |||||
| print('cases per entry', len(cases), 'bad', bad) | |||||
| for e in entries: print(e, 'avg cycles orig %.0f new %.0f' % (tot[e][0]/len(cases), tot[e][1]/len(cases))) | |||||
| for a,b in ((1000,7),(300*7,1000),(99,10),(30000,3)): | |||||
| out=[] | |||||
| for h in (ho,hn): | |||||
| cpu=CPU(); h.install(cpu) | |||||
| for i,v in enumerate((a&255,a>>8,b&255,b>>8)): cpu.m[0x33C+i]=v | |||||
| cpu.a=4; out.append(cpu.call(h.addr('C$0051'))) | |||||
| print(a,b,out) | |||||
| @@ -0,0 +1,32 @@ | |||||
| import sys, os, random | |||||
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | |||||
| from helpertest import * | |||||
| from objfmt import Obj | |||||
| from pcasm import assemble | |||||
| orig = Obj.parse(open('lib/cmulti.obj','rb').read()) | |||||
| new, _ = assemble(open('src/cmulti.s').read()) | |||||
| ho = Helper(orig, 0x6000, {}); hn = Helper(new, 0x6000, {}) | |||||
| rnd = random.Random(1) | |||||
| cases = [(a,b) for a in (0,1,2,3,7,255,256,257,1000,32767,65535) for b in (0,1,2,3,7,255,256,257,1000,32767,65535)] | |||||
| cases += [(rnd.randrange(65536), rnd.randrange(65536)) for _ in range(600)] | |||||
| cases += [(rnd.randrange(256), rnd.randrange(65536)) for _ in range(300)] + [(rnd.randrange(65536), rnd.randrange(256)) for _ in range(300)] | |||||
| co = cn = 0; bad = 0 | |||||
| for a, b in cases: | |||||
| for accinit in ((0,0),): | |||||
| res = [] | |||||
| for h in (ho, hn): | |||||
| cpu = CPU(); h.install(cpu) | |||||
| cpu.m[0x5a], cpu.m[0x5b] = a & 255, a >> 8 | |||||
| cpu.m[0x5c], cpu.m[0x5d] = b & 255, b >> 8 | |||||
| cpu.m[0x5e], cpu.m[0x5f] = accinit | |||||
| c = cpu.call(h.addr('C$MULTI')) | |||||
| res.append((c, bytes(cpu.m[0x40:0x70]), cpu)) | |||||
| co += res[0][0]; cn += res[1][0] | |||||
| lo_o = res[0][2].m[0x5c] | res[0][2].m[0x5d] << 8 | |||||
| lo_n = res[1][2].m[0x5c] | res[1][2].m[0x5d] << 8 | |||||
| if lo_o != lo_n or lo_o != (a*b) & 0xFFFF or res[0][1][:0x1a] != res[1][1][:0x1a]: | |||||
| bad += 1 | |||||
| if bad < 5: print('MISMATCH', a, b, lo_o, lo_n, res[0][1].hex(), res[1][1].hex()) | |||||
| print('cases', len(cases), 'bad', bad, 'avg cycles orig %.0f new %.0f' % (co/len(cases), cn/len(cases))) | |||||
| print('code size orig', len(orig.code), 'new', len(new.code)) | |||||
| @@ -195,3 +195,21 @@ and compared: | |||||
| buffer that `:wq` creates, `n` goes back to the `file:` prompt. | buffer that `:wq` creates, `n` goes back to the `file:` prompt. | ||||
| - `\x03` in `-keybuf` arrives as RUN/STOP (PETSCII `$03`), so a script can | - `\x03` in `-keybuf` arrives as RUN/STOP (PETSCII `$03`), so a script can | ||||
| leave insert mode. | leave insert mode. | ||||
| ## Power C limits met while adding :dir | |||||
| - **Keep each unit small.** With `:dir` added to `vim.c` (9 KB of code) the compiler emitted | |||||
| `return;` in `main` as a jump to garbage (`jmp $a657`, an address outside the object), so the | |||||
| editor crashed on `:q`. Moving `dirlist()` into its own unit (`vdir.c`) fixed the size, and | |||||
| `main` no longer has a `return` after `sdone()` (which never returns anyway). Even at 7.5 KB the | |||||
| bad jump came back until that `return` was removed - treat `return;` in a big `main` with suspicion. | |||||
| - `build.ps1` lists the units: `vim`, `vscreen`, `vview`, `vedit`, `vdir`, `vml`. | |||||
| - The directory is read like a file: `open(5, 8, 0, "$")`, skip the load address, then per line | |||||
| link word (0,0 = end), block count word, text up to a 0 byte. | |||||
| ## Improved build | |||||
| `build-improved.bat` builds all six units with the improved toolchain from | |||||
| `reference/powerc-re` (peephole optimizer + faster runtime objects; the original Power C disk is | |||||
| untouched) and writes `vim-improved.d81`; `run-improved.bat` starts it. The plain `build.bat` / | |||||
| `run.bat` path (original toolchain, `work.d81`) still works. | |||||
| @@ -0,0 +1,17 @@ | |||||
| @echo off | |||||
| REM build-improved.bat - build vim with the improved Power C toolchain and write vim-improved.d81. | |||||
| REM original cc/link (in VICE) + reference\powerc-re optimizer (pcopt) + faster runtime objects. | |||||
| REM The original reference\POWERc.d81 is never written. Takes a few minutes. | |||||
| REM build-improved.bat -Run also starts it and shows the screen after typing a file name prompt. | |||||
| setlocal | |||||
| cd /d "%~dp0" | |||||
| set "TOOLS=%~dp0..\reference\powerc-re\tools" | |||||
| if not exist "%~dp0..\reference\powerc-re\optlib\cmulti.obj" python "%TOOLS%\setup.py" | |||||
| set "RUNARG=" | |||||
| if "%1"=="-Run" set "RUNARG=-Run \n" | |||||
| powershell -NoProfile -ExecutionPolicy Bypass -File "%TOOLS%\pcbuild.ps1" -Src vim.c,vscreen.c,vview.c,vedit.c,vdir.c,vml.c -Name vim -Dir "%~dp0..\reference\powerc-re\work\vimbuild" %RUNARG% | |||||
| if errorlevel 1 exit /b 1 | |||||
| set "C1541=C:\Program Files\GTK3VICE-3.10-win64\bin\c1541.exe" | |||||
| if exist vim-improved.d81 del vim-improved.d81 | |||||
| "%C1541%" -format "vim improved,vi" d81 vim-improved.d81 -write "%~dp0..\reference\powerc-re\work\vimbuild\vim.prg" vim >nul | |||||
| echo wrote vim-improved.d81 | |||||
| @@ -45,7 +45,7 @@ Set-Location $here | |||||
| $img = "work.d81" | $img = "work.d81" | ||||
| $units = @{ | $units = @{ | ||||
| vim = @("vim", "vscreen", "vview", "vedit", "vml") | |||||
| vim = @("vim", "vscreen", "vview", "vedit", "vdir", "vml") | |||||
| mltest = @("mltest", "vml") | mltest = @("mltest", "vml") | ||||
| } | } | ||||
| if (-not $units.ContainsKey($Prog)) { throw "unknown program '$Prog'" } | if (-not $units.ContainsKey($Prog)) { throw "unknown program '$Prog'" } | ||||
| @@ -238,7 +238,9 @@ s += [P("7. Colon (:) commands", H1), | |||||
| [k(":wq") + " / " + k(":wq name"), "Write, then quit."], | [k(":wq") + " / " + k(":wq name"), "Write, then quit."], | ||||
| [k(":x"), "Write (always, even if nothing changed), then quit."], | [k(":x"), "Write (always, even if nothing changed), then quit."], | ||||
| [k(":q"), "Quit. Refused with <i>unsaved - :q! quits</i> if there are unsaved changes."], | [k(":q"), "Quit. Refused with <i>unsaved - :q! quits</i> if there are unsaved changes."], | ||||
| [k(":q!"), "Quit and throw away unsaved changes."]], | |||||
| [k(":q!"), "Quit and throw away unsaved changes."], | |||||
| [k(":dir"), "List the disk directory (device 8) over the editor, one page at a time. " | |||||
| "Any key shows the next page, RUN/STOP stops; the editing screen then comes back unchanged."]], | |||||
| [1.6 * inch, W - 1.6 * inch]), | [1.6 * inch, W - 1.6 * inch]), | ||||
| P("<b>Quitting restarts the C64.</b> Power C uses zero-page locations BASIC depends on, so " | P("<b>Quitting restarts the C64.</b> Power C uses zero-page locations BASIC depends on, so " | ||||
| "instead of returning to a damaged BASIC the editor jumps to the KERNAL reset routine " | "instead of returning to a damaged BASIC the editor jumps to the KERNAL reset routine " | ||||
| @@ -257,7 +259,8 @@ s += [P("8. Status messages", H1), | |||||
| ["cannot write", "The file could not be opened for writing (no disk, drive not present, ...)."], | ["cannot write", "The file could not be opened for writing (no disk, drive not present, ...)."], | ||||
| ["no file name", k(":w") + " on a buffer with no name: use " + k(":w name") + "."], | ["no file name", k(":w") + " on a buffer with no name: use " + k(":w name") + "."], | ||||
| ["unsaved - :q! quits", k(":q") + " with unsaved changes."], | ["unsaved - :q! quits", k(":q") + " with unsaved changes."], | ||||
| ["unknown command", "The colon command was not one of w, wq, x, q, q!."], | |||||
| ["unknown command", "The colon command was not one of w, wq, x, q, q!, dir."], | |||||
| ["cannot read directory", k(":dir") + " could not open the drive's directory."], | |||||
| ["line full (80)", "Insert refused: the line is already 80 characters long."], | ["line full (80)", "Insert refused: the line is already 80 characters long."], | ||||
| ["joined line would pass 80", "Backspace at the start of a line refused: the joined line would be too long."], | ["joined line would pass 80", "Backspace at the start of a line refused: the joined line would be too long."], | ||||
| ["out of memory", "The 19,000-byte buffer could not be allocated (shown at start-up)."]], | ["out of memory", "The 19,000-byte buffer could not be allocated (shown at start-up)."]], | ||||
| @@ -0,0 +1,9 @@ | |||||
| @echo off | |||||
| REM run-improved.bat - start the improved vim from vim-improved.d81 in VICE. | |||||
| setlocal | |||||
| cd /d "%~dp0" | |||||
| if not exist vim-improved.d81 ( | |||||
| echo vim-improved.d81 not found - run build-improved.bat first | |||||
| exit /b 1 | |||||
| ) | |||||
| "C:\Program Files\GTK3VICE-3.10-win64\bin\x64sc.exe" -8 vim-improved.d81 -autostart "vim-improved.d81:vim" | |||||
| @@ -0,0 +1,66 @@ | |||||
| /* vdir.c - the :dir command for the c64 vim clone. | |||||
| * | |||||
| * Kept in its own unit: power c miscompiles jumps once one object file holds more | |||||
| * than 8 KB of code (a return in main jumped to garbage), so vim.c must stay small. | |||||
| */ | |||||
| extern int redraw; | |||||
| /* :dir - show the disk directory over the editor, a page at a time. | |||||
| the drive sends it as a basic program: load address, then per line a link | |||||
| word, the block count, the text and a 0; a link of 0 ends it. | |||||
| RUN/STOP stops early, any other key pages on. returns 1 if it listed */ | |||||
| dirlist() | |||||
| { | |||||
| int f, lo, hi, bl, bh, ch, row, col, r, p, k; | |||||
| char t[8]; | |||||
| if (open(15, 8, 15, "") == 0) return 0; /* command channel first */ | |||||
| f = open(5, 8, 0, "$"); | |||||
| if (f == 0) { | |||||
| close(15); | |||||
| return 0; | |||||
| } | |||||
| for (r = 0; r < 25; r++) sclr(r, 0, 40); | |||||
| getc(5); /* load address */ | |||||
| getc(5); | |||||
| row = 0; | |||||
| for (;;) { | |||||
| lo = getc(5); | |||||
| hi = getc(5); | |||||
| if (lo < 0) break; | |||||
| if (hi < 0) break; | |||||
| if (lo == 0 && hi == 0) break; /* end of directory */ | |||||
| bl = getc(5); | |||||
| bh = getc(5); | |||||
| if (bh < 0) break; | |||||
| p = dnum(t, 0, bl + bh * 256); | |||||
| col = 0; | |||||
| for (k = 0; k < p; k++) spc(row, col++, t[k]); | |||||
| col = 5; | |||||
| for (;;) { | |||||
| ch = getc(5); | |||||
| if (ch <= 0) break; | |||||
| if (ch < 32) ch = 32; /* reverse on etc: show a blank */ | |||||
| if (col < 40) spc(row, col++, ch); | |||||
| } | |||||
| row++; | |||||
| if (row == 24) { | |||||
| sclr(24, 0, 40); | |||||
| sps(24, 0, "-- more --"); | |||||
| k = skey(); | |||||
| for (r = 0; r < 25; r++) sclr(r, 0, 40); | |||||
| row = 0; | |||||
| if (k == 3) break; | |||||
| } | |||||
| } | |||||
| close(5); | |||||
| close(15); /* last: it closes everything */ | |||||
| if (row > 0) { | |||||
| sclr(24, 0, 40); | |||||
| sps(24, 0, "-- any key --"); | |||||
| skey(); | |||||
| } | |||||
| redraw = 2; | |||||
| return 1; | |||||
| } | |||||
| @@ -125,7 +125,7 @@ endobj | |||||
| endobj | endobj | ||||
| 17 0 obj | 17 0 obj | ||||
| << | << | ||||
| /Author (Daniel Covington) /CreationDate (D:20261107083851-05'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20261107083851-05'00') /Producer (ReportLab PDF Library - \(opensource\)) | |||||
| /Author (Daniel Covington) /CreationDate (D:20261109152126-05'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20261109152126-05'00') /Producer (ReportLab PDF Library - \(opensource\)) | |||||
| /Subject (\(unspecified\)) /Title (vim for the Commodore 64 - User Guide) /Trapped /False | /Subject (\(unspecified\)) /Title (vim for the Commodore 64 - User Guide) /Trapped /False | ||||
| >> | >> | ||||
| endobj | endobj | ||||
| @@ -136,10 +136,10 @@ endobj | |||||
| endobj | endobj | ||||
| 19 0 obj | 19 0 obj | ||||
| << | << | ||||
| /Filter [ /ASCII85Decode /FlateDecode ] /Length 1114 | |||||
| /Filter [ /ASCII85Decode /FlateDecode ] /Length 1129 | |||||
| >> | >> | ||||
| stream | stream | ||||
| GatU2gQEI6&:N^lqC0C\(8:V=r"`d\b*A4OfYl(Q^'le!Pb!`7&RbWe>/LclJk\-,'SmZ5cKTD]>;2`FB&.Z;!PFr?oSrt40OFmF7)JkGGWd-s)>,d4:8C;Pc\\AIeI52C^PWt4J%CdcE(:)lXfGj_GR/'mZ_jakU@B#+KF.S,=qAAc>[_PYrX[5&NoqK++umuu$n&a3<Yj?8dQ`lTYZ=]L`XmbWSNU%ps8:&rE1.UmJ2!gZ`_e'\ST;ak&T<!9JTnqg:(qK#`a9GB`K-c4fM.B-G>q@0EIArU\PPBl,*3I6J0Zs/<1^?e#$+Dd<h)Z\N!U\NU3^.,P!W1c*ORl@[nH%(SKENV4Pfa;L.m%t:1`(nr!>N`M2D^%&P9Pu(,8c6fd.I]*GrYN^65hMY@.89ZrL.mJ9#8`/Ss7=%Gsbc_0ui$$PuY0STc70)I*a?RN4jZKDY1W?!QE^p;;6Eh;?#nKf)h4^36oTPZb]R`;Ecmo!A#V/bi%G]6XWT4_V^2+c7;9bb,D>rW"aueaBLIKpQVu:k5-=iS"Mb]\?UP"q%tb\HP?\7&InuqLZd\ZZD7s`bdI!PpT3!>4[sk6"RNY;h`oDq\gMMmi#R3h(2+6W6I<2=tW4/99oi,-!_A"aD$di"h\Tq.0qOI;4^kFpb@-%Vfr3dhUoY\7T.-N%Hgh7]l/P[khF-:Ze$j\bIt*nfqBFkOM'DD20Q:EoPG`.p_NTUb%O+8ijbcn=kL*$&%]JskWtb>Sbd?7[**Cr,oR7+RO)"YIUY!a$Og#E#(*g_L\mQ2]<D\M'?Q<kii+u%l?U'2*i`7a.[d6_Gh1B3`1_V-btcY!FEp-Hh=IP"A'YpC%C*^/>o5@!J"W<9B6TnmV5rC$i#S`Ms2O(%?>a"N7#rR!n+T79Ts"_u&GQel+!P)PjOlsaQ<Y#p,S8G8c+UOX,oDpp=+mCU]Z"p:\h7Y>hfnRJ`j*He&XOj6VT*BrFFY0IPdr!BK]$uP@R8k3?XS50<a[,b+u%Il88K*-D-LiUQa'Y=7;s],=)Q\uZ7RVU;dhaT'=Vk:rtR-f;gLloGf.=p`]r)Wm+8?8>[EH=ctcelp66*4`oA@HB&B%O3'i/D^H]&JD?~>endstream | |||||
| GatU2?'Eu#'Rf.GgntXO=bOL?bBZ`O[ZbF'9bW]`@:+sF5ZrR^Z@M#npWD,>+'WqF^jm8)]'%jiY-).tn]feVX9<c2LZof]`aQRa-AdEOn*kKaqDRW1kjT]<Wgb@JAVjl85.XbH^q/`l"G&X,(iO6tbM%MS)jo/e=m*1KTHm4h;e$gF]!8fVJb^+L^Bj@jK*Y5:e:".el,iuf>5B%um+3]o8o(.UVi1@AmcV?E#cFqn!XO^K<]J6(7d&QH8&1VNESt^)U=DYGU@8tQH2c](p$=$GX';UeUprIIP$CC;!@!U=O+G5+d8XGVML/AT$EQ2tX;DU=W]99g4\?&qQ+,8[pq\M@1KAT0H?-Uc8VTo&.;,GZchrG;;^;\Y3>$8:%uV9%+P@MI,qWuN'u&[cdB-gS$CW_%$:$W#iYJtGdeFo<!H5]!0Y-kTps*%29a.tWp[o$&S\b:"[l1G[0Dj]1bjKCWBDiak1gA_W9T$2\D*n3&:%?cDjqcaLWbf(p]';.]<0N+YUeIKPFnPekXOT40MS`"EALKga/%<b*jN_WPL4S,rD.Z%s,EXG&M&Hpc;X@:lVJob<qa_<r3j!ZoM4h.[_J+\R`OdogRblStr23k_Qr0&fDNnC^1m9I+6;"i9As(J'2%+Y-?W%G?4JFt,FQ5m[c(<bud!K8LZR:u6_5P<Cgqe5q#GcFD<ude.r,26%-#gD=f$ui=,TJ+C[V1\hCpAM+a>U3;I>]<%&BZs`Bmf2):"Y*40.jb3V.H]WGZpK2^P)_@LMBa?YWSf@CS)BU-T&'eS.d5`mrZ%jg""g6h`TV:]'LmCGS2$2pUoW4&'%e\ZKWLcGZImJe\4%XDs(dgg>8UPG5a<X2K@.A&l.5e/Rq9.:K2ORc)qefYbh:?]]8kcBfo/Kou7bmYWUidp(%33F[^n6^dk"X*ru85liuu^HprXM5cF)tF2h0H0J&'a81PnR#G1CkD2SV(I7kY=NlCjP]G,p@(#C1L"WYI;oG*5%"Ri5i&)[?\-2-Y;,pnd*'%:]KK^M^+VSV"p(lK\A,aT'eA!c#RgVD7f-$D4(EGMKIk@"phdOUL!.gQ'Vh1NhcHr%qh`Gh-3?IMfVVgPR)%WB$)rD@M2)%qo-"k=Q>'*~>endstream | |||||
| endobj | endobj | ||||
| 20 0 obj | 20 0 obj | ||||
| << | << | ||||
| @@ -164,24 +164,24 @@ Gb!Sm968iG&AJ$Cm%H5#>FXdPd@;1XcF?R^a-PJ<0/Nu,N%'o9+=m<_?%?is'IbA$3usA.%L=WZgBDc8 | |||||
| endobj | endobj | ||||
| 23 0 obj | 23 0 obj | ||||
| << | << | ||||
| /Filter [ /ASCII85Decode /FlateDecode ] /Length 2632 | |||||
| /Filter [ /ASCII85Decode /FlateDecode ] /Length 2844 | |||||
| >> | >> | ||||
| stream | stream | ||||
| Gb!Sm>B?9+&q9SYkZ")X3@/0#JA?VS*9U;pA9/Affr\2LQSBC]`/'PdLhM;:Fq7Xt">];Fg=gT#QA1gA9jbVP1f3^4PCMCG^kWe4IaUL1@C?<`:^i_>J\0Ugb?=h_V?`,jjm.f'C6#,Vo7-O'#IL#4BtsjIXC8L/f!#i3`%)>,@_ENb&TY)a\:>Y5&)m9$>OZ^$rIijNU0f9'd1sj/$VK=/'*kkPofEL`@q]"'C4pA[Y"ihD!fCSn[Y=a@WF8C*"5Jg99bl>s;oJkRAbl=!EUo*LWB1=LjOib9d>JZYBCh'3)/!,G"00e"H>1)[1(+Kq:J&ZI)`19cJrYmh_95V830K(e$X[<6#N+pJ>?=o_FN#:rqSnLX&KIWbJZ]cQl^Ck=2'X?I2$e^2qCoPZGkVC(UCYRsN],,Ci0Bg%s7gtg2a?b.X('r&ZX?f(/d-jT8+,u994)aA/7`XW*qm[AjDo8q#pU$3E"gEu;9Zlo`"Ep^?GOVN;g8=f\.lkJmb!2?l\QmMcV#;@UQ@-mLbBnDiYBQ-b>4<qRLp.J7s"f1On]=:YUBQWc1g<tO2+sl*[-RW5L-`%U7ai'07p+t\^GI^PHN(XYJ#3C\'b9Gb]cq1'b$l;GHp8X-mFMakA9Gf:O)aRkV6K/o/;U-L;,t!Pc9]%l@:Wi:#"5bYqZ;;$grL/_jlr,Tt?VAL`_"hODBT)XOl.C@SN*qnk$8XBC_%/a6AMN0@mS4jeCJie(CWo/;h=2=n\[=LJ/N;)>UOd7P,.8jM!Q+R?Y!],@Qa@J2XAn+\P(#/DObWkcu<B0g(.6,sR@9&$+?=Y$dZNk_,=qG8kg/&[_FY7XcUs/ed-_ppS^8O=U%"%[TV$1qh8uG`LY0djVMg-HhK'e*C\I?n&o'5oHifkMTfP@DUs57jDejlEmIRBqHE\@=1Ou*R$jrX/=n?jUd5ec$\d>@kq_hF>EA?W>%oO`e6\Q6)tqrrr-g/j8Q(\PW8KUMkb'ts7,dQ@J8kC&7LuO=9*`^M0J(3]%i@,RiI>1)>/ZWW9JV$NU*R%G$A8=3F`QWo:OT@9sk5VaP6(@N[IG);tRoUHRl>L;h7TaAqTYSg*?11Pagq9T:qU<f>hF4UoY`D>XksiHk$0]UYCQOj"m$"$#L>XdTj'1F]Ga3Br0rg9?FG/eUi#bbmkkY.k7!q^#W:\nS;6]@Z@Xb((m.m##hMEHlk.Y5ERSqQtC-r3`*\0.WL`f1a0nNQ,p6GgiJ>j.iF(dba?Te^e<\+SWLi4jp8_JKKkT:]<fgRbYQnE)?\lk%Z&fBS?BrJZ&/`]HndiM;8#.]I9aUT*X.V?jaK/g:*RT(#FJ5;Li)\8"^JaRP:B&h0G^uM_r"pQAt4n8F=Ml*1rd/AlQ10&A-uJ$j-;1?GittQF#nQj8o0LljlbZ31BXlS?$2sSIp,cDWbSqV=4#Efeojgs,1DF==UA]k.8PiOM:i,J2%pEP+g(ZA8_AW/%Flq*TM:dV$[uMKh4\P7[$ubm,:Hq;;q43m=ul_N!YSa7=06,pU=Xg<0:C=-)Vn&'@V#E;gX2k<'[$d!W#(Du0nj0e#Oq/N827%"!_,cbs5K`^f[)at(G;ID*l3W$LX3H6i#@\Q'MH7Z,!i>,bh7Sob`Kk:(s'QB.R44H[RN<9r&Cr/r.dGhcXa_G+R_\t\@_ceo7b-D0g`o+-N#eErV#LL:<,O1rXoN*4=eZ:$JgfZ(DXe(4^odh.Zf_Khq`\X$esTIQ_cIUY%t`eiJrfHP6gq@.cBb<?*P5i8378L<Y$!QoF+S0DS9n"FJJT_D5Hu[`[-CGOj#1g(:u)'J7gORVD>$g7EQuPI"9GU+5C-O_ImWBTD@I6Qfd_)=V+hZGToimqOf*Y`ekiPi;Q_nd);<c5ndcZn;t$jC[<1jLH>=)M*C=05*IHNk"pA%o^k.h@%.U<@=`HH^6GsfQC\;O,i?eN5tf"C`AD]#CfN=J3N6"uL9qNSJ6q@;].'0YCKkgtka<J$Vp6\ZG+]&a)VVh,4I(muTBOj&1c8KK9p!Sd:P]u=S'pdPNu49HG<ET'"Q<'KQ$U.TDTE]-h/62k)8kqR9c2t/+bFCf'D@9@&S6`7"u7FHa<#??(-,<u`EKW-Mn15[@EfrDq&#!%6JF>PN=^>i=O?->:/g@D]9F#'Ia!j3j14l9l'))L-*_L%0'<VVkgVs?f!+515#S90aTo+"0@H-7C1?pn>s;^=XO6[/LR;$)S<&71/<"!L@Ki$$C"C7UGRTJ(FAK@N:FM<?3K31,JTG:j\+J4qE5_fi7GBK--kq:Z\0AG-)"$h8$,DB2P,9gkokqfEEn[n]cl`R7#&/`loR__Y$Z.li\:0(`biBb17aAt02e>EqkCeH80r-I4pb`9Rqss.a`Z,3i]5@,qnc!,;Qd=?sRf"2A<JL.%1R9fY@mO;bp9V2T<V7\KO/=>]n'OmUH2(!deb_tiqL3D@.Ym$6hVGmDRlbqiL6?(ag4/5?*Vm5-N)`lkGhg:"(iq@Z1LE&QW:tZRXXN\c<74GVaeh4t^LBOhe0ZHd8J@9@Y.Y.`hJ$h1N@\hFAo3c@en67s;:70KNlU<Art?'5`_4:n!\G'&BCZ9*Vk_$tO$&(#0&elO"NUGu_q)HTHL$;5^tHI'"/e(O~>endstream | |||||
| Gau0E>B?9+&q9SYkYuEL.Oc5;ip&WdNcQ]5G]OL%fr\2L(Q:g!0k+Yt9XMS6F:VFt";9Ci<,=Z*I@Iruh4oOYs"sQ#hZ/%k_o3u41`b^/cj[mDq(:CGc^rI?2\*N?'rtG<9N?-c%GJSW8,>J$W1+"8n;@`L?@iWfGRV]__):+dpl`Z+/-F2V]3mAkQ3L"U(?Rhhrf62$M9L(\17XNU=7;9&g"?-1KkrHM/-e&L2+:S`a-_FPR?h@ADo4J,.:ImVOgV/4Hd,Sb5Z(SNc4mb?kaK(uTMDYbc<S'\6LJZ>UIrP:f$])K1n\0CYE[64_o8gTA@N*&VG]_#f;RWH-QQG`7&,H?R/FdDYLID=X;SV/N$j&8-NT8<;R\nm9?Kt'CW`*.'@p??5C^59?[TMsKuN,*k<Fg*k;*An:6U0W^:ma_*DieKhSHO/<Uh[FKZQ_"Z@keVo@I=8q$DB""'^o2ai-(P4BCAiOai8,M\G#!n/r,`2ME58L`jL0.,f3e2J+TC:PF1'l*-PnCm>@9KIf[F+P/81c#RMF6j-lE(Qe/D>f,&*!Z\W#=%GK*?mU*:9Vc)pCjeQ73JW\dO,LJ>chYf]%];A]p?>gT"2GT)M1Hlg8,lroKh8'/1kCa601#5dViQD#!/<]'dcboQs5QeXFUV=f'O*F87gI#L#h1E8qs=f5;HAP?7Kt&#KQ&Tr$k$3VnsAI,.Mq08I#5Qd2ST$Xr_SNl`cean<82e(411JF;4,,As(KIsdma7qHu+8,<Zb<%^9nK>P]Af0@?r!c2QK1g:I'%R>id$jHVq]pG`6o;I[HcMf!-AN@?njrKUABU>Bs6N\pZXf5ERd(Bc(<,4),AK2>]t+bG$n`?>=O@8&*L$.'o6ebE+@*lbli.P[K<?%$um]i+#p[U?,gE7b4I(G,H%$aGsU^5"0/jZdeTPXZ1Ve6p0=NTlfoU)72pq\TjK@b+t5/6D="DeZC*HG;K8JpI,*TS89&gi]Z(Fs)N*Ds77c1\4Ski"DkD=G1G"://3k6N^_>\P9!L72,ih>EC4kGZVLPA)8XDXic@Eeen4WS,'f`Ogu1r+CPk<EqHsW\[*!"dd]hCKI@1PdHo1)2LAt$h'G^<b?*,MpE3,E.<^FeLni#_-1#%GS:YCE0ENEn=hsU>uRAT6oooq"_PV&kY&a_jtKrVdki%(+9/Un;3Qu=YZA7_,Ef"lb!HXV`pFI5UkKcHjDII/Il[["AHT%gd8U<[BH$LTpoNjT:*HtGu_M;UB'P!2;dH^lJ]*"`*?]%uegR!<'%mDK^k$gN8X%;Y6t4<.".oZ3>V[n!H2N"5+8!<nJN!]TK.hah:flTqml,eOq@G7KtcDkr8b)p;]&p@*RB5;u>5JO1281@Upn%%-'G_X6qAA&u6OXC[+*;a3\kmFW!';eR&e6Q1qua)2!ApH$J_4Q2?gq$E++kg<&iCp5d<&3qCPcPptX*^$$'%`?kQg2.@[!D0M(fJ>rXl5N!iGp_V?M#C!sBL:IO2/!R.h]dk*M,L8X5@uIh_ZV\+'C^M],$MI3TjR*pEWf<kD>APrbNJgiF'#[[Gl>$./"7#pW`HO=*QXkr2m-@9j$UB>e=nrtg-,DY?:U`VFS1g"VO?W_ZB()C[G2d^`6>Y?kj8om7$(DP<Hi/1,HY[R2$n_7L94WmB1kaZ=Mh:`8maWHHB,g*]TZ#-DM[8Q[Mom.%o?2\8V]Rl9+?.69\5T(ls]1_4@@H-#0]aBrl\c"0"j%$^.6gI^k\ufce#C3>DDnph0'-W;nn_SDb5q:b`XfC,S]2pMM@t:5s!3Ko@ifp8%R@\.%:0#DXP5Dh3+-V6+id9"M,D5IseiAdBP]e^N^*;j^>$;r`^$1]LI%-#Ah.O"j/6;2ch+$P&Z_Q%Vut?CdZ$p+Q3o"mQE$2mN>_DeEr?@(@rMqaOh5Ar"?[7VB7p4K`%UV`<,<3RD1[eiicM<Ylle)nd0Gs;EegS]lrVohUuI&I\OBp'AMFCgtpj@$[+?8*_Fhojti!g&7J"=7s._u3R\21K1C$TNWSs#s+^>BTF^]RS\".ZQ\/FHL5P\9<Xr"7o&+Aob_o+n)7#MI=EQH`kTn,R0<*AUAaJ:QcoIgFQg"b:4*bXgH:M\>c:'maMg[@EfrqfUXuUMp>Bn!"W:!aNH^#NI!0-B)_GLogA4nm-H:e*(bW9+oc)/^KO,2(0LXM]nL_H12G:\$Shs.S2CDMRH+>iUBfCi]ubo2t++1)63F61V8JCoU00U$Muggci$m)V5n+('B<Jus]NY==!SS7/fKd/FUJ-gGJ\ln,H\M6W;en+s<W\/B:@/QKXt%mC?f7oC_j$*IdOoR4`Fh70c=5rV<f\<s=[14r=cRRqX`^:YF.L(=\PU)g>\FK7mtWG!n2;%=8:Y`,i1b,mL(A5P)c78`n7'/XiBPk04h1LpX]W(?BE?aKnN6ej34/$`AJ.7et^Jr0Bp%OY"Tgp'_#W(T<j'=[k-lNj9Pc#fa):2!VoERE<j^^fZhP0$r=c"Xn!Nm9GabjLG::H!pnGmZd6J3T[`9W>5_;qClc+HNLkW-[V-V/'\@*bm\:BH1e.pD!7FBqtJ&Vl8_A*NtE[lcr_+T"DjZ>X%?FK#N/eAI`[91Y-S",bsg7QRmD6g8FAcV=d,OqX86]\Yq]S@=:*&Fl#R%[`Z=/NI0[br@Ka0hP)9@GPZg"-`.I,go7<Rk7>2#R[1]<F'^9lI+3KtTqPCQVNPOP:Uf0Qm.bQ4oeXQL78jF]7J387TW%[_k3DV)83YXl_"A^eA(>DN%@_N^^JPH7N!=^D6!o+1?`ho0HI]":4&,!`8_gI_Rl+od8U4@>cHHs*ZqJU'K.&_V)tbAfc2~>endstream | |||||
| endobj | endobj | ||||
| 24 0 obj | 24 0 obj | ||||
| << | << | ||||
| /Filter [ /ASCII85Decode /FlateDecode ] /Length 2291 | |||||
| /Filter [ /ASCII85Decode /FlateDecode ] /Length 2164 | |||||
| >> | >> | ||||
| stream | stream | ||||
| Gatm<D0+Dj&H88.Z-DoQBSRGZpl4p`9q+_#fCmWL+a7u`HkEde-ar,=FpS-I<q7aNR,VG>J.^L@mJF6<5fK':hq\k%dhG8Q)JD0*(SDY'!Rkt+3B_XK.=Q9T%hlo3eeM-6-C+l'cX\I1[">o(^_eLe(d.[=/6Du<MS]N)6ftY,Jn;_8giJq"kKN\a4.&4IoY'2?A2;^NbA^'K2e7buNNGG7O:?c,3`/T#$!.jI[2i`cMIp%PKqF*:R.?2ufWFJLd8]WERZ1*fT#LR?AQ%lcR/4`d`?pU+T'.J_@inKVp5l':P+tW$Et_2bWR>!sjf?f<(B3CU2S`VFG)H^e6tjflPn"JL6PBjb@.5nEABVDUlY<Gr35&Hj=$?lSq'eu19!D]tQJQCl]7[fJ@XlL*'%6qqn'1h)rd&ZFrI+jTJIGtGg_JcZ0gN;=82hXKmlrHX+XAdtRgZcq1@WcG4/G;KHedpD(G(JBIR)MJpF_aXVi#VB!#SrsLpE*h^'epI(]Fg(k9*3QY0;:b@/NhP'\!6#e8FY.4g2Q@6e3fB2+o"<Nh4#X;[s6'.I$tkg)SU#4i&uEQP!7.YM6.Ug!e4?&Y<uql/o5Qf'P'KRRIpk*[f8JfHR.hQQfq;Q]-DcoI/6lqE1UrTh$W/*#U3_;3<De84^Ed'J0/)_!`f]2DF$q?2?#t5)9nfD;&f0RtbD$[N%Kd6$UkQla!VK[hAVX;P-C59Nnbe55d0"4:$!_Qm_2Cb_JGrn"e*rP/LYbnn8<Dj%?T3ZG\c#n=1gqfV;dsqC[s*:iuB<_m@ZF&tgO5\k>Pi>ojUs;j-S&g,?mR]tlk=iQJBcC`AqR:Ga"QAUXt3_SFD?+A:]aU+NU:ijU<hI@)a2G<,PPrspK7XWr:_J7>RVj63-qL_DTQDEja99NsH[9ILK?rZ&_Er'`2<eZBR/CKLN4/o"+_\>o5%"@;jhHn<n:P)3I;1[B[]:!Vd>Fki'Zo@7TiR]9g2$.eoNIDR)n-q84Mpb;R!$:U^X/"2.$\jTI!9i^*nlr.#,kR.^WJF:a.+kA>oM`#$73I,+)pm"`"&CIloN%UfYA`Q1-]>6t]P)uIgokm;eG,DMJQD<ZS=EL(mjI'8i)qb&.;j*ph\Sn7P!<t7>E-jUY^O8g/:4Eao91?)<0*EOW=<Oj<YWKG,*W5PRbb<8eX'EW]#P`oU!?2@Wp'.tEVjj%TB-Dtn^8)L':kH7e=U0CTfsCs+]Dgs2I2'41$,bn*W$4?25ODAg@%I)0Kd]VAIgm5..P8bXAQmQ@nEJU%?NTK7J&*O"F"hm&O?JN#C21L6U;jWi>XTI\^5.(8n_3TtZ*gZc_D5=:#p=`2m%TY8,lVh<2;Y)/74S`sjf\*#DHccX6l@"Rk%<jn\T%iT`(5H>PQ;p1$;(TV\fpTk]g!P`eGCAo.*X^sClJfA%]:LG&H86g%u.@Z';Oni"N-eGR[@40D">^mY`t(S1<4SSai*TuE+;m0/i(H/^;rQD#ubI+/s@$l""oKjJ-4A:^9o`Ei!!G17n4UD8RA2P.#LoLV^#F)1lI$>7+F;IH6[KsT/K6tm&--E^%Tni_[&OTI^Io!9`5bg;q%-VZ]q0Vn\A70;i*"9</BM>bC@+f*9TP7TDRc=+*bquU`f)Ic>HMYfiKX))S[fjC_f/r4EL_6Y%p+mZ"?Ws33Ej%I_ATG_G<KR!rooj2B("?!\.O90r(B]a9,G0XB:-E_EpgFk>9LfO&_%t7:fXK1f&2l61/"Q&[h\">mYM?cXfRm<K*a?Q"7U<=Qa>\*$m4hU2bhr*0+r4IjhRU?o/&T=I-Iq$/&^>mM0K2Mq,^#"[\\PNkNNN^3:iV?HBq.UT_?NUX\>FbGhNpXJK>,p?$b=,-:?@eJ0XIfC2dqpV'"]N4W^UUHNsX32Xn("\`XU4<bR_kscpm9N"qe0._Z7%PeY>nR7P4!JMB>/B1F*0]HMLPW:(>FBO,'a(8\8/%fXCXh3JqL(S"UJ8&c\7h2DgZGcn<(*ZSWF"G%q-5@^BMZ,(Ta&"ZS1m:R&&'U)@9pDia6:S'dnLO4C=i/SAkQ+NaA\[ILl1KMVHhQ_#JG"5o^2jr'TQQPfLp;^3,T&,=+:s_uL0Rp$8<&nGo[Ac5nW?oD6TTD\er79\MB9TJHA2Dil9e-`-JF>JL%SS6T4n:36IsQnkfYt2M7D1^?JV'a?J]"<r!%3[CK7-t%GkDnUUR7@gV8&CJ;$*-[..T%@\?n<h?pW5D-n\N>(%Ea2+DsErV7p,P\Z"Rm?Nao\mCsuT2]<M3V5D0pjCqRPQ(X\e59<~>endstream | |||||
| Gau0DD0+Dj&H88.Z-CM;cjhp^"d=>t[(3&X]t60saPGV[?]6R%M/>8=%,p+$8iARE"2X6<KRg]gdd#N9ZSFed\"Q"a#iO/;HIi?<-B+g0fU<8E5N24tm-1TrilVKnX^@$&Nc#:W[N1T=aref>\\e;0d(TFjk#$=ZcgMFA*b=A#6)_gAoV,_[lh%EM`j4i$9P[(g=n1:=j`XtAIS1K<-@rIL/;bP$&q(ii&)VO@:r[4T`6Vsj?L^HJflq@%MNV:FG1$&2WXC!%06L)Tt^Vc/bAfI%Z9.>o;p?3gqm_mO<khja\LfPqAF3AuRn8t..73][J](uR=]aRn;V2CrBoN]FI$Z9q^&L?;[]*Ls:m[.%Q^q9d_Bo*?hUkR&qS$'oTRn<'3\!2X'^l>f?_gkX%,N0t+P7u[#qkSebSF'G+Tbp`l1:r1Sn*r7S?f=SN&g'7.<c2;5BHO2\fpX[o6n%\P`7c/,lgeg(bWAF9.`!k!G4He5@r;HNCB8F3R_p+Gb'b%#X^XWgqG$q_Hbn\E2_2k[?D7dpNg/;X24TXbo@h<8b8Z<4l8uD6Kn-`'qHIknEgp%oXJO6sN=BfMq:(Yp/!]8.)/6Vh*E=D&i$4[L).1;=Uf.H3/I1OeWc=/>1_;VUZ:<RLgO)^1C9XfNmM/I)FhcNRVluI#1J5$3(%kY_?>P\'GM]^J)As<[d*^e*(bDAdcNXNtgfS`9s\q2@?6i>3gE!;G*I7nc<E@Fhb-F?t,KXt&pgCNa2.,NJG%Rnrb>7(:<K!P/R81X;0V]Y%k!)`.go46O.8JfJCX]dJ-?L#jV\CZEF3)U1:,&r/_4<`qqnRu![^<>S#L9@t)=AYUJOcO%,1p'WUQ\UeN`"Pk>og!Wb!rap*Y>(+[#G'@W2"!J;<.Gc0E3gCoR7Cb4/?jfcY;e1`QnbDIS:/AkLGNdT,(CJ^2UEJr2*l;G-Q%9ArGmWTEOins,/kDimigJc-[IMIDiNsb;Yfo2D*otYlIDgW62@b]\fMrpN=7:T:,Ob=e$?A&[5PZtG)aYIU$ILYBfc8pc1"4D`TYT"lgh(^W1e"r&"Z^%ElbR',pCs6n^=P.D`bG:as]25fTCUh!Zo9jWIT!0WGV<LVn_$F6-[j!'7p?G?KsDFE?G%[B'*uNj"r^@j>FW%]e;"N<2eAVS%WqU^duah5EG5NGLbgA00kAY$EO,#5Z*._&t8ks7mE%:'d#,b<^!O)V%!e$)[Sm_PpG]*CqCLaKE,Ws!C<;GW?W3FI"nLc)OE1\86s\[iq\Wj=(<-#]F'p](X&_)b6D(G<,Nj?;orAY`g!DmnnKnkm?=Za56XU0oGLMZiF$CI&R.e*D6CSZ;3j@-%8h:u+/0fA^A#8A[T%BG7V6F+6HJ'19j29D.@n_g`Wmb7(hLgcr&N'K(Y(O!/E6`Sr(QJR9b&R:Jf;.]$4tXZe:`o82`tCtpZ"P:]h=b^,3+cj,n@"9S4f@prB&mI#0VB[/,KB%ge"Z*M1p^Z?<JP?;Rl3CprR!1fEVeS3Nu7GL?7bh#0'8i1/"'&<[84N/6N!3=Qd5.)5PP64?'#[7nB'b.1VfD<P*;p>N%]&jT`i,F7JlljX`XR:joZW=691>ME+%WTFi*gp2?i(MN_aq1I#K'@J"QV=BU@;'hBfbo6G<T/H)sS1\YbD,bne@9frII;X+-3qLAMMOatI/RQhk6%hB0U5]$q37VZ`J'I0t77[RP$<0Zm9oSg^QYG)s@;HFG[^?/pKU"N9V.QLWdQsV13A^uP?PWtH[Or'BtSru4\q5'7EdMS#FNi*N\2Tf#h#:2hBg[WMN%.!g.2b-LJ:i&ha!"[*\V`g<t@PEN@G:ZC8>`#pVKH@]]Hg.(='"\f1ZE.,5@oZh.M3grgLU`P!$.[c#'oT>3+0(WD]ClT7q^29I7bt6A;A$>Xj%_m%2L?9tm74IF8W\26=5=-T@=jJq,!<MB^cs*+@+llNnqP?rYgBA>OUljUiW8&F.)HSq^U2c(RH@"+e%4N;Pf\8a<Cq.4(Ka?k8P=fcQ05!F"C6.`@@2GjHXJ\ekn&'A2L763A1.;(Q?!BmmXVBRlMUGQ/c&pBamp:O>$8T2;\ihYAmIs3,#^/]9sZO<I5h:'PR+7Ucg.%TVY2c`B$5bM::;LWh$!b:[WL>i\nbAm572''!CTNtG5~>endstream | |||||
| endobj | endobj | ||||
| 25 0 obj | 25 0 obj | ||||
| << | << | ||||
| /Filter [ /ASCII85Decode /FlateDecode ] /Length 1179 | |||||
| /Filter [ /ASCII85Decode /FlateDecode ] /Length 1299 | |||||
| >> | >> | ||||
| stream | stream | ||||
| Gat=*968iG&AII3llcVQ,JaB;9M_d"Vp6QI'<+.j!g/R5Rj;);P+qsP0#35g`o0TJILt5R\%CNMZ!ougLY;]!!V",:Y&>13#?DdO*q<Y2$#We-Ij_?F15_G%Xdo0N`;tVMZ[O_@X@;kHE7(G2krpV*,AYY1!=N$E16o7t^k4ZFX[EU?G$-<O_WhG.<sJ[=`-"W/730f>IhWHcR"!"":BCaE-U-+7;W?%Wnm!<]&#'&r?Kh2Ca3n(r!"F@63Ns&jR*Xo06K6QSp79jR_W$B_Z-t^eoN+32+(J-:D*sS"SQaU8T(B+T!*)cm$X1B.?4%0O+t;-Q%`B-8)8R\fhIa;Xj62^R`k74\(UDPbeEWsi!CU8GJC?hl_.Ef9I?>0Hf7;17bq"s9<Y2i:O>C^;,BS^nLZfA%C#C5j>EN!]<fkBP'.IT$D\eScGHfc^q-55R&1(:</+j<cH0C:S>UIcnaM!s,Ss<cVQ)XNg4FPp4<'Q:NQghAF'`n3rqUTG&R'(X9T$FU\9!MFIVDe[]PUP+n;!&39!5hD*XuqS>e6HLH"#.Vm1-d!-:%A/iRrUqITgp'l!rX%s82'CL<m/J<?./u]EaqaCE`XX`Z/DU3p!Lo5FhcViJ;54;J5'WhjS5bFS_hLMdp-O40h.0B>ss[sMQFA.R;L5TB7EBN([)ZlqLZ&NKMn3hRm;LZ!>,Q>5EdoB:=`P#%_Zg.l<S,H*\I%on>59Tm^oVfUm]6IOd5F^h;W%RN>KS2lc\R*8W%W&E>Lq@am4%t^g<:(RfP2$?64(?/S:Z9l='PYG%M-$Rq"%t(3l8$.a@2"!p<UMY8-sIs.1P_FE"M(5XOa+OTQlG[DN(@p0iRMqf&k%$G1nYEt1jDM.77KJWO%QWZ$HYZ?ATZo9m&XhO=-^H621lMUAAbR_sFCYk&B4!.:]jh]=kigI^3;R9d(7Z=.Zeim9SiS&O"<kYV2:Rs?$qrF!#*/q)u4]>i[8q4C)!lQbb\Gh9>DW`hU;]Wch@$=:PPjLAR`p48!uR0>1VCRk=I"s#J3a+2,beBZZ9/Qs7&L<Siq4PA^4lBb6in]&N%aMQ1EdgA#ML(bK1RcS+gT$dNbC,rKAQRToJ_VCKjeM>XC"RUCj:n"/Ofl0ZkIC(s%0a`JCZRq.e[D7XCnHObe^ON]/pCUR<Af)lp")X!LZ2~>endstream | |||||
| Gat=*>Beg[%"@rS^dB05**Xb4=814T8XD#Ge(EDR?>,6lPXqN<dWW<25ENIp;,_?)nW?7ki!80B%KQn>J(4J$$\2TN0Y=&E?!(upqMl*0)n9.5rZ3pYZ.V[<Y`V!JTh.eKs-=2X!,^'b+$;Y_^j)1\YRqN?,DWfX$BY\;+nT1MIk!)0j#0R+3@pe1H"$3'5s3a*rO>;-AIfcT,Ls!]jH78$%\3T06E0"../#fCBFr0,^AZ+`XH04*p&e8&#l6e2>bOBOMij2[\k1g,4pI=k`(SnRO;leAguSJULrdo2n@j+(f^NWnD468TPM7C_NB[R2d5lO%Jpolh\;5tmcE:DC/-[t`)]2pn3IaK_0s-]:-Nh[`WHkJn&&HY+lEr'&Nq<d"g&p63[H+uBf#+X(G6f3Hh?71fCf"A4[hZ,&"jNI*VIEn-+;aohc?DC+<3gpQ5"sn&+'+!8'U-O7fLSllErY1jmdp;p=D]u9/mCQ\pZ&T*3'/@&']#BC"6@E"WSA2uUL4<$hFgoJ#=jnX)gR(((9o[C3We=q^f#R@H,ZV3[AYj@,RHH"'11QlAt.u43^Z`KXOIoF@c&e>\QD'`+a+jYbXt(Db*7AXQ9K/N[VU+t<MFNLIa,%ng5#35S)rlKA#pA][Mnn[^m*it.d+r"TE$erCapKNaX,F"A.Qn'[I0^s,p`gl9Zi&fEBT\F([/=>#BFg2\Qm@^YNn"n&/uRo&mModc[qu5<X1.]eGWjC4W_<^!B-@>Ol8IA[e`kB9J`7,p^fGlHkJ<f.qcgXeni$;1]&Ds>!kMGe/,2D`KeS6=W7EIoSD--q<Xns^TrU2G0,Ru+h+9SHSEs5_&&P3h.9jo:G[uY3%NsklhTXM)Gd[5q9/>Li=tNqi_G+)%I_m4*ck<gD$mq/>Hu6%@6O^7;"s#Io8*p`]8+XrD5&k`.gi@^;8*e7n+]#)SB/Z(+TWr75odMp`7U8f#^7@\3">897Y\eRkB`]:U72OJ:8=);>)%C3`1F<(I0B.9>'[#A3Z70bHEB\/grSmJPn-I7W8jV;74i?JJfD4*BQi(O]iOD=72rqe&9YS(2JISQI/DVN.l7r![G7*qr_QaBjJ=Jtq8[7XitF)!4MY"fGATo:-,kLIKronrc72q9CtWrDK);gP'@";VVCb/iG9"t?K"KL^61a5tBi1b/$FYE%(=b:<[D:N2D^l,RJP7T\C1rWC`2XcGh=]%\5?s([A]@ciG21lLXP'G[CZc$\'napCc^F3&453n"qg_]eB]]nJPZZkOaROG/]D%r!n3`.<^&!K2^Fng+Sc~>endstream | |||||
| endobj | endobj | ||||
| xref | xref | ||||
| 0 26 | 0 26 | ||||
| @@ -205,16 +205,16 @@ xref | |||||
| 0000005637 00000 n | 0000005637 00000 n | ||||
| 0000005945 00000 n | 0000005945 00000 n | ||||
| 0000006047 00000 n | 0000006047 00000 n | ||||
| 0000007253 00000 n | |||||
| 0000010334 00000 n | |||||
| 0000012885 00000 n | |||||
| 0000015053 00000 n | |||||
| 0000017777 00000 n | |||||
| 0000020160 00000 n | |||||
| 0000007268 00000 n | |||||
| 0000010349 00000 n | |||||
| 0000012900 00000 n | |||||
| 0000015068 00000 n | |||||
| 0000018004 00000 n | |||||
| 0000020260 00000 n | |||||
| trailer | trailer | ||||
| << | << | ||||
| /ID | /ID | ||||
| [<93ff8ecba1600bc59dfd0d3f9189f269><93ff8ecba1600bc59dfd0d3f9189f269>] | |||||
| [<2db4e987e0adcf5648dea723e2828700><2db4e987e0adcf5648dea723e2828700>] | |||||
| % ReportLab generated PDF document -- digest (opensource) | % ReportLab generated PDF document -- digest (opensource) | ||||
| /Info 17 0 R | /Info 17 0 R | ||||
| @@ -222,5 +222,5 @@ trailer | |||||
| /Size 26 | /Size 26 | ||||
| >> | >> | ||||
| startxref | startxref | ||||
| 21431 | |||||
| 21651 | |||||
| %%EOF | %%EOF | ||||
| @@ -236,6 +236,11 @@ doscmd() | |||||
| sclr(24, 0, 40); | sclr(24, 0, 40); | ||||
| if (n == 0) return 0; | if (n == 0) return 0; | ||||
| if (cmd[0] == 68 && cmd[1] == 73 && cmd[2] == 82 && cmd[3] == 0) { /* dir */ | |||||
| if (dirlist() == 0) note = "cannot read directory"; | |||||
| return 0; | |||||
| } | |||||
| if (cmd[0] == 81) { /* q, q! */ | if (cmd[0] == 81) { /* q, q! */ | ||||
| if (cmd[1] == 33) return 1; | if (cmd[1] == 33) return 1; | ||||
| if (dirty) { | if (dirty) { | ||||
| @@ -415,8 +420,9 @@ main() | |||||
| mode = 1; | mode = 1; | ||||
| } else if (k == 58) { /* : */ | } else if (k == 58) { /* : */ | ||||
| if (doscmd() == 1) { | if (doscmd() == 1) { | ||||
| sdone(); | |||||
| return; | |||||
| sdone(); /* resets the machine: never returns. | |||||
| (a return here made power c emit a | |||||
| jump to garbage - see NOTES.md) */ | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,907 @@ | |||||
| Modules list: | |||||
| ------------- | |||||
| vim128.c.89112.0.o: | |||||
| CODE Offs=000000 Size=001F14 Align=00001 Fill=0000 | |||||
| RODATA Offs=000000 Size=0000CF Align=00001 Fill=0000 | |||||
| BSS Offs=000000 Size=004FFC Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(_cursor.o): | |||||
| BSS Offs=004FFC Size=000001 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(_oserror.o): | |||||
| BSS Offs=004FFD Size=000001 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(_scrsize.o): | |||||
| CODE Offs=001F14 Size=00000B Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(add.o): | |||||
| CODE Offs=001F1F Size=00001C Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(addeqsp.o): | |||||
| CODE Offs=001F3B Size=000011 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(addysp.o): | |||||
| CODE Offs=001F4C Size=00000E Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(aslax1.o): | |||||
| CODE Offs=001F5A Size=000008 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(c_bsout.o): | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(c_close.o): | |||||
| CODE Offs=001F62 Size=000004 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(c_open.o): | |||||
| CODE Offs=001F66 Size=000009 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(c_setlfs.o): | |||||
| CODE Offs=001F6F Size=00000E Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(c_setnam.o): | |||||
| CODE Offs=001F7D Size=000013 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(callmain.o): | |||||
| CODE Offs=001F90 Size=000017 Align=00001 Fill=0000 | |||||
| DATA Offs=000000 Size=000004 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(cbm_close.o): | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(cbm_open.o): | |||||
| CODE Offs=001FA7 Size=000010 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(cbm_read.o): | |||||
| CODE Offs=001FB7 Size=00005A Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(cbm_write.o): | |||||
| CODE Offs=002011 Size=000049 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(cgetc.o): | |||||
| CODE Offs=00205A Size=000030 Align=00001 Fill=0000 | |||||
| INIT Offs=000000 Size=000002 Align=00001 Fill=0000 | |||||
| ONCE Offs=000000 Size=000013 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(clrscr.o): | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(color.o): | |||||
| CODE Offs=00208A Size=000060 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(condes.o): | |||||
| CODE Offs=0020EA Size=00000C Align=00001 Fill=0000 | |||||
| DATA Offs=000004 Size=000025 Align=00001 Fill=0000 | |||||
| ONCE Offs=000013 Size=00000C Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(conio.o): | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(cputc.o): | |||||
| CODE Offs=0020F6 Size=000049 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(crt0.o): | |||||
| BSS Offs=004FFE Size=000003 Align=00001 Fill=0000 | |||||
| STARTUP Offs=000000 Size=000062 Align=00001 Fill=0000 | |||||
| INIT Offs=000002 Size=00001A Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(cursor.o): | |||||
| CODE Offs=00213F Size=00000A Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(decax1.o): | |||||
| CODE Offs=002149 Size=000007 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(decaxy.o): | |||||
| CODE Offs=002150 Size=000009 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(decsp1.o): | |||||
| CODE Offs=002159 Size=000009 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(decsp2.o): | |||||
| CODE Offs=002162 Size=00000D Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(decsp4.o): | |||||
| CODE Offs=00216F Size=00000D Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(decsp6.o): | |||||
| CODE Offs=00217C Size=00000D Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(decsp8.o): | |||||
| CODE Offs=002189 Size=00000D Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(div.o): | |||||
| CODE Offs=002196 Size=000015 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(exehdr.o): | |||||
| EXEHDR Offs=000000 Size=00000C Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(fast.o): | |||||
| CODE Offs=0021AB Size=000006 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(gotoxy.o): | |||||
| CODE Offs=0021B1 Size=00000D Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(icmp.o): | |||||
| CODE Offs=0021BE Size=00002E Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(incax1.o): | |||||
| CODE Offs=0021EC Size=000007 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(incax2.o): | |||||
| CODE Offs=0021F3 Size=000007 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(incax8.o): | |||||
| CODE Offs=0021FA Size=000005 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(incaxy.o): | |||||
| CODE Offs=0021FF Size=00000B Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(incsp2.o): | |||||
| CODE Offs=00220A Size=000016 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(incsp4.o): | |||||
| CODE Offs=002220 Size=000005 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(incsp5.o): | |||||
| CODE Offs=002225 Size=000005 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(incsp6.o): | |||||
| CODE Offs=00222A Size=000005 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(incsp8.o): | |||||
| CODE Offs=00222F Size=000005 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(kernal.o): | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(ldaxi.o): | |||||
| CODE Offs=002234 Size=00000D Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(ldaxsp.o): | |||||
| CODE Offs=002241 Size=000009 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(ldptr1sp.o): | |||||
| CODE Offs=00224A Size=00000C Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(leaaxsp.o): | |||||
| CODE Offs=002256 Size=00000C Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(loadaddr.o): | |||||
| LOADADDR Offs=000000 Size=000002 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(mod.o): | |||||
| CODE Offs=002262 Size=000011 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(mulax10.o): | |||||
| CODE Offs=002273 Size=000019 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(negabs.o): | |||||
| CODE Offs=00228C Size=000012 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(popa.o): | |||||
| CODE Offs=00229E Size=00000C Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(popptr1.o): | |||||
| CODE Offs=0022AA Size=00000E Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(pusha.o): | |||||
| CODE Offs=0022B8 Size=000016 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(pushax.o): | |||||
| CODE Offs=0022CE Size=00001A Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(pushwsp.o): | |||||
| CODE Offs=0022E8 Size=00001C Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(return0.o): | |||||
| CODE Offs=002304 Size=000004 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(returnFFFF.o): | |||||
| CODE Offs=002308 Size=000004 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(revers.o): | |||||
| CODE Offs=00230C Size=000014 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(scrsize.o): | |||||
| CODE Offs=002320 Size=000013 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(shelp.o): | |||||
| CODE Offs=002333 Size=000020 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(slow.o): | |||||
| CODE Offs=002353 Size=000006 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(staspidx.o): | |||||
| CODE Offs=002359 Size=000016 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(staxsp.o): | |||||
| CODE Offs=00236F Size=00000B Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(strcmp.o): | |||||
| CODE Offs=00237A Size=000021 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(strcpy.o): | |||||
| CODE Offs=00239B Size=00001F Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(sub.o): | |||||
| CODE Offs=0023BA Size=000015 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(subeqsp.o): | |||||
| CODE Offs=0023CF Size=000015 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(subysp.o): | |||||
| CODE Offs=0023E4 Size=00000D Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(udiv.o): | |||||
| CODE Offs=0023F1 Size=00004F Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(zerobss.o): | |||||
| CODE Offs=002440 Size=000023 Align=00001 Fill=0000 | |||||
| D:/Development/Commodore/cc65-snapshot-win32/lib/c128.lib(zeropage.o): | |||||
| ZEROPAGE Offs=000000 Size=00001A Align=00001 Fill=0000 | |||||
| Segment list: | |||||
| ------------- | |||||
| Name Start End Size Align | |||||
| ---------------------------------------------------- | |||||
| ZEROPAGE 000002 00001B 00001A 00001 | |||||
| LOADADDR 001BFF 001C00 000002 00001 | |||||
| EXEHDR 001C01 001C0C 00000C 00001 | |||||
| STARTUP 001C0D 001C6E 000062 00001 | |||||
| ONCE 001C6F 001C8F 000021 00001 | |||||
| CODE 001C90 0040F2 002463 00001 | |||||
| RODATA 0040F3 0041C3 0000D1 00001 | |||||
| DATA 0041C4 0041EC 000029 00001 | |||||
| INIT 0041ED 004208 00001C 00001 | |||||
| BSS 004209 009209 005001 00001 | |||||
| Exports list by name: | |||||
| --------------------- | |||||
| BASIN 00FFCF RLA BSOUT 00FFD2 RLA | |||||
| CHKIN 00FFC6 RLA CHROUT 00FFD2 RLA | |||||
| CKOUT 00FFC9 RLA CLOSE 00FFC3 RLA | |||||
| CLRCH 00FFCC RLA CURS_X 0000EC RLZ | |||||
| CURS_Y 0000EB RLZ OPEN 00FFC0 RLA | |||||
| READST 00FFB7 RLA SETLFS 00FFBA RLA | |||||
| SETNAM 00FFBD RLA __BSS_RUN__ 004209 RLA | |||||
| __BSS_SIZE__ 005001 REA __CONSTRUCTOR_COUNT__ 000001 REA | |||||
| __CONSTRUCTOR_TABLE__ 001C8E RLA __DESTRUCTOR_COUNT__ 000001 REA | |||||
| __DESTRUCTOR_TABLE__ 0041C2 RLA __EXEHDR__ 000001 REA | |||||
| __LOADADDR__ 000001 REA __MAIN_SIZE__ 009BF3 REA | |||||
| __MAIN_START__ 001C0D RLA __STACKSIZE__ 000800 REA | |||||
| __STARTUP__ 000001 REA ___oserror 009206 RLA | |||||
| _bgcolor 003D43 RLA _bordercolor 003D69 RLA | |||||
| _cbm_close 003BF2 REA _cbm_k_bsout 00FFD2 RLA | |||||
| _cbm_k_close 003BF2 RLA _cbm_k_open 003BF6 RLA | |||||
| _cbm_k_setlfs 003BFF RLA _cbm_k_setnam 003C0D RLA | |||||
| _cbm_open 003C37 RLA _cbm_read 003C47 RLA | |||||
| _cbm_write 003CA1 RLA _cgetc 003CEA RLA | |||||
| _clrscr 00C142 REA _cputc 003D8B RLA | |||||
| _cputcxy 003D86 RLA _cursor 003DCF RLA | |||||
| _fast 003E3B RLA _gotoxy 003E44 RLA | |||||
| _main 003722 RLA _revers 003F9C RLA | |||||
| _screensize 003FB0 RLA _slow 003FE3 RLA | |||||
| _strcmp 00400A RLA _strcpy 00402B RLA | |||||
| _textcolor 003D1A RLA addeq0sp 003BCB RLA | |||||
| addeqysp 003BCD RLA addysp 003BDD RLA | |||||
| addysp1 003BDC RLA aslax1 003BEA RLA | |||||
| c_sp 000002 RLZ callmain 003C20 RLA | |||||
| cursor 009205 RLA decax1 003DD9 RLA | |||||
| decaxy 003DE0 RLA decsp1 003DE9 RLA | |||||
| decsp2 003DF2 RLA decsp4 003DFF RLA | |||||
| decsp6 003E0C RLA decsp8 003E19 RLA | |||||
| donecgetc 003D0B LAI donelib 003D7A RLA | |||||
| gotoxy 003E41 RLA incax1 003E7C RLA | |||||
| incax2 003E83 RLA incax4 003E8F RLA | |||||
| incax8 003E8A RLA incaxy 003E91 RLA | |||||
| incsp2 003EA2 RLA incsp4 003EB0 RLA | |||||
| incsp5 003EB5 RLA incsp6 003EBA RLA | |||||
| incsp8 003EBF RLA initcgetc 001C6F LAI | |||||
| initlib 001C82 RLA ldax0sp 003ED1 RLA | |||||
| ldaxi 003EC4 RLA ldaxysp 003ED3 RLA | |||||
| ldptr10sp 003EDA RLA ldptr1ysp 003EDC RLA | |||||
| leaa0sp 003EE6 RLA leaaxsp 003EE8 RLA | |||||
| mulax10 003F03 RLA negax 003F20 RLA | |||||
| plot 003DC7 RLA popa 003F2E RLA | |||||
| popax 003E9A RLA popptr1 003F3A RLA | |||||
| popsargsudiv16 003FC3 RLA ptr1 00000A RLZ | |||||
| ptr2 00000C RLZ ptr3 00000E RLZ | |||||
| ptr4 000010 RLZ push0 003F5E RLA | |||||
| pusha 003F4C RLA pusha0 003F60 RLA | |||||
| pushax 003F62 RLA pushw0sp 003F78 RLA | |||||
| pushwysp 003F7A RLA return0 003F94 RLA | |||||
| returnFFFF 003F98 RLA screensize 003BA4 RLA | |||||
| sreg 000004 RLZ staspidx 003FE9 RLA | |||||
| stax0sp 003FFF RLA staxysp 004001 RLA | |||||
| subeq0sp 00405F RLA subeqysp 004061 RLA | |||||
| subysp 004074 RLA tmp1 000012 RLZ | |||||
| tmp2 000013 RLZ tosaddax 003BB1 RLA | |||||
| tosdiva0 003E26 RLA tosicmp 003E50 RLA | |||||
| tosicmp0 003E4E RLA tosmoda0 003EF2 RLA | |||||
| tossubax 00404C RLA udiv16 004092 RLA | |||||
| zerobss 0040D0 RLA | |||||
| Exports list by value: | |||||
| ---------------------- | |||||
| __EXEHDR__ 000001 REA __LOADADDR__ 000001 REA | |||||
| __CONSTRUCTOR_COUNT__ 000001 REA __STARTUP__ 000001 REA | |||||
| __DESTRUCTOR_COUNT__ 000001 REA c_sp 000002 RLZ | |||||
| sreg 000004 RLZ ptr1 00000A RLZ | |||||
| ptr2 00000C RLZ ptr3 00000E RLZ | |||||
| ptr4 000010 RLZ tmp1 000012 RLZ | |||||
| tmp2 000013 RLZ CURS_Y 0000EB RLZ | |||||
| CURS_X 0000EC RLZ __STACKSIZE__ 000800 REA | |||||
| __MAIN_START__ 001C0D RLA initcgetc 001C6F LAI | |||||
| initlib 001C82 RLA __CONSTRUCTOR_TABLE__ 001C8E RLA | |||||
| _main 003722 RLA screensize 003BA4 RLA | |||||
| tosaddax 003BB1 RLA addeq0sp 003BCB RLA | |||||
| addeqysp 003BCD RLA addysp1 003BDC RLA | |||||
| addysp 003BDD RLA aslax1 003BEA RLA | |||||
| _cbm_close 003BF2 REA _cbm_k_close 003BF2 RLA | |||||
| _cbm_k_open 003BF6 RLA _cbm_k_setlfs 003BFF RLA | |||||
| _cbm_k_setnam 003C0D RLA callmain 003C20 RLA | |||||
| _cbm_open 003C37 RLA _cbm_read 003C47 RLA | |||||
| _cbm_write 003CA1 RLA _cgetc 003CEA RLA | |||||
| donecgetc 003D0B LAI _textcolor 003D1A RLA | |||||
| _bgcolor 003D43 RLA _bordercolor 003D69 RLA | |||||
| donelib 003D7A RLA _cputcxy 003D86 RLA | |||||
| _cputc 003D8B RLA plot 003DC7 RLA | |||||
| _cursor 003DCF RLA decax1 003DD9 RLA | |||||
| decaxy 003DE0 RLA decsp1 003DE9 RLA | |||||
| decsp2 003DF2 RLA decsp4 003DFF RLA | |||||
| decsp6 003E0C RLA decsp8 003E19 RLA | |||||
| tosdiva0 003E26 RLA _fast 003E3B RLA | |||||
| gotoxy 003E41 RLA _gotoxy 003E44 RLA | |||||
| tosicmp0 003E4E RLA tosicmp 003E50 RLA | |||||
| incax1 003E7C RLA incax2 003E83 RLA | |||||
| incax8 003E8A RLA incax4 003E8F RLA | |||||
| incaxy 003E91 RLA popax 003E9A RLA | |||||
| incsp2 003EA2 RLA incsp4 003EB0 RLA | |||||
| incsp5 003EB5 RLA incsp6 003EBA RLA | |||||
| incsp8 003EBF RLA ldaxi 003EC4 RLA | |||||
| ldax0sp 003ED1 RLA ldaxysp 003ED3 RLA | |||||
| ldptr10sp 003EDA RLA ldptr1ysp 003EDC RLA | |||||
| leaa0sp 003EE6 RLA leaaxsp 003EE8 RLA | |||||
| tosmoda0 003EF2 RLA mulax10 003F03 RLA | |||||
| negax 003F20 RLA popa 003F2E RLA | |||||
| popptr1 003F3A RLA pusha 003F4C RLA | |||||
| push0 003F5E RLA pusha0 003F60 RLA | |||||
| pushax 003F62 RLA pushw0sp 003F78 RLA | |||||
| pushwysp 003F7A RLA return0 003F94 RLA | |||||
| returnFFFF 003F98 RLA _revers 003F9C RLA | |||||
| _screensize 003FB0 RLA popsargsudiv16 003FC3 RLA | |||||
| _slow 003FE3 RLA staspidx 003FE9 RLA | |||||
| stax0sp 003FFF RLA staxysp 004001 RLA | |||||
| _strcmp 00400A RLA _strcpy 00402B RLA | |||||
| tossubax 00404C RLA subeq0sp 00405F RLA | |||||
| subeqysp 004061 RLA subysp 004074 RLA | |||||
| udiv16 004092 RLA zerobss 0040D0 RLA | |||||
| __DESTRUCTOR_TABLE__ 0041C2 RLA __BSS_RUN__ 004209 RLA | |||||
| __BSS_SIZE__ 005001 REA cursor 009205 RLA | |||||
| ___oserror 009206 RLA __MAIN_SIZE__ 009BF3 REA | |||||
| _clrscr 00C142 REA READST 00FFB7 RLA | |||||
| SETLFS 00FFBA RLA SETNAM 00FFBD RLA | |||||
| OPEN 00FFC0 RLA CLOSE 00FFC3 RLA | |||||
| CHKIN 00FFC6 RLA CKOUT 00FFC9 RLA | |||||
| CLRCH 00FFCC RLA BASIN 00FFCF RLA | |||||
| _cbm_k_bsout 00FFD2 RLA CHROUT 00FFD2 RLA | |||||
| BSOUT 00FFD2 RLA | |||||
| Imports list: | |||||
| ------------- | |||||
| BASIN (kernal.o): | |||||
| cbm_read.o cbm/cbm.inc:47 | |||||
| BSOUT (kernal.o): | |||||
| crt0.o c128/crt0.s:10 | |||||
| cbm_write.o cbm/cbm.inc:49 | |||||
| c_bsout.o cbm/cbm.inc:49 | |||||
| CHKIN (kernal.o): | |||||
| cbm_read.o cbm/cbm.inc:44 | |||||
| CHROUT (kernal.o): | |||||
| c_bsout.o cbm/cbm.inc:50 | |||||
| CKOUT (kernal.o): | |||||
| cbm_write.o cbm/cbm.inc:45 | |||||
| CLOSE (kernal.o): | |||||
| c_close.o cbm/cbm.inc:36 | |||||
| CLRCH (kernal.o): | |||||
| cbm_write.o cbm/cbm.inc:46 | |||||
| cbm_read.o cbm/cbm.inc:46 | |||||
| CURS_X (conio.o): | |||||
| gotoxy.o cbm/gotoxy.s:9 | |||||
| CURS_Y (conio.o): | |||||
| gotoxy.o cbm/gotoxy.s:9 | |||||
| OPEN (kernal.o): | |||||
| c_open.o cbm/cbm.inc:35 | |||||
| READST (kernal.o): | |||||
| cbm_write.o cbm/cbm.inc:32 | |||||
| cbm_read.o cbm/cbm.inc:32 | |||||
| SETLFS (kernal.o): | |||||
| c_setlfs.o cbm/cbm.inc:33 | |||||
| SETNAM (kernal.o): | |||||
| c_setnam.o cbm/cbm.inc:34 | |||||
| __BSS_RUN__ ([linker generated]): | |||||
| zerobss.o common/zerobss.s:8 | |||||
| __BSS_SIZE__ ([linker generated]): | |||||
| zerobss.o common/zerobss.s:8 | |||||
| __CONSTRUCTOR_COUNT__ ([linker generated]): | |||||
| condes.o runtime/condes.s:18 | |||||
| __CONSTRUCTOR_TABLE__ ([linker generated]): | |||||
| condes.o runtime/condes.s:18 | |||||
| __DESTRUCTOR_COUNT__ ([linker generated]): | |||||
| condes.o runtime/condes.s:19 | |||||
| __DESTRUCTOR_TABLE__ ([linker generated]): | |||||
| condes.o runtime/condes.s:19 | |||||
| __EXEHDR__ (exehdr.o): | |||||
| [linker generated] D:/Development/Commodore/cc65-snapshot-win32/cfg/c128.cfg:3 | |||||
| __LOADADDR__ (loadaddr.o): | |||||
| [linker generated] D:/Development/Commodore/cc65-snapshot-win32/cfg/c128.cfg:2 | |||||
| __MAIN_SIZE__ ([linker generated]): | |||||
| crt0.o c128/crt0.s:11 | |||||
| __MAIN_START__ ([linker generated]): | |||||
| crt0.o c128/crt0.s:11 | |||||
| __STACKSIZE__ ([linker generated]): | |||||
| crt0.o c128/crt0.s:11 | |||||
| [linker generated] D:/Development/Commodore/cc65-snapshot-win32/cfg/c128.cfg:10 | |||||
| __STARTUP__ (crt0.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:13 | |||||
| ___oserror (_oserror.o): | |||||
| cbm_write.o cbm/cbm_write.s:36 | |||||
| cbm_read.o cbm/cbm_read.s:44 | |||||
| cbm_open.o cbm/cbm_open.s:26 | |||||
| _bgcolor (color.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:29 | |||||
| _bordercolor (color.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:30 | |||||
| _cbm_close (cbm_close.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:18 | |||||
| _cbm_k_bsout (c_bsout.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:16 | |||||
| _cbm_k_close (c_close.o): | |||||
| cbm_close.o cbm/cbm_close.s:7 | |||||
| _cbm_k_open (c_open.o): | |||||
| cbm_open.o cbm/cbm_open.s:25 | |||||
| _cbm_k_setlfs (c_setlfs.o): | |||||
| cbm_open.o cbm/cbm_open.s:25 | |||||
| _cbm_k_setnam (c_setnam.o): | |||||
| cbm_open.o cbm/cbm_open.s:25 | |||||
| _cbm_open (cbm_open.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:17 | |||||
| _cbm_read (cbm_read.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:19 | |||||
| _cbm_write (cbm_write.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:20 | |||||
| _cgetc (cgetc.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:25 | |||||
| _clrscr (clrscr.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:21 | |||||
| _cputc (cputc.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:23 | |||||
| _cputcxy (cputc.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:24 | |||||
| _cursor (cursor.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:26 | |||||
| _fast (fast.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:14 | |||||
| _gotoxy (gotoxy.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:22 | |||||
| _main (vim128.c.89112.0.o): | |||||
| callmain.o runtime/callmain.s:11 | |||||
| _revers (revers.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:27 | |||||
| _screensize (scrsize.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:31 | |||||
| _slow (slow.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:15 | |||||
| _strcmp (strcmp.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:32 | |||||
| _strcpy (strcpy.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:33 | |||||
| _textcolor (color.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:28 | |||||
| addeq0sp (addeqsp.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| addeqysp (addeqsp.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| addysp (addysp.o): | |||||
| incsp8.o runtime/incsp8.s:8 | |||||
| incsp6.o runtime/incsp6.s:8 | |||||
| incsp5.o runtime/incsp5.s:8 | |||||
| incsp4.o runtime/incsp4.s:8 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| addysp1 (addysp.o): | |||||
| sub.o runtime/sub.s:8 | |||||
| aslax1 (aslax1.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| c_sp (zeropage.o): | |||||
| popptr1.o runtime/popptr1.s:9 | |||||
| subysp.o runtime/subysp.s:9 | |||||
| subeqsp.o runtime/subeqsp.s:8 | |||||
| sub.o runtime/sub.s:9 | |||||
| staxsp.o runtime/staxsp.s:8 | |||||
| staspidx.o runtime/staspidx.s:9 | |||||
| pushwsp.o runtime/pushwsp.s:8 | |||||
| pushax.o runtime/pushax.s:8 | |||||
| pusha.o runtime/pusha.s:8 | |||||
| popa.o runtime/popa.s:8 | |||||
| leaaxsp.o runtime/leaaxsp.s:8 | |||||
| ldptr1sp.o runtime/ldptr1sp.s:8 | |||||
| ldaxsp.o runtime/ldaxsp.s:8 | |||||
| incsp2.o runtime/incsp2.s:8 | |||||
| icmp.o runtime/icmp.s:9 | |||||
| decsp8.o runtime/decsp8.s:8 | |||||
| decsp6.o runtime/decsp6.s:8 | |||||
| decsp4.o runtime/decsp4.s:8 | |||||
| decsp2.o runtime/decsp2.s:8 | |||||
| decsp1.o runtime/decsp1.s:8 | |||||
| crt0.o /home/runner/work/cc65/cc65/asminc/zeropage.inc:11 | |||||
| addysp.o runtime/addysp.s:8 | |||||
| addeqsp.o runtime/addeqsp.s:8 | |||||
| add.o runtime/add.s:12 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:10 | |||||
| callmain (callmain.o): | |||||
| crt0.o c128/crt0.s:9 | |||||
| cursor (_cursor.o): | |||||
| cursor.o conio/cursor.s:8 | |||||
| cgetc.o c128/cgetc.s:11 | |||||
| decax1 (decax1.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| decaxy (decaxy.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| decsp1 (decsp1.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| decsp2 (decsp2.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| decsp4 (decsp4.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| decsp6 (decsp6.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| decsp8 (decsp8.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| donelib (condes.o): | |||||
| crt0.o c128/crt0.s:7 | |||||
| gotoxy (gotoxy.o): | |||||
| cputc.o c128/cputc.s:13 | |||||
| incax1 (incax1.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| incax2 (incax2.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| incax4 (incaxy.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| incax8 (incax8.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| incaxy (incaxy.o): | |||||
| incax8.o runtime/incax8.s:8 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| incsp2 (incsp2.o): | |||||
| popptr1.o runtime/popptr1.s:8 | |||||
| staspidx.o runtime/staspidx.s:8 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| incsp4 (incsp4.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| incsp5 (incsp5.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| incsp6 (incsp6.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| incsp8 (incsp8.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| initlib (condes.o): | |||||
| crt0.o c128/crt0.s:7 | |||||
| ldax0sp (ldaxsp.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| ldaxi (ldaxi.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| ldaxysp (ldaxsp.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| ldptr10sp (ldptr1sp.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| ldptr1ysp (ldptr1sp.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| leaa0sp (leaaxsp.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| leaaxsp (leaaxsp.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| mulax10 (mulax10.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| negax (negabs.o): | |||||
| shelp.o runtime/shelp.s:11 | |||||
| mod.o runtime/mod.s:11 | |||||
| div.o runtime/div.s:11 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| plot (cputc.o): | |||||
| gotoxy.o cbm/gotoxy.s:8 | |||||
| popa (popa.o): | |||||
| c_setlfs.o cbm/c_setlfs.s:12 | |||||
| gotoxy.o cbm/gotoxy.s:8 | |||||
| cbm_write.o cbm/cbm_write.s:35 | |||||
| cbm_read.o cbm/cbm_read.s:43 | |||||
| cbm_open.o cbm/cbm_open.s:24 | |||||
| popax (incsp2.o): | |||||
| strcpy.o common/strcpy.s:8 | |||||
| shelp.o runtime/shelp.s:11 | |||||
| cbm_write.o cbm/cbm_write.s:35 | |||||
| cbm_read.o cbm/cbm_read.s:43 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| popptr1 (popptr1.o): | |||||
| udiv.o runtime/udiv.s:9 | |||||
| strcmp.o common/strcmp.s:8 | |||||
| scrsize.o conio/scrsize.s:9 | |||||
| popsargsudiv16 (shelp.o): | |||||
| mod.o runtime/mod.s:11 | |||||
| div.o runtime/div.s:11 | |||||
| ptr1 (zeropage.o): | |||||
| popptr1.o runtime/popptr1.s:9 | |||||
| c_setnam.o cbm/c_setnam.s:10 | |||||
| zerobss.o common/zerobss.s:9 | |||||
| udiv.o runtime/udiv.s:10 | |||||
| strcpy.o common/strcpy.s:9 | |||||
| strcmp.o common/strcmp.s:9 | |||||
| staspidx.o runtime/staspidx.s:9 | |||||
| shelp.o runtime/shelp.s:12 | |||||
| scrsize.o conio/scrsize.s:11 | |||||
| mulax10.o runtime/mulax10.s:9 | |||||
| ldptr1sp.o runtime/ldptr1sp.s:8 | |||||
| ldaxi.o runtime/ldaxi.s:8 | |||||
| div.o runtime/div.s:12 | |||||
| cbm_write.o cbm/cbm_write.s:34 | |||||
| cbm_read.o cbm/cbm_read.s:42 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:11 | |||||
| ptr2 (zeropage.o): | |||||
| strcpy.o common/strcpy.s:9 | |||||
| strcmp.o common/strcmp.s:9 | |||||
| scrsize.o conio/scrsize.s:11 | |||||
| cbm_write.o cbm/cbm_write.s:34 | |||||
| cbm_read.o cbm/cbm_read.s:42 | |||||
| ptr3 (zeropage.o): | |||||
| cbm_write.o cbm/cbm_write.s:34 | |||||
| cbm_read.o cbm/cbm_read.s:42 | |||||
| ptr4 (zeropage.o): | |||||
| udiv.o runtime/udiv.s:10 | |||||
| shelp.o runtime/shelp.s:12 | |||||
| push0 (pushax.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| pusha (pusha.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| pusha0 (pushax.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| pushax (pushax.o): | |||||
| callmain.o runtime/callmain.s:11 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| pushw0sp (pushwsp.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| pushwysp (pushwsp.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| return0 (return0.o): | |||||
| color.o c128/color.s:10 | |||||
| returnFFFF (returnFFFF.o): | |||||
| cbm_write.o cbm/cbm_write.s:35 | |||||
| cbm_read.o cbm/cbm_read.s:43 | |||||
| screensize (_scrsize.o): | |||||
| scrsize.o conio/scrsize.s:10 | |||||
| sreg (zeropage.o): | |||||
| udiv.o runtime/udiv.s:10 | |||||
| mod.o runtime/mod.s:12 | |||||
| icmp.o runtime/icmp.s:9 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:10 | |||||
| staspidx (staspidx.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| stax0sp (staxsp.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| staxysp (staxsp.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| subeq0sp (subeqsp.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| subeqysp (subeqsp.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| subysp (subysp.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| tmp1 (zeropage.o): | |||||
| c_setlfs.o cbm/c_setlfs.s:13 | |||||
| staspidx.o runtime/staspidx.s:9 | |||||
| shelp.o runtime/shelp.s:12 | |||||
| mod.o runtime/mod.s:12 | |||||
| incaxy.o runtime/incaxy.s:8 | |||||
| div.o runtime/div.s:12 | |||||
| decaxy.o runtime/decaxy.s:8 | |||||
| cbm_read.o cbm/cbm_read.s:42 | |||||
| aslax1.o runtime/aslax1.s:8 | |||||
| add.o runtime/add.s:12 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:11 | |||||
| tmp2 (zeropage.o): | |||||
| shelp.o runtime/shelp.s:12 | |||||
| div.o runtime/div.s:12 | |||||
| tosaddax (add.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| tosdiva0 (div.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| tosicmp (icmp.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| tosicmp0 (icmp.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| tosmoda0 (mod.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| tossubax (sub.o): | |||||
| vim128.c.89112.0.o vim128.c.89112.0.s:4190 | |||||
| udiv16 (udiv.o): | |||||
| shelp.o runtime/shelp.s:11 | |||||
| zerobss (zerobss.o): | |||||
| crt0.o c128/crt0.s:8 | |||||
Powered by TurnKey Linux.