strings: copy-on-write

Only copy strings when they are mutated. This avoids copying strings in
cases where neither the copy nor the original would be modified during
the lifetime of the copy, e.g. when passing strings by-value.

This is purely an optimization.
This commit is contained in:
Nunuhara Cabbage
2019-10-20 21:26:51 -07:00
parent e5ef9f4d2d
commit b3a660f8ec
5 changed files with 203 additions and 147 deletions
+2 -5
View File
@@ -59,11 +59,8 @@ static int32_t read_int32(struct ain_reader *r)
static uint8_t *read_code(struct ain_reader *r, size_t len)
{
uint8_t *bytes = xmalloc(len + 6);
uint8_t *bytes = xmalloc(len);
memcpy(bytes, r->buf + r->index, len);
// XXX: main() returns to CALLSYS 0x0 (system.Exit)
LittleEndian_putW(bytes, len, CALLSYS);
LittleEndian_putDW(bytes, len + 2, 0);
r->index += len;
return bytes;
}
@@ -91,7 +88,7 @@ static struct string *read_vm_string(struct ain_reader *r)
{
size_t len = strlen((char*)r->buf + r->index);
struct string *s = make_string((char*)r->buf + r->index, len);
s->literal = true;
s->cow = true;
r->index += len + 1;
return s;
}
+134 -103
View File
@@ -22,127 +22,52 @@
#include "vm_string.h"
#include "utfsjis.h"
int nr_strings = 0;
static struct string *alloc_string(int size)
{
nr_strings++;
return xmalloc(sizeof(struct string) + size + 1);
}
void free_string(struct string *str)
{
if (!str->ref)
ERROR("Double free of string object");
if (!--str->ref) {
free(str);
nr_strings--;
}
}
struct string *make_string(const char *str, unsigned int len)
{
struct string *s = xmalloc(sizeof(struct string) + len + 1);
struct string *s = alloc_string(len);
s->size = len;
s->literal = false;
s->ref = 1;
s->cow = 0;
memcpy(s->text, str, len);
s->text[len] = '\0';
return s;
}
void free_string(struct string *str)
struct string *string_ref(struct string *s)
{
if (!str->literal)
free(str);
s->cow = 1;
s->ref++;
return s;
}
struct string *string_dup(const struct string *in)
{
struct string *out = xmalloc(sizeof(struct string) + in->size + 1);
struct string *out = alloc_string(in->size);
out->size = in->size;
out->literal = false;
out->ref = 1;
out->cow = 0;
memcpy(out->text, in->text, in->size + 1);
return out;
}
void string_append(struct string **_a, const struct string *b)
{
struct string *a = *_a;
size_t a_size = a->size;
if (a->literal) {
a = xmalloc(sizeof(struct string) + a->size + b->size + 1);
a->literal = false;
memcpy(a->text, (*_a)->text, a_size+1);
} else {
a = xrealloc(a, sizeof(struct string) + a->size + b->size + 1);
}
a->size = a_size + b->size;
memcpy(a->text + a_size, b->text, b->size + 1);
*_a = a;
}
struct string *string_concatenate(const struct string *a, const struct string *b)
{
struct string *s = xmalloc(sizeof(struct string) + a->size + b->size + 1);
s->size = a->size + b->size;
s->literal = false;
memcpy(s->text, a->text, a->size);
memcpy(s->text + a->size, b->text, b->size + 1);
return s;
}
struct string *string_copy(const struct string *s, int index, int len)
{
if (index < 0)
index = 0;
if (len <= 0)
return make_string("", 0);
if ((index = sjis_index(s->text, index)) < 0)
return make_string("", 0);
if ((len = sjis_index(s->text + index, len)) < 0)
len = s->size - index;
return make_string(s->text + index, len);
}
int string_find(const struct string *haystack, const struct string *needle)
{
int c = 0;
for (int i = 0; i < haystack->size; i++, c++) {
if (!strncmp(haystack->text+i, needle->text, needle->size))
return c;
if (SJIS_2BYTE(haystack->text[i])) {
i++;
}
}
return -1;
}
void string_push_back(struct string **s, int c)
{
int bytes = SJIS_2BYTE(c) ? 2 : 1;
*s = xrealloc(*s, sizeof(struct string) + (*s)->size + bytes + 1);
(*s)->text[(*s)->size++] = c & 0xFF;
if (bytes == 2) {
(*s)->text[(*s)->size++] = c >> 8;
}
(*s)->text[(*s)->size] = '\0';
}
void string_pop_back(struct string *s)
{
// get index of last character
int c = 0;
for (int i = 0; i < s->size; i++) {
c = i;
if (SJIS_2BYTE(s->text[i])) {
i++;
}
}
s->text[c] = '\0';
s->size = c;
}
void string_erase(struct string *s, int index)
{
int bytes;
if (index < 0)
index = 0;
if (index >= s->size)
return;
if ((index = sjis_index(s->text, index)) < 0)
return;
bytes = SJIS_2BYTE(s->text[index]) ? 2 : 1;
memcpy(s->text + index, s->text + index + bytes, s->size - index - bytes);
s->size -= bytes;
s->text[s->size] = '\0';
}
struct string *integer_to_string(int n)
{
char buf[512];
@@ -163,3 +88,109 @@ struct string *float_to_string(float f, int precision)
len = snprintf(buf, 512, "%.*f", precision, f);
return make_string(buf, len);
}
struct string *string_concatenate(const struct string *a, const struct string *b)
{
struct string *s = alloc_string(a->size + b->size);
s->size = a->size + b->size;
s->ref = 1;
s->cow = 0;
memcpy(s->text, a->text, a->size);
memcpy(s->text + a->size, b->text, b->size + 1);
return s;
}
struct string *string_copy(const struct string *s, int index, int len)
{
if (index < 0)
index = 0;
if (len <= 0)
return make_string("", 0);
if ((index = sjis_index(s->text, index)) < 0)
return make_string("", 0);
if ((len = sjis_index(s->text + index, len)) < 0)
len = s->size - index;
return make_string(s->text + index, len);
}
static struct string *cow_check(struct string *s)
{
if (s->cow && s->ref > 1) {
struct string *out = string_dup(s);
free_string(s);
return out;
}
if (s->cow)
s->cow = 0;
return s;
}
void string_append(struct string **_a, const struct string *b)
{
struct string *a = *_a = cow_check(*_a);
size_t a_size = a->size;
a = xrealloc(a, sizeof(struct string) + a->size + b->size + 1);
a->size = a_size + b->size;
memcpy(a->text + a_size, b->text, b->size + 1);
*_a = a;
}
void string_push_back(struct string **s, int c)
{
int bytes = SJIS_2BYTE(c) ? 2 : 1;
*s = cow_check(*s);
*s = xrealloc(*s, sizeof(struct string) + (*s)->size + bytes + 1);
(*s)->text[(*s)->size++] = c & 0xFF;
if (bytes == 2) {
(*s)->text[(*s)->size++] = c >> 8;
}
(*s)->text[(*s)->size] = '\0';
}
void string_pop_back(struct string **s)
{
*s = cow_check(*s);
// get index of last character
int c = 0;
for (int i = 0; i < (*s)->size; i++) {
c = i;
if (SJIS_2BYTE((*s)->text[i])) {
i++;
}
}
(*s)->text[c] = '\0';
(*s)->size = c;
}
void string_erase(struct string **s, int index)
{
int bytes;
if (index < 0)
index = 0;
if (index >= (*s)->size)
return;
if ((index = sjis_index((*s)->text, index)) < 0)
return;
bytes = SJIS_2BYTE((*s)->text[index]) ? 2 : 1;
*s = cow_check(*s);
memcpy((*s)->text + index, (*s)->text + index + bytes, (*s)->size - index - bytes);
(*s)->size -= bytes;
(*s)->text[(*s)->size] = '\0';
}
int string_find(const struct string *haystack, const struct string *needle)
{
int c = 0;
for (int i = 0; i < haystack->size; i++, c++) {
if (!strncmp(haystack->text+i, needle->text, needle->size))
return c;
if (SJIS_2BYTE(haystack->text[i])) {
i++;
}
}
return -1;
}
+23 -1
View File
@@ -13,7 +13,8 @@ from the stack and stored in a "page" by the CALLFUNC instruction.
### Pages
Non-pointer variables are stored in "pages". There is a global page for global
variables, and a local page for each function call.
variables, and a local page for each function call. Structures and arrays are
also implemented as pages.
The SH_LOCAL* family of instructions implicitly operate on the current page.
@@ -50,7 +51,28 @@ memory leaks by creating circular references. E.g. the following code leaks:
// they each hold a reference to the other, even after they're both out
// of scope
}
Another point to note is that reference counting operates at the page level. So
if you create a reference to a local variable which escapes the normal scope of
that variable, *every other variable in the same page* remains referenced until
that reference is deleted. E.g.
struct a {
int a;
~a() { system.Output("destructor called\n"); }
};
ref int global_ref;
void leak(void)
{
int i;
a local_a;
global_ref <- i;
// the destructor for local_a is never called, because it lives in the
// same page as i, which is referenced by a global
}
Calling Convention
------------------
+26 -28
View File
@@ -222,11 +222,6 @@ static struct string *stack_peek_string(int n)
return heap[stack_peek(n).i].s;
}
static struct string *stack_pop_string(void)
{
return heap[stack[--stack_ptr].i].s;
}
/*
* System 4 calling convention:
* - caller pushes arguments, in order
@@ -352,7 +347,8 @@ static int struct_copy(int no, int src_slot)
}
static struct string EMPTY_STRING = {
.literal = true,
.cow = true,
.ref = 1,
.size = 0,
.text = ""
};
@@ -366,7 +362,7 @@ static void create_struct(int no, union vm_value *var)
switch (s->members[i].data_type) {
case AIN_STRING:
memb = heap_alloc_slot(VM_STRING);
heap[memb].s = &EMPTY_STRING;
heap[memb].s = string_ref(&EMPTY_STRING);
heap[slot].page[i].i = memb;
break;
case AIN_STRUCT:
@@ -404,13 +400,6 @@ static void execute_instruction(int16_t opcode)
case F_PUSH:
stack_push(get_argument_float(0));
break;
case S_PUSH:
stack_push_string(ain->strings[get_argument(0)]);
break;
case S_POP:
index = stack_pop().i;
heap_unref(index);
break;
case REF:
// Dereference a reference to a value.
index = stack_pop_var()[0].i;
@@ -423,11 +412,6 @@ static void execute_instruction(int16_t opcode)
stack_push(ref[0].i);
stack_push(ref[1].i);
break;
case S_REF:
// Dereference a reference to a string
index = stack_pop_var()->i;
stack_push_string(string_dup(heap[index].s));
break;
case DUP:
// A -> AA
stack_push(stack_peek(0).i);
@@ -505,7 +489,7 @@ static void execute_instruction(int16_t opcode)
create_struct(get_argument(1), local_ptr(get_argument(0)));
break;
//
// --- Function Calls ---
// --- Control Flow ---
//
case CALLFUNC:
function_call(get_argument(0), instr_ptr + instruction_width(CALLFUNC));
@@ -519,9 +503,6 @@ static void execute_instruction(int16_t opcode)
case CALLSYS:
system_call(get_argument(0));
break;
//
// --- Control Flow ---
//
case JUMP: // ADDR
instr_ptr = get_argument(0);
break;
@@ -726,29 +707,42 @@ static void execute_instruction(int16_t opcode)
//
// --- Strings ---
//
case S_PUSH:
stack_push_string(string_ref(ain->strings[get_argument(0)]));
break;
case S_POP:
index = stack_pop().i;
heap_unref(index);
break;
case S_REF:
// Dereference a reference to a string
index = stack_pop_var()->i;
stack_push_string(string_ref(heap[index].s));
break;
case S_ASSIGN: // A = B
b = stack_peek(0).i;
a = stack_peek(1).i;
if (heap[a].s) {
free_string(heap[a].s);
}
heap[a].s = string_dup(heap[b].s);
heap[a].s = string_ref(heap[b].s);
// remove A from the stack, but leave B
stack_set(1, b);
stack_pop();
break;
case S_PLUSA2:
b = stack_peek(0).i;
a = stack_peek(1).i;
b = stack_peek(0).i;
string_append(&heap[a].s, heap[b].s);
heap_unref(b);
stack_pop();
stack_pop();
stack_push_string(string_dup(heap[a].s));
stack_push_string(string_ref(heap[a].s));
break;
case S_ADD:
b = stack_pop().i;
a = stack_pop().i;
// TODO: can use string_append here?
stack_push_string(string_concatenate(heap[a].s, heap[b].s));
heap_unref(a);
heap_unref(b);
@@ -822,13 +816,15 @@ static void execute_instruction(int16_t opcode)
break;
//case S_POPBACK: // ???
case S_POPBACK2:
string_pop_back(stack_pop_string());
index = stack_pop().i;
string_pop_back(&heap[index].s);
break;
//case S_ERASE: // ???
case S_ERASE2:
b = stack_pop().i; // ???
a = stack_pop().i; // index
string_erase(stack_pop_string(), a);
index = stack_pop().i;
string_erase(&heap[index].s, a);
break;
case I_STRING:
stack_push_string(integer_to_string(stack_pop().i));
@@ -872,6 +868,7 @@ static void vm_execute(void)
}
}
void vm_execute_ain(struct ain *program)
{
// initialize VM state
@@ -922,3 +919,4 @@ void vm_execute_ain(struct ain *program)
vm_call(ain->main, -1);
}
+18 -10
View File
@@ -18,25 +18,33 @@
#define SYSTEM4_STRING_H
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
struct string {
int size;
bool literal;
unsigned int ref : 24;
unsigned int cow : 1;
char text[];
};
struct string *make_string(const char *str, unsigned int len);
void free_string(struct string *str);
// constructors
struct string *make_string(const char *str, unsigned int len);
struct string *string_ref(struct string *s);
struct string *string_dup(const struct string *in);
void string_append(struct string **a, const struct string *b);
struct string *string_concatenate(const struct string *a, const struct string *b);
struct string *string_copy(const struct string *s, int index, int len);
int string_find(const struct string *haystack, const struct string *needle);
void string_push_back(struct string **s, int c);
void string_pop_back(struct string *s);
void string_erase(struct string *s, int index);
struct string *integer_to_string(int n);
struct string *float_to_string(float f, int precision);
struct string *string_concatenate(const struct string *a, const struct string *b);
struct string *string_copy(const struct string *s, int index, int len);
// mutators
void string_append(struct string **a, const struct string *b);
void string_push_back(struct string **s, int c);
void string_pop_back(struct string **s);
void string_erase(struct string **s, int index);
// queries
int string_find(const struct string *haystack, const struct string *needle);
#endif