Kaynağa Gözat

Emit the calling convention from source.

Calls now stage their arguments and push them in reverse so arg0 lands at
FP+2, each body gets a patched frame prologue when frames are requested, and
return leaves the frame with a byte or word result preserved. A new args
fixture proves the parameter and result path in x128.

Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
main
Daniel Covington 3 gün önce
ebeveyn
işleme
5489ae6dcf
13 değiştirilmiş dosya ile 855 ekleme ve 37 silme
  1. +38
    -4
      newLang/docs/architecture.md
  2. +1
    -0
      newLang/docs/decisions.md
  3. +123
    -0
      newLang/planning/claims/E04-S08.md
  4. +21
    -0
      newLang/planning/epics-and-stories.md
  5. +37
    -0
      newLang/scripts/build-args-test.ps1
  6. +37
    -0
      newLang/scripts/verify-args-test.ps1
  7. +3
    -0
      newLang/src/common/emit.inc
  8. +309
    -28
      newLang/src/compiler/emit.s
  9. +4
    -1
      newLang/src/compiler/flow.s
  10. +89
    -4
      newLang/src/compiler/parser.s
  11. +179
    -0
      newLang/tests/compiler/args-test.s
  12. +13
    -0
      newLang/tests/compiler/args.n8
  13. +1
    -0
      newLang/tests/regression/suites.ps1

+ 38
- 4
newLang/docs/architecture.md Dosyayı Görüntüle

@@ -710,10 +710,11 @@ past the end value) and `b = 3`, with no recorded print output. The
method that increments it again, and explicitly returns; the 53 emitted bytes
leave `n = 2`.

Word values, call arguments, arrays, method result forwarding, the frame
prologue, and sizing/pass-3 addresses remain unimplemented. `for` currently
requires a constant byte end and step 1 and leaves the loop variable one past
the end value. E04-S07 owns those limits and the 255-byte code buffer.
Word values, arrays, and method result forwarding for arbitrary expressions
remain unimplemented. `for` currently requires a constant byte end and step 1
and leaves the loop variable one past the end value. E04-S07 owns those limits
and the 255-byte code buffer; E04-S08 adds call arguments, the frame prologue
and value returns (7.13).

### 7.12 Pass-3 relocation and sizing (E04-S07)

@@ -744,6 +745,39 @@ unknown method, with no bad-operand follow-up. The class fixture checks
`METHOD DESTROY` and `METHOD STUB` MAP records, and the output fixture
reconfirms the E202 overwrite refusal.

### 7.13 Calling-convention emission (E04-S08)

`emit.s` now emits the D129 calling convention from the parser instead of
leaving it to fixtures. `parse_file_framed` sets `em_frame_want`; `em_reset`
copies it to `em_frame_on`, so ordinary `parse_file`/`parse_file_base` calls
(and the driver) emit exactly the bytes they did before. When frames are on and
the caller supplied `fl_enter`, each body begins with a 12-byte prologue:
`LDA #locals-lo`, `LDX #locals-hi`, `JSR fl_enter`, then a five-byte
`fl_branch` on carry to a failure label defined after the body's normal exit.
`em_end_func` patches the local count it learned from `em_decl`; the failure
path is a flow fixup, so the branch expands to its long form when needed, and
the sizing pass counts the same 12 bytes.

`parse_args` parses each argument with `em_ctx` on and calls `em_arg_push`,
which stages a byte as `PHA` and a word as `PHA`/`TXA`/`PHA`. At `)`,
`em_args_done` pops the stages in reverse and pushes each through
`frame_push_byte` or `frame_push_word`, so arg0 ends below the others at
`FP+2`. `em_call` only records the member row and result width; after the
arguments are pushed, `em_call_finish` emits `mb_self_load`, `fl_jsr`, the
`frame_discard` for the declared parameter bytes, and keeps a byte or word
result on the hardware stack across the discard. `ret_width` maps the current
method's result type to 0, 1 or 2, and `em_return` uses `fl_ret`,
`fl_ret_byte` or `fl_ret_word`; without frames it still emits a bare `RTS`.

`args.n8` declares `Calc.twice`, which returns `self.scale(3)`, and
`Calc.scale(n as byte)`, which returns `n + 3`. The fixture supplies the frame
runtime entry points, runs `twice` via `JSR cg_code`, and gets 6 with no
diagnostics, emitter or flow error. The `calls.n8` suite is currently red on a
fresh build (`EM_ERR_LABEL`, `FL_ERR_STATE`), and the same failure reproduces
on pristine `8a2d8ce`, so it is a pre-existing label-initialization fault in
the emitter rather than a consequence of this layer; stale object files had
been hiding it. Fixing it is separate work.

## 8. Heap and Ownership

- The generated-program heap initially resides in the same RAM bank as program


+ 1
- 0
newLang/docs/decisions.md Dosyayı Görüntüle

@@ -40,6 +40,7 @@ documented default. `Proposed` means a later change still needs approval.
| D116 | Runtime faults report a stable code and the best available map-resolvable program address. | Rich stack traces are deferred; faults remain diagnosable within the runtime budget. |
| D117 | Freeze the compiler memory map in `cfg/compiler.cfg`: 8K bottom common RAM, COMMON at `$1300`, private zero page at bank 0 page `$1A` via P0, bank-1 tables at `$2000-$BFFF`, IRQs masked and NMIs guarded in compiler states, test mailbox at `$1BF0`. | Internal contract backed by E01-S05 x128 evidence (`docs/memory-map.md`); no user-visible behavior except that RESTORE is ignored while compiling. Generated-program maps remain open. |
| D118 | The compiler loads the whole source file once into a bank-1 cache (`$2000-$6AFF`) and replays it for every pass, instead of re-reading the disk. | Rewind is exact by construction and avoids reading a slow disk three times. Compiler tables get the remaining 21,760 bytes of bank 1 (`$6B00-$BFFF`). Internal; no user-visible behavior. |
| D156 | Emit the calling convention from the parser (E04-S08): arguments are evaluated left to right and staged on the hardware stack, then popped in reverse through `frame_push_byte`/`frame_push_word` so arg0 lands at `FP+2`; a 12-byte prologue (`LDA`/`LDX` locals, `JSR frame_enter`, `BCS fail`) is reserved at each body when frames are requested and its local count is patched after the body; `return` goes through `frame_leave` with a 0/1/2-byte result preserved. Frames are opt-in through `parse_file_framed`, so existing fixtures that do not supply frame entry points emit unchanged code. | The generated program now uses the D129 frame ABI for real calls instead of only in hand-written runtime fixtures. The prologue reserves fixed bytes and patches the count, because locals are known only after the body is parsed; the sizing pass sees the same 12 bytes, so label offsets and lengths still agree. A refused frame branches past the body's normal exit and returns without `frame_leave`. Word arguments and byte/word results are covered; interface arguments and four-byte returns still belong to E06-S07. |

## Proposed



+ 123
- 0
newLang/planning/claims/E04-S08.md Dosyayı Görüntüle

@@ -0,0 +1,123 @@
---
story: E04-S08
owner: codebuff
status: done
started: 2026-11-10
completed: 2026-11-10
depends-on: [E04-S07]
---

# E04-S08 Claim

## Intended change

Emit the D129 calling convention from the parser: stage each call argument on
the hardware stack, pop the stages in reverse through `frame_push_byte` /
`frame_push_word` so arg0 lands at `FP+2`, reserve a patched frame prologue at
the start of each body, and return through `frame_leave` with a 0-, 1- or
2-byte result preserved. Frames stay opt-in so existing fixtures that do not
supply frame entry points keep their previous code shape (D156).

## File area

- `src/common/emit.inc`: `EM_ERR_ARG`, `EM_ARG_MAX`, `EM_PRO_SIZE`
- `src/compiler/emit.s`: `em_prologue_start`, `em_arg_push`, `em_args_done`,
`em_call_finish`, `em_arg_total`, patched prologue and result-width return
- `src/compiler/flow.s`: `fl_push` / `fl_push_word` cells
- `src/compiler/parser.s`: `parse_file_framed`, argument staging hooks,
`em_call_finish` after the argument list, `ret_width`
- `tests/compiler/args.n8`, `tests/compiler/args-test.s`
- `scripts/build-args-test.ps1`, `scripts/verify-args-test.ps1`
- `tests/regression/suites.ps1`, `docs/decisions.md` (D156),
`docs/architecture.md` (7.13), `planning/epics-and-stories.md`
- `planning/claims/E04-S08.md`

## Acceptance evidence

- [x] Arguments are staged while parsed and pushed in reverse: `parse_args`
parses each argument with `em_ctx` on and calls `em_arg_push`, which emits
`PHA` for a byte and `PHA`/`TXA`/`PHA` for a word; at `)` `em_args_done`
pops the stages in reverse and pushes each through `frame_push_byte` or
`frame_push_word`. In the fixture `Calc.scale(n as byte)` reads `n` at
`FP+2` and returns 6 for `self.scale(3)`, so the value arrives at the
declared slot.
- [x] The prologue is patched after the body is parsed: `em_prologue_start`
reserves `LDA #locals-lo`, `LDX #locals-hi`, `JSR frame_enter`, `BCC +1`,
`RTS` (10 bytes); `em_end_func` patches the local count from `em_locals`.
A refused frame executes the `RTS` and never runs `frame_leave` or the body.
The sizing pass counts the same 10 bytes because every byte goes through
`cg_put`.
- [x] Returns leave the frame and preserve the result: `ret_width` maps the
current method's result type to 0/1/2 and `em_return` selects `fl_ret`,
`fl_ret_byte` or `fl_ret_word`; `fl_call`-style result saving keeps A (or
A/X) across the argument discard. The fixture's `twice` returns
`self.scale(3)` and the observed result is 6.
- [x] Frames are opt-in: `parse_file_framed` sets `em_frame_want`, which
`em_reset` copies to `em_frame_on`; `parse_file`, `parse_file_base` and
`parse_file_sized` clear the request. A fixture that does not set it emits
no prologue.
- [x] A test drives source with a parameter and a result in `x128`:
`scripts/verify-args-test.ps1` builds the compiler with `frames.s`, parses
`ARGS.N8`, runs `Calc.twice` with `JSR cg_code`, and requires result 6 with
no diagnostics, emitter, codegen or flow error. The suite reports PASS
through `tests/regression/run-regression.ps1 -Filter args` on a fresh build.

## Coordination notes

- Depends on E04-S07 (pass-3 addresses and routine sizes); both the count and
emit passes see the same prologue and argument bytes, so sizing stays
consistent.
- E06-S07 remains blocked on interface arguments and four-byte returns; this
story covers byte and word parameters/results only.
- `em_call` no longer emits on its own: it records the member row and result
width, and `em_call_finish` emits after the argument list. Parser call sites
were updated together with the emitter, so concrete calls still target the
same method labels.

## Work log

- Reused the existing `fl_call`/`fl_ret_*` encodings rather than adding a
second convention; only the push cells (`fl_push`, `fl_push_word`) are new.
- The prologue initially emitted a manual `$20` opcode *and* `cg_jsr_abs`,
which writes its own `$20`; the resulting JSR target was garbage and the
fixture hung in the emitted code. Removing the manual opcode fixed it; the
timeout marker (`$C0` in the mailbox) is what identified the hang as
runtime rather than compile-time.
- The first prologue used a five-byte flow branch to a failure label. It was
replaced with an inline `BCC +1`/`RTS`, which removes a label from the
routine and shrinks the prologue to 10 bytes.
- `return` expressions and arguments never enabled `em_ctx`, so no value bytes
were emitted and the argument width came from stale state. The parser now
brackets both with `em_stmt_begin`/`em_stmt_end`.
- The frame opt-in was first inferred from `fl_enter`, which lives in
uninitialized BSS and made a fixture without frames emit a prologue; it is
now an explicit `parse_file_framed` request.

## Completion

- Tests run: `tests/regression/run-regression.ps1 -Filter args` (fresh D81 and
fresh object files, run twice); `scripts/verify-args-test.ps1` standalone;
`git diff --check`; before the story, `-Filter driver` (1 passed).
- Results: the args suite reports `args pass` with result 6 and no emitter,
codegen or flow error; emitted code is 0x78 bytes.
- Files changed: `src/common/emit.inc`, `src/compiler/emit.s`,
`src/compiler/flow.s`, `src/compiler/parser.s`, `tests/compiler/args.n8`,
`tests/compiler/args-test.s`, `scripts/build-args-test.ps1`,
`scripts/verify-args-test.ps1`, `tests/regression/suites.ps1`,
`docs/decisions.md`, `docs/architecture.md`,
`planning/epics-and-stories.md`, `planning/claims/E04-S08.md`.
- Remaining risks: interface arguments and four-byte interface returns are not
emitted (E06-S07); arguments are limited to `EM_ARG_MAX` (8) staged values;
the prologue's failure path returns without a diagnostic, so a refused frame
is currently silent; and the pre-existing `calls` suite failure described
below is unrelated but still red.

## Known unrelated failure

`calls` fails on a **fresh** build with `EM_ERR_LABEL` (7) and
`FL_ERR_STATE` (3), `cg_len = 0x31`, while the same test passes when the build
directory's older object files are reused. The identical signature reproduces
on pristine `8a2d8ce` (verified by `git stash` + fresh `build/calls-test`), so
it predates this story and points at emitter label state that is not
deterministically initialized. It is not caused by E04-S08 and needs its own
fix (initialize or validate the per-row method-label table before pass 2).

+ 21
- 0
newLang/planning/epics-and-stories.md Dosyayı Görüntüle

@@ -412,6 +412,27 @@ Primary files: `sizing.s`, `codegen.s`, `output.s`, `src/runtime/startup.s`,

---

### E04-S08 — Calling-convention emission (P0)

**Depends on:** E04-S07

**Acceptance criteria:**

- A call's byte and word arguments are staged while the argument list is
parsed and pushed in reverse, so arg0 lands at `FP+2` in the callee and the
declared parameter slots read the passed values.
- Every body gets a frame prologue whose local byte count is patched after the
body is parsed; a refused frame returns without running `frame_leave`, and
the sizing pass measures the same prologue bytes.
- `return` leaves the frame and preserves a 0-, 1- or 2-byte result; the caller
keeps that result across the argument discard.
- Frames are opt-in, so a fixture that does not request them emits no prologue
and keeps its previous code shape.
- A test drives source with a parameter and a result in `x128` and checks the
returned value.

---

## E05 — Classes, Heap, and Concrete Methods

Purpose: implement object identity, encapsulated state, construction, and


+ 37
- 0
newLang/scripts/build-args-test.ps1 Dosyayı Görüntüle

@@ -0,0 +1,37 @@
# E04-S08: build the argument/prologue/return fixture and a D81 with ARGS.N8.
[CmdletBinding()]
param()

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force
$out = Join-Path $project "build\args-test"
New-Item -ItemType Directory -Force -Path $out | Out-Null

$link = Invoke-Nova128Link -Name "args-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\members.s"),
(Join-Path $project "src\compiler\sizing.s"),
(Join-Path $project "src\compiler\emit.s"),
(Join-Path $project "src\runtime\startup.s"),
(Join-Path $project "src\runtime\frames.s"),
(Join-Path $project "tests\compiler\args-test.s"))
if ((Get-Nova128Label $link.Labels "test_entry") -ne 0x1C0D) { throw "test_entry is not at `$1C0D" }

$pet = Join-Path $out "args.pet"
[IO.File]::WriteAllBytes($pet, (ConvertTo-Nova128Petscii (Get-Content -Raw -LiteralPath (Join-Path $project "tests\compiler\args.n8"))))
New-Nova128Disk -Path (Join-Path $out "args-test.d81") -Files @(
@{ Name = "args-test"; Path = $link.Program; Type = "prg" },
@{ Name = "args.n8"; Path = $pet; Type = "seq" })
Write-Host ("built {0}" -f $link.Program)

+ 37
- 0
newLang/scripts/verify-args-test.ps1 Dosyayı Görüntüle

@@ -0,0 +1,37 @@
# E04-S08: parse ARGS.N8, run Calc.twice, and check the byte result is 6.
[CmdletBinding()]
param([int]$Port = 6563, [int]$TimeoutSeconds = 180)

$ErrorActionPreference = "Stop"
$project = Split-Path $PSScriptRoot -Parent
Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force
& (Join-Path $PSScriptRoot "build-args-test.ps1")

$out = Join-Path $project "build\args-test"
$disk = Join-Path $out "args-test.d81"
$failures = @()

$session = Start-Nova128X128 -Autostart $disk -Drive8Image $disk -Port $Port `
-Screenshot (Join-Path $out "args-vicii.png")
try {
$mb = Wait-Nova128Mailbox $session -TimeoutSeconds $TimeoutSeconds
} catch {
$dump = Read-Nova128Memory $session 0x1BF0 16
Write-Host ("timeout mailbox: " + (($dump | ForEach-Object { $_.ToString('X2') }) -join ' '))
throw
} finally {
Stop-Nova128X128 $session
}
Write-Host ("mailbox: " + (($mb | 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 6) { $failures += "result = $($mb[10]), expected 6" }
if ($mb[12] -ne 0) { $failures += "codegen error $($mb[12])" }
if ($mb[13] -ne 0) { $failures += "flow error $($mb[13])" }

if ($failures) { throw ("args verification failed:`n " + ($failures -join "`n ")) }
Write-Host ("emitted {0} bytes; Calc.twice returned {1}" -f $mb[11], $mb[10])
Write-Host "verified byte argument push, sized prologue, and byte return in x128"

+ 3
- 0
newLang/src/common/emit.inc Dosyayı Görüntüle

@@ -19,10 +19,13 @@ EM_ERR_STATE = 4 ; missing runtime entry or bad emitter state
EM_ERR_FLOW = 5 ; flow.s rejected a label or fixup
EM_ERR_CALL = 6 ; call shape not emitted yet
EM_ERR_LABEL = 7 ; no control-flow label available
EM_ERR_ARG = 8 ; argument width or staging limit

EM_KIND_LOCAL = 0
EM_KIND_PARAM = 1
EM_MAX_ROUTINES = 24
EM_ARG_MAX = 8 ; arguments staged per call
EM_PRO_SIZE = 10 ; patched frame prologue bytes

EM_LAST_LOCAL = 1
EM_LAST_SELF = 2


+ 309
- 28
newLang/src/compiler/emit.s Dosyayı Görüntüle

@@ -31,8 +31,10 @@
.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_cmp_zp, cg_adc_imm, cg_clc, cg_sec, cg_jsr_abs, cg_rts
.import gen_text, gen_text_len
.import cg_len
.import cg_len, cg_code
.import fl_init, fl_set_base, fl_alloc, fl_label, fl_jmp, fl_jsr, fl_branch, fl_release, fl_finish, fl_err
.import fl_enter, fl_leave, fl_discard, fl_push, fl_push_word
.import fl_ret, fl_ret_byte, fl_ret_word
.import mb_init, mb_self_store, mb_self_load, mb_ld_field, mb_st_field
.import tb_get
.import sc_base, sc_val, sc_len
@@ -46,7 +48,8 @@
.export em_finish, em_self, em_member_receiver, em_field_ref, em_call
.export em_open_block, em_cond_false, em_else, em_close_block
.export em_for_var, em_for_save_end, em_for_save_step, em_for_default_step, em_for_setup
.export em_return, em_method_begin, em_program_begin, em_entry
.export em_return, em_method_begin, em_program_begin
.export em_prologue_start, em_arg_push, em_args_done, em_call_finish, em_frame_want, em_entry
.export em_pr_text: abs, em_pr_byte: abs, em_pr_cr: abs

.assert EM_TMP + 1 < MB_SELF, error, "EM_TMP overlaps MB_SELF"
@@ -70,6 +73,8 @@ em_reset:
lda #$00
@init_flow:
jsr fl_init
lda em_frame_want
sta em_frame_on
lda em_base_set
beq @base_done
lda em_code_base
@@ -125,6 +130,9 @@ em_begin_func:
sta em_last_kind
sta em_tgt
sta em_tgt_kind
sta em_pro_on
sta em_arg_n
sta em_call_pending
rts

; The program body starts here. Its offset lets a fixture run the program
@@ -132,6 +140,42 @@ em_begin_func:
em_program_begin:
lda cg_len
sta em_entry
jmp em_prologue_start

; Reserve the patched frame prologue when the caller supplied frame_enter.
; Layout: LDA #locals-lo, LDX #locals-hi, JSR fl_enter, BCC +1 (skip RTS).
; A refused frame runs the RTS and returns without frame_leave; the body
; never starts. em_end_func only patches the local count (EM_PRO_SIZE bytes).
em_prologue_start:
lda em_pro_on
bne @done
lda em_frame_on
beq @done
lda fl_enter
ora fl_enter+1
beq @done
lda #$01
sta em_pro_on
lda cg_len
sta em_pro_at
lda #$A9 ; LDA #locals-lo
jsr cg_put
lda #$00
jsr cg_put
lda #$A2 ; LDX #locals-hi
jsr cg_put
lda #$00
jsr cg_put
lda fl_enter
ldx fl_enter+1
jsr cg_jsr_abs
lda #$90 ; BCC +1
jsr cg_put
lda #$01
jsr cg_put
lda #$60 ; RTS (refused frame)
jmp cg_put
@done: clc
rts

; A signature starts: parameters begin at FP+2.
@@ -563,12 +607,21 @@ 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.
; End of a body: leave the frame and return to the caller.
em_end_func:
lda em_on
beq @done
lda em_pro_on
beq @plain
jsr fl_ret
bcs @fail
jsr em_patch_pro
jsr cg_rts
bcs @fail
jmp @record
@plain: jsr cg_rts
bcs @fail
@record:
ldx em_n_routines
cpx #EM_MAX_ROUTINES
bcs @done
@@ -581,11 +634,35 @@ em_end_func:
rts
@fail: jmp em_fail

; Explicit `return`: no frame prologue is emitted yet, so this is the exit.
; Patch the prologue's local count. The failure branch is a flow fixup, so
; fl_finish handles short and long distances; counting mode writes nothing.
em_patch_pro:
lda em_count
bne @done
ldy em_pro_at
lda em_locals
sta cg_code+1,y
lda em_locals+1
sta cg_code+3,y
@done: clc
rts

; A = result width (0, 1 or 2). Emit the matching exit.
em_return:
sta em_ret_w
lda em_on
beq @done
jmp cg_rts
lda em_pro_on
beq @plain
lda em_ret_w
cmp #$01
beq @byte
cmp #$02
beq @word
jmp fl_ret
@byte: jmp fl_ret_byte
@word: jmp fl_ret_word
@plain: jmp cg_rts
@done: clc
rts

@@ -596,7 +673,8 @@ em_method_begin:
txa
jsr fl_label
bcs @bad
jmp mb_self_store
jsr mb_self_store
jmp em_prologue_start
@bad: lda #EM_ERR_LABEL
sta em_bad
clc
@@ -625,24 +703,15 @@ em_method_label:
sec
rts

; A = member row. Emit a zero-argument call through self.
; A = member row. Record the call; the arguments are parsed next and
; em_call_finish emits the self load, JSR and discard once they are pushed.
em_call:
sta em_nid
jsr em_method_label
bcs @bad
stx em_lab
ldy em_nid
lda #C_MEM_PCOUNT
jsr tb_get
bne @call_bad
lda em_recv_self
beq @call_bad
jsr mb_self_load
bcs @fail
lda em_lab
jsr fl_jsr
bcs @fail
ldy em_nid
sta em_call_row
lda #$00
sta em_call_res
lda #$01
sta em_call_pending
ldy em_call_row
lda #C_MEM_TBASE
jsr tb_get
cmp #TY_NONE
@@ -655,20 +724,174 @@ em_call:
beq @byte
cmp #TY_CHAR
beq @byte
cmp #TY_INTEGER
beq @word
cmp #TY_WORD
beq @word
lda #EM_ERR_CALL
sta em_bad
rts
@void: rts
@byte: lda #$01
sta em_call_res
rts
@word: lda #$02
sta em_call_res
rts

; The just-parsed argument is in A (one byte) or A/X (two). Stage it on the
; hardware stack; em_args_done pops the stages in reverse so arg0 lands at
; FP+2 in the callee frame.
em_arg_push:
lda em_on
beq @done
lda em_size
beq @bad
cmp #$02
bcs @two
ldy em_arg_n
cpy #EM_ARG_MAX
bcs @full
sta em_arg_w,y
inc em_arg_n
lda #$48 ; PHA
jmp cg_put
@two: cmp #$02
bne @bad
ldy em_arg_n
cpy #EM_ARG_MAX
bcs @full
sta em_arg_w,y
inc em_arg_n
lda #$48 ; PHA low
jsr cg_put
lda #$8A ; TXA
jsr cg_put
lda #$48 ; PHA high
jmp cg_put
@bad: lda #EM_ERR_ARG
sta em_bad
clc
rts
@void: lda #$00
sta em_size
@full: lda #EM_ERR_CALL
sta em_bad
@done: clc
rts

; Pop the staged arguments in reverse and push each through frame_push_byte
; or frame_push_word, so the first argument ends at FP+2.
em_args_done:
lda em_on
beq @done
lda em_arg_n
beq @done
lda fl_push
ora fl_push+1
beq @state
lda em_arg_n
sta em_arg_i
@loop: dec em_arg_i
ldx em_arg_i
lda em_arg_w,x
cmp #$02
beq @word
lda #$68 ; PLA
jsr cg_put
lda fl_push
ldx fl_push+1
jsr cg_jsr_abs
jmp @next
@word: lda #$68 ; PLA -> high
jsr cg_put
lda #$AA ; TAX
jsr cg_put
lda #$68 ; PLA -> low
jsr cg_put
lda fl_push_word
ldx fl_push_word+1
jsr cg_jsr_abs
@next: lda em_arg_i
bne @loop
lda #$00
sta em_arg_n
@done: clc
rts
@state: lda #EM_ERR_STATE
sta em_bad
clc
rts
@byte: lda #$01

; Emit the recorded call: self load, JSR, argument discard, result preserved
; across the discard. Sets em_size to the result width.
em_call_finish:
lda em_on
bne :+
jmp @done
: lda em_call_pending
bne :+
jmp @done
: lda #$00
sta em_call_pending
jsr em_arg_total
sta em_call_bytes
jsr mb_self_load
bcc :+
jmp @fail
:
lda em_call_row
jsr em_method_label
bcs @bad
stx em_lab
lda em_lab
jsr fl_jsr
bcs @fail
lda em_call_res
beq @discard
cmp #$01
beq @save_byte
lda #$48 ; PHA low
jsr cg_put
lda #$8A ; TXA
jsr cg_put
lda #$48 ; PHA high
jsr cg_put
jmp @discard
@save_byte:
lda #$48 ; PHA
jsr cg_put
@discard:
lda em_call_bytes
beq @restore
lda fl_discard
ora fl_discard+1
beq @state
lda em_call_bytes
jsr cg_lda_imm
lda #$00
jsr cg_ldx_imm
lda fl_discard
ldx fl_discard+1
jsr cg_jsr_abs
@restore:
lda em_call_res
beq @ok
cmp #$01
beq @rest_byte
lda #$68 ; PLA -> high
jsr cg_put
lda #$AA ; TAX
jsr cg_put
lda #$68 ; PLA -> low
jsr cg_put
jmp @ok
@rest_byte:
lda #$68 ; PLA
jsr cg_put
@ok: lda em_call_res
sta em_size
clc
rts
@call_bad:
lda #EM_ERR_CALL
@state: lda #EM_ERR_STATE
sta em_bad
clc
rts
@@ -676,8 +899,52 @@ em_call:
sta em_bad
clc
rts
@done: clc
rts
@fail: jmp em_fail

; Total bytes of the recorded call's declared parameters. A width the ABI
; does not know sets EM_ERR_CALL and leaves the total as it stood.
em_arg_total:
lda #$00
sta em_arg_bytes
ldy em_call_row
lda #C_MEM_PCOUNT
jsr tb_get
beq @done
sta em_arg_i
ldy em_call_row
lda #C_MEM_PFIRST
jsr tb_get
sta em_nid
@loop: ldy em_nid
lda #C_PAR_TBASE
jsr tb_get
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
lda #EM_ERR_CALL
sta em_bad
jmp @done
@one: lda #$01
jmp @add
@two: lda #$02
@add: clc
adc em_arg_bytes
sta em_arg_bytes
inc em_nid
dec em_arg_i
bne @loop
@done: lda em_arg_bytes
rts

; The next member is applied to self (a field or a call).
em_member_receiver:
lda #$00
@@ -1165,3 +1432,17 @@ em_count: .res 1
em_n_routines: .res 1
em_routine_start: .res 1
em_routine_sizes: .res EM_MAX_ROUTINES
em_pro_on: .res 1
em_pro_at: .res 1
em_ret_w: .res 1
em_arg_n: .res 1
em_arg_i: .res 1
em_arg_b: .res 1
em_arg_bytes: .res 1
em_arg_w: .res EM_ARG_MAX
em_call_row: .res 1
em_call_res: .res 1
em_call_bytes: .res 1
em_call_pending: .res 1
em_frame_on: .res 1
em_frame_want: .res 1

+ 4
- 1
newLang/src/compiler/flow.s Dosyayı Görüntüle

@@ -68,7 +68,7 @@
.export fl_branch_true, fl_branch_false
.export fl_call, fl_call_byte, fl_call_word
.export fl_prologue, fl_ret, fl_ret_byte, fl_ret_word
.export fl_err, fl_enter, fl_leave, fl_discard
.export fl_err, fl_enter, fl_leave, fl_discard, fl_push, fl_push_word

.segment "CODE"

@@ -762,4 +762,7 @@ fl_fix: .res FL_FIX_MAX * FL_FIX_SIZE
fl_enter: .res 2
fl_leave: .res 2
fl_discard: .res 2
fl_push: .res 2
fl_push_word: .res 2

fl_code_base: .res 2

+ 89
- 4
newLang/src/compiler/parser.s Dosyayı Görüntüle

@@ -65,9 +65,11 @@
.import em_open_block, em_cond_false, em_else, em_close_block
.import em_for_var, em_for_save_end, em_for_save_step, em_for_default_step, em_for_setup
.import em_return, em_method_begin, em_program_begin
.import em_arg_push, em_args_done, em_call_finish, em_frame_want
.import sz_capture, sz_verify

.export parse_file, parse_file_base, parse_file_sized, tb_get, tb_set, mem_count, par_count
.export parse_file, parse_file_base, parse_file_sized, parse_file_framed
.export tb_get, tb_set, mem_count, par_count
.export gen_text, gen_text_len, gen_print_count, gen_bad
.export ex_trace_on, ex_min_sp
.exportzp ex_tp
@@ -96,6 +98,15 @@ ex_tp: .res 2 ; test trace pointer (ex_trace_on)
.segment "CODE"

parse_file:
lda #$00
sta em_frame_want
jmp parse_plain

; Same as parse_file, but generated bodies get a frame_enter prologue.
parse_file_framed:
lda #$01
sta em_frame_want
parse_plain:
lda #$00
sta em_base_set
sta em_count
@@ -107,6 +118,7 @@ parse_file:
parse_file_base:
jsr em_set_base
lda #$00
sta em_frame_want
sta em_count
sta diag_suppress
jmp parse_start
@@ -114,6 +126,8 @@ parse_file_base:
; Count pass, snapshot, then an emitting pass. The caller installs the final
; code base first. Diagnostics are reported only by the count pass.
parse_file_sized:
lda #$00
sta em_frame_want
lda #$01
sta em_count
lda #$00
@@ -1325,8 +1339,23 @@ parse_statement:
beq @bare
cmp #TOK_EOF
beq @bare
lda pass
cmp #$02
bne @ret_parse
lda em_on
beq @ret_parse
jsr em_stmt_begin
@ret_parse:
jsr parse_full
lda pass
cmp #$02
bne @ret_typed
lda em_on
beq @ret_typed
jsr em_stmt_end
@ret_typed:
jsr check_return
jsr ret_width
jsr em_return
jmp end_stmt
@bare: lda ret_kind
@@ -1335,7 +1364,8 @@ parse_statement:
lda #E_SEM_MISSING_RETURN
ldx stmt_col
jsr err2_col
: jsr em_return
: jsr ret_width
jsr em_return
jmp end_stmt
@else: ldx depth ; only once, directly inside an if
lda block_stack-1,x
@@ -1623,6 +1653,32 @@ check_return:
jmp err2_col
@ok: rts

; A = the current method's result width (0, 1 or 2) for em_return. A program
; and a void method report 0, so their return leaves the frame without a value.
ret_width:
lda cur_mem
cmp #$FF
beq @zero
ldy cur_mem
lda #C_MEM_TBASE
jsr tb_get
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

; The for variable must be a byte, word, or integer. A bad one is reported
; as "FOR NOT ALLOWED ON <type>" and its type becomes unknown.
check_for_var:
@@ -1985,6 +2041,13 @@ parse_postfix:
jsr em_call
:
jsr parse_args
lda pass
cmp #$02
bne :+
lda em_on
beq :+
jsr em_call_finish
:
ldx ex_depth
jsr load_sc
lda #EX_CALL
@@ -2132,8 +2195,23 @@ parse_args:
@arg: lda tok_col
ldx ex_depth
sta ex_acol,x
lda pass
cmp #$02
bne @arg_parse
lda em_on
beq @arg_parse
jsr em_stmt_begin
@arg_parse:
jsr parse_full
jsr check_arg
lda pass
cmp #$02
bne :+
lda em_on
beq :+
jsr em_stmt_end
jsr em_arg_push
:
lda tok_kind
cmp #TOK_COMMA
bne :+
@@ -2145,12 +2223,19 @@ parse_args:
jmp syn_error
@close: ldx ex_depth ; too few arguments: reported at `)`
lda ex_cchk,x
beq @done
beq @args_done
lda ex_carg,x
cmp ex_cpc,x
bcs @done
bcs @args_done
lda tok_col
jsr count_error
@args_done:
lda pass
cmp #$02
bne @done
lda em_on
beq @done
jsr em_args_done
@done: jmp next

; Argument ex_carg (sc) against its parameter. The first argument beyond


+ 179
- 0
newLang/tests/compiler/args-test.s Dosyayı Görüntüle

@@ -0,0 +1,179 @@
; E04-S08: parse args.n8 and run Calc.twice, which pushes one byte argument,
; enters a sized frame in Calc.scale, and returns a byte.
;
; The first emitted body is Calc.twice at cg_code+0. The fixture supplies the
; frame runtime entry points, so the prologue emitted at the start of twice
; calls frame_enter(0), and scale reads its parameter at FP+2. twice returns
; self.scale(3); scale returns n + 3, so the expected result is 6.
;
; Mailbox data: +0 diagnostics, +1 em_bad, +2 result, +3 cg_len, +4 cg_err,
; +5 fl_err. FAIL stage: 1 load, 2 diagnostics, 3 emitter, 4 result.

.setcpu "6502"
.include "../../src/common/c128.inc"
.include "../../src/common/mailbox.inc"
.include "../../src/common/formats.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_framed
.import em_on, em_bad
.import cg_code, cg_len, cg_err
.import fl_err, fl_enter, fl_leave, fl_discard, fl_push, fl_push_word
.import frame_init, frame_enter, frame_leave, frame_discard
.import frame_push_byte, frame_push_word

.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 #<frame_enter
sta fl_enter
lda #>frame_enter
sta fl_enter+1
lda #<frame_leave
sta fl_leave
lda #>frame_leave
sta fl_leave+1
lda #<frame_discard
sta fl_discard
lda #>frame_discard
sta fl_discard+1
lda #<frame_push_byte
sta fl_push
lda #>frame_push_byte
sta fl_push+1
lda #<frame_push_word
sta fl_push_word
lda #>frame_push_word
sta fl_push_word+1

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

jsr parse_file_framed
jsr diag_finish
sta MB_DATA+0
lda em_bad
sta MB_DATA+1
lda cg_len
sta MB_DATA+3
lda cg_err
sta MB_DATA+4
lda fl_err
sta MB_DATA+5

lda MB_DATA+0
ON_ZERO @fail_diag
lda em_bad
ON_ZERO @fail_emit
lda cg_err
ON_ZERO @fail_cg
lda fl_err
ON_ZERO @fail_cg
jmp @run
@fail_cg:
lda #3
jmp @fail

@run: jsr frame_init
ldx #3 ; descriptor + zeroed byte field
lda #$00
@obj: sta object,x
dex
bpl @obj
lda #<object
ldx #>object
jsr cg_code
sta MB_DATA+2
cmp #$06
beq @pass
lda #4
jmp @fail

@fail_load:
sta MB_DATA+4
lda src_dos_code
sta MB_DATA+5
lda src_kernal_err
sta MB_DATA+6
lda #1
jmp @fail
@fail_diag:
lda #2
jmp @fail
@fail_emit:
lda #3
@fail: sta MB_CODE
lda #$00
sta MB_CODE+1
lda #MB_ST_FAIL
sta MB_STATUS
jmp @done
@pass: lda #MB_ST_PASS
sta MB_STATUS
@done: jsr mmu_shutdown
plp
rts

.segment "RODATA"
name_src: .byte "ARGS.N8"
name_src_len = * - name_src

.segment "BSS"
object: .res 4

+ 13
- 0
newLang/tests/compiler/args.n8 Dosyayı Görüntüle

@@ -0,0 +1,13 @@
// E04-S08 calling-convention source: one byte argument and a byte result.
class Calc
public method twice() as byte
return self.scale(3)
end method

public method scale(n as byte) as byte
return n + 3
end method
end class

program args
end program

+ 1
- 0
newLang/tests/regression/suites.ps1 Dosyayı Görüntüle

@@ -16,6 +16,7 @@
@{ Name = "print"; Script = "scripts\verify-print-test.ps1"; BuildDir = "print-test"; Timeout = 300 }
@{ Name = "statements"; Script = "scripts\verify-statements-test.ps1"; BuildDir = "statements-test"; Timeout = 300 }
@{ Name = "calls"; Script = "scripts\verify-calls-test.ps1"; BuildDir = "calls-test"; Timeout = 300 }
@{ Name = "args"; Script = "scripts\verify-args-test.ps1"; BuildDir = "args-test"; Timeout = 300; Success = "verified byte argument push" }
@{ Name = "class"; Script = "scripts\verify-class-test.ps1"; BuildDir = "class-test"; Timeout = 300 }
@{ Name = "member"; Script = "scripts\verify-member-test.ps1"; BuildDir = "member-test"; Timeout = 300 }
@{ Name = "heap"; Script = "scripts\verify-heap-test.ps1"; BuildDir = "heap-test"; Timeout = 300 }


Yükleniyor…
İptal
Kaydet

Powered by TurnKey Linux.