/** * @file runtime_machine.c * @brief The group J verbs: FETCH, STASH and SYS. * * Machine-level verbs on a machine that is not a Commodore 128. `POKE`, `PEEK` * and `POINTER` already treat a BASIC integer as a real address in this * process's memory -- that decision was made when they were ported -- and these * follow it, because a `FETCH` that meant something different from the `PEEK` * beside it would be worse than either answer on its own. * * `SYS` does not follow it, and the difference is the point: reading and writing * a byte is something this process can do, and jumping to one is not. */ #include #include #include #include #include #include #include #include "verbs.h" /* Most verbs answer "did something happen"; this is that answer. */ #define SUCCEED_TRUE(__obj, __dest) \ do { \ *(__dest) = &(__obj)->staticTrueValue; \ } while ( 0 ) /** * @brief The copy behind both FETCH and STASH. * * On a C128 the two differ by which side is expansion RAM: `STASH` writes out to * the RAM Expansion Unit and `FETCH` reads back from it. There is no expansion * unit here and no banks, so both are the same byte copy and the only honest * thing to do is say so rather than invent a distinction. * * @param obj Runtime to evaluate against. * @param expr The command leaf. * @param verb Name to use in a diagnostic. * @return `NULL` on success, otherwise an error context owned by the caller. */ static akerr_ErrorContext *copy_bytes(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, const char *verb) { PREPARE_ERROR(errctx); double args[4]; int count = 0; int64_t length = 0; PASS(errctx, akbasic_args_numbers(obj, expr, verb, args, 4, &count)); FAIL_ZERO_RETURN(errctx, (count >= 3), AKBASIC_ERR_SYNTAX, "Expected %s (count), (source address), (destination address)", verb); length = (int64_t)args[0]; FAIL_ZERO_RETURN(errctx, (length >= 0), AKBASIC_ERR_VALUE, "%s cannot copy a negative number of bytes", verb); FAIL_ZERO_RETURN(errctx, (args[1] != 0.0 && args[2] != 0.0), AKBASIC_ERR_VALUE, "%s will not touch address zero", verb); if ( length == 0 ) { SUCCEED_RETURN(errctx); } /* * memmove rather than memcpy: a program shifting a buffer along by a few * bytes overlaps its own source, and that is a normal thing to ask for. * * There is no bounds check, and there cannot be one -- the same is true of * POKE and PEEK. A BASIC integer here is a real address, so a wrong one is a * segmentation fault rather than an error message. See TODO.md section 5. */ PASS(errctx, aksl_memmove((void *)(uintptr_t)args[2], (const void *)(uintptr_t)args[1], (size_t)length)); SUCCEED_RETURN(errctx); } akerr_ErrorContext *akbasic_cmd_fetch(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest) { PREPARE_ERROR(errctx); (void)lval; (void)rval; FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER, "NULL argument in FETCH"); PASS(errctx, copy_bytes(obj, expr, "FETCH")); SUCCEED_TRUE(obj, dest); SUCCEED_RETURN(errctx); } akerr_ErrorContext *akbasic_cmd_stash(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest) { PREPARE_ERROR(errctx); (void)lval; (void)rval; FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER, "NULL argument in STASH"); PASS(errctx, copy_bytes(obj, expr, "STASH")); SUCCEED_TRUE(obj, dest); SUCCEED_RETURN(errctx); } akerr_ErrorContext *akbasic_cmd_sys(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest) { PREPARE_ERROR(errctx); double args[4]; int count = 0; (void)lval; (void)rval; FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER, "NULL argument in SYS"); /* * Parsed, then refused. Parsing first is deliberate: a program is told that * SYS is not implemented rather than that its arguments are wrong, and a * listing containing SYS still LISTs and still RENUMBERs. */ PASS(errctx, akbasic_args_numbers(obj, expr, "SYS", args, 4, &count)); FAIL_ZERO_RETURN(errctx, (count >= 1), AKBASIC_ERR_SYNTAX, "Expected SYS (address)"); /* * There is no 6502. SYS calls machine code at an address, and the machine * code a C128 program would call is ROM that does not exist here -- so there * is nothing to jump to and nothing sensible to emulate. Jumping to a real * address in this process would be a way to crash it on purpose. * * Refused by name rather than ignored, because a program whose SYS silently * did nothing would carry on as though it had worked. This is the same * reasoning BANK, FAST and MONITOR are out of scope on, with one difference: * those are refused at the table and this one has a handler, because SYS is * common enough in published listings to be worth a specific message. */ FAIL_RETURN(errctx, AKBASIC_ERR_DEVICE, "SYS %d: there is no 6502 here, and no ROM to call. Machine code cannot be run by this interpreter", (int)args[0]); }