Fixed stack overflow caused by assignments, read more

Variable declaration was also causing this issue.

It was caused by a value being left on the stack after these statements.
It wasn't a quick fix, as chained assignments depended on it. Now, the
assignment opcode has a configuration option, indicating if the last
value should be left on the stack or not.

This also means the benchmark in 'scripts/benchpress.toy' will no longer
cause a stack overflow.

Fixed #171
This commit is contained in:
2025-01-11 15:07:29 +11:00
parent ce03a342c9
commit 9141102f2e
2 changed files with 102 additions and 47 deletions

View File

@@ -69,6 +69,25 @@ static void emitFloat(unsigned char** handle, unsigned int* capacity, unsigned i
emitByte(handle, capacity, count, *(ptr++)); emitByte(handle, capacity, count, *(ptr++));
} }
static bool checkForChaining(Toy_Ast* ptr) {
//BUGFIX
if (ptr == NULL) {
return false;
}
if (ptr->type == TOY_AST_VAR_ASSIGN) {
return true;
}
if (ptr->type == TOY_AST_UNARY) {
if (ptr->unary.flag >= TOY_AST_FLAG_PREFIX_INCREMENT && ptr->unary.flag <= TOY_AST_FLAG_POSTFIX_DECREMENT) {
return true;
}
}
return false;
}
//write instructions based on the AST types //write instructions based on the AST types
#define EMIT_BYTE(rt, part, byte) \ #define EMIT_BYTE(rt, part, byte) \
emitByte((&((*rt)->part)), &((*rt)->part##Capacity), &((*rt)->part##Count), byte) emitByte((&((*rt)->part)), &((*rt)->part##Capacity), &((*rt)->part##Count), byte)
@@ -127,6 +146,7 @@ static unsigned int emitString(Toy_Routine** rt, Toy_String* str) {
} }
static unsigned int writeRoutineCode(Toy_Routine** rt, Toy_Ast* ast); //forward declare for recursion static unsigned int writeRoutineCode(Toy_Routine** rt, Toy_Ast* ast); //forward declare for recursion
static unsigned int writeInstructionAssign(Toy_Routine** rt, Toy_AstVarAssign ast, bool chainedAssignment); //forward declare for chaining of var declarations
static unsigned int writeInstructionValue(Toy_Routine** rt, Toy_AstValue ast) { static unsigned int writeInstructionValue(Toy_Routine** rt, Toy_AstValue ast) {
EMIT_BYTE(rt, code, TOY_OPCODE_READ); EMIT_BYTE(rt, code, TOY_OPCODE_READ);
@@ -217,7 +237,7 @@ static unsigned int writeInstructionUnary(Toy_Routine** rt, Toy_AstUnary ast) {
//add (or subtract) the two values, then assign (pops the second duplicate, and leaves value on the stack) //add (or subtract) the two values, then assign (pops the second duplicate, and leaves value on the stack)
EMIT_BYTE(rt, code, ast.flag == TOY_AST_FLAG_PREFIX_INCREMENT ? TOY_OPCODE_ADD : TOY_OPCODE_SUBTRACT); EMIT_BYTE(rt, code, ast.flag == TOY_AST_FLAG_PREFIX_INCREMENT ? TOY_OPCODE_ADD : TOY_OPCODE_SUBTRACT);
EMIT_BYTE(rt, code,TOY_OPCODE_ASSIGN); //squeezed EMIT_BYTE(rt, code,TOY_OPCODE_ASSIGN); //squeezed
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code,1);
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code,0);
//leaves one value on the stack //leaves one value on the stack
@@ -265,18 +285,12 @@ static unsigned int writeInstructionUnary(Toy_Routine** rt, Toy_AstUnary ast) {
EMIT_INT(rt, code, 1); EMIT_INT(rt, code, 1);
//add (or subtract) the two values, then assign (pops the second duplicate, and leaves value on the stack) //add (or subtract) the two values, then assign (pops the second duplicate)
EMIT_BYTE(rt, code, ast.flag == TOY_AST_FLAG_POSTFIX_INCREMENT ? TOY_OPCODE_ADD : TOY_OPCODE_SUBTRACT); EMIT_BYTE(rt, code, ast.flag == TOY_AST_FLAG_POSTFIX_INCREMENT ? TOY_OPCODE_ADD : TOY_OPCODE_SUBTRACT);
EMIT_BYTE(rt, code,TOY_OPCODE_ASSIGN); //squeezed EMIT_BYTE(rt, code,TOY_OPCODE_ASSIGN); //squeezed
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code,0);
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code,0);
//remove the lingering value (yep, this is UGLY)
EMIT_BYTE(rt, code, TOY_OPCODE_ELIMINATE);
EMIT_BYTE(rt, code,0);
EMIT_BYTE(rt, code,0);
EMIT_BYTE(rt, code,0);
//leaves one value on the stack //leaves one value on the stack
result = 1; result = 1;
} }
@@ -676,8 +690,13 @@ static unsigned int writeInstructionPrint(Toy_Routine** rt, Toy_AstPrint ast) {
} }
static unsigned int writeInstructionVarDeclare(Toy_Routine** rt, Toy_AstVarDeclare ast) { static unsigned int writeInstructionVarDeclare(Toy_Routine** rt, Toy_AstVarDeclare ast) {
//initial value //if we're dealing with chained assignments, hijack the next assignment with 'chainedAssignment' set to true
writeRoutineCode(rt, ast.expr); if (checkForChaining(ast.expr)) {
writeInstructionAssign(rt, ast.expr->varAssign, true);
}
else {
writeRoutineCode(rt, ast.expr); //default value
}
//delcare with the given name string //delcare with the given name string
EMIT_BYTE(rt, code, TOY_OPCODE_DECLARE); EMIT_BYTE(rt, code, TOY_OPCODE_DECLARE);
@@ -690,25 +709,9 @@ static unsigned int writeInstructionVarDeclare(Toy_Routine** rt, Toy_AstVarDecla
return 0; return 0;
} }
static unsigned int writeInstructionAssign(Toy_Routine** rt, Toy_AstVarAssign ast) { static unsigned int writeInstructionAssign(Toy_Routine** rt, Toy_AstVarAssign ast, bool chainedAssignment) {
unsigned int result = 0; unsigned int result = 0;
//don't treat these as valid values
switch (ast.expr->type) {
case TOY_AST_BLOCK:
case TOY_AST_AGGREGATE:
case TOY_AST_ASSERT:
case TOY_AST_PRINT:
case TOY_AST_VAR_DECLARE:
//emit a compiler error, set the panic flag and skip out
fprintf(stderr, TOY_CC_ERROR "COMPILER ERROR: Invalid AST type found: Malformed assignment value\n" TOY_CC_RESET);
(*rt)->panic = true;
return 0;
default:
break;
}
//target is a name string //target is a name string
if (ast.target->type == TOY_AST_VALUE && TOY_VALUE_IS_STRING(ast.target->value.value) && TOY_VALUE_AS_STRING(ast.target->value.value)->info.type == TOY_STRING_NAME) { if (ast.target->type == TOY_AST_VALUE && TOY_VALUE_IS_STRING(ast.target->value.value) && TOY_VALUE_AS_STRING(ast.target->value.value)->info.type == TOY_STRING_NAME) {
//name string //name string
@@ -727,14 +730,21 @@ static unsigned int writeInstructionAssign(Toy_Routine** rt, Toy_AstVarAssign as
else if (ast.target->type == TOY_AST_AGGREGATE && ast.target->aggregate.flag == TOY_AST_FLAG_INDEX) { else if (ast.target->type == TOY_AST_AGGREGATE && ast.target->aggregate.flag == TOY_AST_FLAG_INDEX) {
writeRoutineCode(rt, ast.target->aggregate.left); //any deeper indexing will just work, using reference values writeRoutineCode(rt, ast.target->aggregate.left); //any deeper indexing will just work, using reference values
writeRoutineCode(rt, ast.target->aggregate.right); //key writeRoutineCode(rt, ast.target->aggregate.right); //key
writeRoutineCode(rt, ast.expr); //value
//if we're dealing with chained assignments, hijack the next assignment with 'chainedAssignment' set to true
if (checkForChaining(ast.expr)) {
result += writeInstructionAssign(rt, ast.expr->varAssign, true);
}
else {
result += writeRoutineCode(rt, ast.expr); //default value
}
EMIT_BYTE(rt, code, TOY_OPCODE_ASSIGN_COMPOUND); //uses the top three values on the stack EMIT_BYTE(rt, code, TOY_OPCODE_ASSIGN_COMPOUND); //uses the top three values on the stack
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code, chainedAssignment);
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code,0);
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code,0);
return 0; return result + (chainedAssignment ? 1 : 0);
} }
else { else {
@@ -746,10 +756,16 @@ static unsigned int writeInstructionAssign(Toy_Routine** rt, Toy_AstVarAssign as
//determine RHS, include duplication if needed //determine RHS, include duplication if needed
if (ast.flag == TOY_AST_FLAG_ASSIGN) { if (ast.flag == TOY_AST_FLAG_ASSIGN) {
result += writeRoutineCode(rt, ast.expr); //if we're dealing with chained assignments, hijack the next assignment with 'chainedAssignment' set to true
if (checkForChaining(ast.expr)) {
result += writeInstructionAssign(rt, ast.expr->varAssign, true);
}
else {
result += writeRoutineCode(rt, ast.expr); //default value
}
EMIT_BYTE(rt, code, TOY_OPCODE_ASSIGN); EMIT_BYTE(rt, code, TOY_OPCODE_ASSIGN);
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code, chainedAssignment);
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code,0);
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code,0);
} }
@@ -759,11 +775,17 @@ static unsigned int writeInstructionAssign(Toy_Routine** rt, Toy_AstVarAssign as
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code,0);
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code,0);
result += writeRoutineCode(rt, ast.expr); //if we're dealing with chained assignments, hijack the next assignment with 'chainedAssignment' set to true
if (checkForChaining(ast.expr)) {
result += writeInstructionAssign(rt, ast.expr->varAssign, true);
}
else {
result += writeRoutineCode(rt, ast.expr); //default value
}
EMIT_BYTE(rt, code,TOY_OPCODE_ADD); EMIT_BYTE(rt, code,TOY_OPCODE_ADD);
EMIT_BYTE(rt, code,TOY_OPCODE_ASSIGN); //squeezed EMIT_BYTE(rt, code,TOY_OPCODE_ASSIGN); //squeezed
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code, chainedAssignment);
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code,0);
} }
else if (ast.flag == TOY_AST_FLAG_SUBTRACT_ASSIGN) { else if (ast.flag == TOY_AST_FLAG_SUBTRACT_ASSIGN) {
@@ -772,11 +794,17 @@ static unsigned int writeInstructionAssign(Toy_Routine** rt, Toy_AstVarAssign as
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code,0);
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code,0);
result += writeRoutineCode(rt, ast.expr); //if we're dealing with chained assignments, hijack the next assignment with 'chainedAssignment' set to true
if (checkForChaining(ast.expr)) {
result += writeInstructionAssign(rt, ast.expr->varAssign, true);
}
else {
result += writeRoutineCode(rt, ast.expr); //default value
}
EMIT_BYTE(rt, code,TOY_OPCODE_SUBTRACT); EMIT_BYTE(rt, code,TOY_OPCODE_SUBTRACT);
EMIT_BYTE(rt, code,TOY_OPCODE_ASSIGN); //squeezed EMIT_BYTE(rt, code,TOY_OPCODE_ASSIGN); //squeezed
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code, chainedAssignment);
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code,0);
} }
else if (ast.flag == TOY_AST_FLAG_MULTIPLY_ASSIGN) { else if (ast.flag == TOY_AST_FLAG_MULTIPLY_ASSIGN) {
@@ -785,11 +813,17 @@ static unsigned int writeInstructionAssign(Toy_Routine** rt, Toy_AstVarAssign as
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code,0);
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code,0);
result += writeRoutineCode(rt, ast.expr); //if we're dealing with chained assignments, hijack the next assignment with 'chainedAssignment' set to true
if (checkForChaining(ast.expr)) {
result += writeInstructionAssign(rt, ast.expr->varAssign, true);
}
else {
result += writeRoutineCode(rt, ast.expr); //default value
}
EMIT_BYTE(rt, code,TOY_OPCODE_MULTIPLY); EMIT_BYTE(rt, code,TOY_OPCODE_MULTIPLY);
EMIT_BYTE(rt, code,TOY_OPCODE_ASSIGN); //squeezed EMIT_BYTE(rt, code,TOY_OPCODE_ASSIGN); //squeezed
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code, chainedAssignment);
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code,0);
} }
else if (ast.flag == TOY_AST_FLAG_DIVIDE_ASSIGN) { else if (ast.flag == TOY_AST_FLAG_DIVIDE_ASSIGN) {
@@ -798,11 +832,17 @@ static unsigned int writeInstructionAssign(Toy_Routine** rt, Toy_AstVarAssign as
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code,0);
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code,0);
result += writeRoutineCode(rt, ast.expr); //if we're dealing with chained assignments, hijack the next assignment with 'chainedAssignment' set to true
if (checkForChaining(ast.expr)) {
result += writeInstructionAssign(rt, ast.expr->varAssign, true);
}
else {
result += writeRoutineCode(rt, ast.expr); //default value
}
EMIT_BYTE(rt, code,TOY_OPCODE_DIVIDE); EMIT_BYTE(rt, code,TOY_OPCODE_DIVIDE);
EMIT_BYTE(rt, code,TOY_OPCODE_ASSIGN); //squeezed EMIT_BYTE(rt, code,TOY_OPCODE_ASSIGN); //squeezed
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code, chainedAssignment);
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code,0);
} }
else if (ast.flag == TOY_AST_FLAG_MODULO_ASSIGN) { else if (ast.flag == TOY_AST_FLAG_MODULO_ASSIGN) {
@@ -811,11 +851,17 @@ static unsigned int writeInstructionAssign(Toy_Routine** rt, Toy_AstVarAssign as
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code,0);
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code,0);
result += writeRoutineCode(rt, ast.expr); //if we're dealing with chained assignments, hijack the next assignment with 'chainedAssignment' set to true
if (checkForChaining(ast.expr)) {
result += writeInstructionAssign(rt, ast.expr->varAssign, true);
}
else {
result += writeRoutineCode(rt, ast.expr); //default value
}
EMIT_BYTE(rt, code,TOY_OPCODE_MODULO); EMIT_BYTE(rt, code,TOY_OPCODE_MODULO);
EMIT_BYTE(rt, code,TOY_OPCODE_ASSIGN); //squeezed EMIT_BYTE(rt, code,TOY_OPCODE_ASSIGN); //squeezed
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code, chainedAssignment);
EMIT_BYTE(rt, code,0); EMIT_BYTE(rt, code,0);
} }
@@ -824,7 +870,7 @@ static unsigned int writeInstructionAssign(Toy_Routine** rt, Toy_AstVarAssign as
exit(-1); exit(-1);
} }
return result; return result + (chainedAssignment ? 1 : 0);
} }
static unsigned int writeInstructionAccess(Toy_Routine** rt, Toy_AstVarAccess ast) { static unsigned int writeInstructionAccess(Toy_Routine** rt, Toy_AstVarAccess ast) {
@@ -956,7 +1002,7 @@ static unsigned int writeRoutineCode(Toy_Routine** rt, Toy_Ast* ast) {
break; break;
case TOY_AST_VAR_ASSIGN: case TOY_AST_VAR_ASSIGN:
result += writeInstructionAssign(rt, ast->varAssign); result += writeInstructionAssign(rt, ast->varAssign, false);
break; break;
case TOY_AST_VAR_ACCESS: case TOY_AST_VAR_ACCESS:

View File

@@ -227,7 +227,10 @@ static void processAssign(Toy_VM* vm) {
Toy_assignScope(vm->scope, TOY_VALUE_AS_STRING(name), value); //scope now owns the value, doesn't need to be freed Toy_assignScope(vm->scope, TOY_VALUE_AS_STRING(name), value); //scope now owns the value, doesn't need to be freed
//in case of chaining, leave a copy on the stack //in case of chaining, leave a copy on the stack
Toy_pushStack(&vm->stack, Toy_copyValue(value)); bool chainedAssignment = READ_BYTE(vm);
if (chainedAssignment) {
Toy_pushStack(&vm->stack, Toy_copyValue(value));
}
//cleanup //cleanup
Toy_freeValue(name); Toy_freeValue(name);
@@ -275,7 +278,10 @@ static void processAssignCompound(Toy_VM* vm) {
array->data[index] = Toy_copyValue(Toy_unwrapValue(value)); array->data[index] = Toy_copyValue(Toy_unwrapValue(value));
//in case of chaining, leave a copy on the stack //in case of chaining, leave a copy on the stack
Toy_pushStack(&vm->stack, Toy_copyValue(value)); bool chainedAssignment = READ_BYTE(vm);
if (chainedAssignment) {
Toy_pushStack(&vm->stack, Toy_copyValue(value));
}
//cleanup //cleanup
Toy_freeValue(value); Toy_freeValue(value);
@@ -288,7 +294,10 @@ static void processAssignCompound(Toy_VM* vm) {
Toy_insertTable(&table, Toy_copyValue(Toy_unwrapValue(key)), Toy_copyValue(Toy_unwrapValue(value))); Toy_insertTable(&table, Toy_copyValue(Toy_unwrapValue(key)), Toy_copyValue(Toy_unwrapValue(value)));
//in case of chaining, leave a copy on the stack //in case of chaining, leave a copy on the stack
Toy_pushStack(&vm->stack, Toy_copyValue(value)); bool chainedAssignment = READ_BYTE(vm);
if (chainedAssignment) {
Toy_pushStack(&vm->stack, Toy_copyValue(value));
}
//cleanup //cleanup
Toy_freeValue(value); Toy_freeValue(value);