Make renumber scratch runtime-owned #50

Open
logikoma wants to merge 1 commits from 12 into main
2 changed files with 59 additions and 14 deletions
Showing only changes of commit e20fd445a4 - Show all commits

View File

@@ -118,6 +118,12 @@ typedef struct akbasic_Runtime
{
akbasic_SourceLine source[AKBASIC_MAX_SOURCE_LINES];
/* Scratch owned by this runtime for RENUMBER and its target prescan. */
int16_t renumber_map[AKBASIC_MAX_SOURCE_LINES];
uint8_t renumber_visited[AKBASIC_MAX_SOURCE_LINES];
akbasic_SourceLine renumber_line;
char renumber_discard[AKBASIC_MAX_LINE_LENGTH * 2];
/* Pools. Nothing here is malloc'd; everything is drawn from and returned. */
akbasic_Environment environments[AKBASIC_MAX_ENVIRONMENTS];
akbasic_Variable variables[AKBASIC_MAX_VARIABLES];

View File

@@ -72,7 +72,7 @@ struct akbasic_TargetWalk
* rewritten: `GOTO 9999` in a program with no line 9999 is already broken, and
* inventing a destination for it would hide that.
*/
static int64_t mapped(const int64_t *map, int64_t line)
static int64_t mapped(const int16_t *map, int64_t line)
{
if ( line < 0 || line >= AKBASIC_MAX_SOURCE_LINES ) {
return line;
@@ -306,7 +306,7 @@ static akerr_ErrorContext *rewrite_line(akbasic_TargetWalk *walk, const char *co
static akerr_ErrorContext *visit_renumber(akbasic_TargetWalk *walk, int64_t target, char *dest, size_t len)
{
PREPARE_ERROR(errctx);
const int64_t *map = (const int64_t *)walk->self;
const int16_t *map = (const int16_t *)walk->self;
int written = 0;
PASS(errctx, aksl_snprintf(&written, dest, len, "%" PRId64, mapped(map, target)));
@@ -316,8 +316,7 @@ static akerr_ErrorContext *visit_renumber(akbasic_TargetWalk *walk, int64_t targ
akerr_ErrorContext *akbasic_renumber(akbasic_Runtime *obj, int64_t newstart, int64_t increment, int64_t oldstart)
{
PREPARE_ERROR(errctx);
static int64_t map[AKBASIC_MAX_SOURCE_LINES];
static akbasic_SourceLine rewritten[AKBASIC_MAX_SOURCE_LINES];
int16_t *map = obj == NULL ? NULL : obj->renumber_map;
akbasic_TargetWalk walk = { map, visit_renumber };
int64_t next = newstart;
int64_t i = 0;
@@ -356,10 +355,8 @@ akerr_ErrorContext *akbasic_renumber(akbasic_Runtime *obj, int64_t newstart, int
next += increment;
}
PASS(errctx, aksl_memset(rewritten, 0, sizeof(rewritten)));
PASS(errctx, aksl_memset(obj->renumber_visited, 0, sizeof(obj->renumber_visited)));
for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) {
int64_t target = 0;
if ( obj->source[i].code[0] == '\0' ) {
continue;
}
@@ -367,20 +364,62 @@ akerr_ErrorContext *akbasic_renumber(akbasic_Runtime *obj, int64_t newstart, int
* Every line is rewritten, not just the moved ones: a line before
* `oldstart` can branch into the region that moved.
*/
target = mapped(map, i);
PASS(errctx, rewrite_line(&walk, obj->source[i].code,
rewritten[target].code, sizeof(rewritten[target].code)));
rewritten[target].lineno = target;
obj->renumber_line.code, sizeof(obj->renumber_line.code)));
obj->renumber_line.lineno = i;
/*
* Every line comes out numbered, whether or not it went in that way.
* Asking for numbers is what RENUMBER is, and a program that has been
* through it can be branched into by number -- which is the whole point
* of running it over source that arrived without any.
*/
rewritten[target].numbered = true;
obj->renumber_line.numbered = true;
PASS(errctx, aksl_memcpy(&obj->source[i], &obj->renumber_line,
sizeof(obj->source[i])));
}
PASS(errctx, aksl_memcpy(obj->source, rewritten, sizeof(obj->source)));
/* Move the already-rewritten lines in place. The map is a partial
* permutation: a chain ends at an empty slot, while a cycle closes back
* on its starting line. A single displaced line is sufficient for both. */
for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) {
int64_t current = i;
akbasic_SourceLine displaced;
akbasic_SourceLine next_line;
if ( map[i] < 0 || obj->renumber_visited[i] ) {
continue;
}
PASS(errctx, aksl_memcpy(&displaced, &obj->source[i], sizeof(displaced)));
Review

Couldn't all of these aksl_memcpy calls in this loop be aksl_strncpy() calls sized to AKBASIC_MAX_LINE_LENGTH? That would be faster, since very few lines will fill all of the bytes in their line. We don't actually care if everything in the line gets initialized to zero, we just want the new null-terminated string in there. This would save cycles.

Couldn't all of these `aksl_memcpy` calls in this loop be `aksl_strncpy()` calls sized to `AKBASIC_MAX_LINE_LENGTH`? That would be faster, since very few lines will fill all of the bytes in their line. We don't actually care if *everything* in the line gets initialized to zero, we just want the new null-terminated string in there. This would save cycles.
Review

@logikoma Couldn't all of these aksl_memcpy calls in this loop be aksl_strncpy() calls sized to AKBASIC_MAX_LINE_LENGTH? That would be faster, since very few lines will fill all of the bytes in their line. We don't actually care if everything in the line gets initialized to zero, we just want the new null-terminated string in there. This would save cycles.

@logikoma Couldn't all of these aksl_memcpy calls in this loop be aksl_strncpy() calls sized to AKBASIC_MAX_LINE_LENGTH? That would be faster, since very few lines will fill all of the bytes in their line. We don't actually care if everything in the line gets initialized to zero, we just want the new null-terminated string in there. This would save cycles.
for ( ;; ) {
int64_t destination = map[current];
obj->renumber_visited[current] = 1;
if ( destination == i ) {
displaced.lineno = destination;
PASS(errctx, aksl_memcpy(&obj->source[destination], &displaced,
sizeof(displaced)));
break;
}
if ( map[destination] < 0 ) {
displaced.lineno = destination;
PASS(errctx, aksl_memcpy(&obj->source[destination], &displaced,
sizeof(displaced)));
PASS(errctx, aksl_memset(&obj->source[current], 0,
sizeof(obj->source[current])));
break;
}
PASS(errctx, aksl_memcpy(&next_line, &obj->source[destination],
sizeof(displaced)));
displaced.lineno = destination;
PASS(errctx, aksl_memcpy(&obj->source[destination], &displaced,
sizeof(obj->renumber_line)));
PASS(errctx, aksl_memset(&obj->source[current], 0,
sizeof(obj->source[current])));
PASS(errctx, aksl_memcpy(&displaced, &next_line,
sizeof(displaced)));
current = destination;
}
}
SUCCEED_RETURN(errctx);
}
@@ -435,7 +474,6 @@ akerr_ErrorContext *akbasic_runtime_check_targets(akbasic_Runtime *obj)
* step(). Nothing is read back out of it -- the walk needs somewhere to put
* the text it would have written, and this is it.
*/
static char discard[AKBASIC_MAX_LINE_LENGTH * 2];
CheckState state = { NULL, 0 };
akbasic_TargetWalk walk = { &state, visit_check };
int64_t entry = 0;
@@ -463,7 +501,8 @@ akerr_ErrorContext *akbasic_runtime_check_targets(akbasic_Runtime *obj)
* the whole point of setting it.
*/
obj->environment->lineno = i;
PASS(errctx, rewrite_line(&walk, obj->source[i].code, discard, sizeof(discard)));
PASS(errctx, rewrite_line(&walk, obj->source[i].code,
obj->renumber_discard, sizeof(obj->renumber_discard)));
}
/* Nothing was refused, so leave the cursor as the caller had it. */
obj->environment->lineno = entry;