; construct.s - runtime object construction and deletion (E05-S03). ; ; This layer sits above the E05-S01 heap and the E05-S02 descriptor ABI. It ; deliberately does not depend on the compiler: generated code will call it ; after evaluating constructor arguments. ; ; API: ; obj_new A/X = class descriptor pointer. The caller has already ; pushed constructor arguments right-to-left on the software ; frame stack and set obj_init_ptr to the init entry (or 0). ; Allocates the descriptor's instance size, zeroes it (heap ; contract), stores the descriptor at object+0, then calls ; init with A/X = self. Returns A/X = object, C=0. On failure ; A/X=0, C=1 and obj_error is OBJ_ERR_HEAP or OBJ_ERR_BAD. ; init is never called on failure. ; obj_delete A/X = object. Null is a no-op. Reads the optional ; destructor from the descriptor, calls it once with A/X = ; self, then frees the complete object. Returns C=0 and ; A/X=0; C=1 with obj_error on a bad object or heap error. ; obj_init_ptr init entry selected by the caller; 0 = no init. ; obj_error OBJ_OK, OBJ_ERR_HEAP, or OBJ_ERR_BAD. ; ; Constructor arguments remain on the caller's frame stack. init uses the ; normal frame_enter/frame_leave contract and sees them at FP+2. The caller ; discards the argument bytes after obj_new returns. obj_delete and obj_new ; use obj_call, a synthetic-return indirect call, because the 8502 has no ; JSR (address). obj_call takes its entry from co_vec because A/X already ; carry the callee's self pointer; it preserves A/X and only needs one level ; of synthetic return, so E06-S02 can reuse it for interface dispatch. .setcpu "6502" .include "../common/class.inc" .import heap_alloc, heap_free, obj_class .export obj_new, obj_delete, obj_init_ptr, obj_error .segment "ZEROPAGE" co_vec: .res 2 ; indirect target for obj_call co_desc: .res 2 ; descriptor pointer co_ptr: .res 2 ; object header writer co_self: .res 2 ; A/X passed through obj_call .segment "CODE" ; A/X = descriptor. Allocate and construct; see the API above. obj_new: sta co_desc stx co_desc+1 lda #OBJ_OK sta obj_error ldy #CD_INSTANCE_SIZE lda (co_desc),y sta co_size iny lda (co_desc),y sta co_size+1 ora co_size beq @bad lda co_size ldx co_size+1 jsr heap_alloc bcs @heap_fail sta co_obj stx co_obj+1 lda co_obj ; object+0 = descriptor sta co_ptr lda co_obj+1 sta co_ptr+1 ldy #OBJ_DESCRIPTOR lda co_desc sta (co_ptr),y iny lda co_desc+1 sta (co_ptr),y lda obj_init_ptr ora obj_init_ptr+1 beq @ok lda obj_init_ptr sta co_vec lda obj_init_ptr+1 sta co_vec+1 lda co_obj ldx co_obj+1 jsr obj_call @ok: lda co_obj ldx co_obj+1 clc rts @heap_fail: lda #OBJ_ERR_HEAP sta obj_error lda #$00 tax sec rts @bad: lda #OBJ_ERR_BAD sta obj_error lda #$00 tax sec rts ; A/X = object. Optional destructor, then free. Null is successful. obj_delete: sta co_obj stx co_obj+1 lda #OBJ_OK sta obj_error lda co_obj ora co_obj+1 bne :+ jmp @null : lda co_obj ; reload: the null test left A = low|high ldx co_obj+1 jsr obj_class bcc :+ jmp @bad : sta co_desc stx co_desc+1 ldy #CD_INSTANCE_SIZE lda (co_desc),y sta co_size iny lda (co_desc),y sta co_size+1 ora co_size bne :+ jmp @bad : ldy #CD_DESTRUCTOR lda (co_desc),y sta co_destroy iny lda (co_desc),y sta co_destroy+1 lda co_destroy ora co_destroy+1 bne :+ jmp @free : lda co_destroy sta co_vec lda co_destroy+1 sta co_vec+1 lda co_obj ldx co_obj+1 jsr obj_call @free: lda co_obj ldx co_obj+1 jsr heap_free bcs @heap_fail @null: lda #$00 tax clc rts @heap_fail: lda #OBJ_ERR_HEAP sta obj_error lda #$00 tax sec rts @bad: lda #OBJ_ERR_BAD sta obj_error lda #$00 tax sec rts ; Entry in co_vec. A/X pass through to the callee, so the caller passes self ; there. Push a synthetic return address, then JMP (co_vec): the callee's RTS ; returns to @ret, whose RTS returns to the original caller. co_vec must not ; straddle a page because of the 6502 JMP-indirect bug. obj_call: sta co_self stx co_self+1 lda #>(@ret - 1) pha lda #<(@ret - 1) pha lda co_self ldx co_self+1 jmp (co_vec) @ret: rts .assert $FF, error, "co_vec must not straddle a page" .segment "BSS" co_obj: .res 2 co_size: .res 2 co_destroy: .res 2 obj_init_ptr: .res 2 obj_error: .res 1