Refactor pointer arithmetic

This commit is contained in:
Curle 2020-09-14 02:05:24 +01:00
parent 264c50509e
commit a0d2575a83
Signed by: TheCurle
GPG Key ID: 2F2E62F0DA69A5AE
7 changed files with 294 additions and 168 deletions

View File

@ -16,6 +16,7 @@
extern_ struct SymbolTable Symbols[SYMBOLS]; extern_ struct SymbolTable Symbols[SYMBOLS];
extern_ int TypeSizes[9];
extern_ char* TypeNames[9]; extern_ char* TypeNames[9];
extern_ char* TokenStrings[]; extern_ char* TokenStrings[];

View File

@ -107,6 +107,7 @@ enum SyntaxOps {
LV_IDENT, // Write an identifier in the form of an l-value. LV_IDENT, // Write an identifier in the form of an l-value.
OP_WIDEN, // Something contains a type that needs to be casted up OP_WIDEN, // Something contains a type that needs to be casted up
OP_SCALE, // We have a pointer that needs to be scaled!
OP_CALL, // Call a function OP_CALL, // Call a function
OP_RET, // Return from a function OP_RET, // Return from a function
@ -128,6 +129,7 @@ struct ASTNode {
struct ASTNode* Middle; struct ASTNode* Middle;
struct ASTNode* Right; struct ASTNode* Right;
union { union {
int Size; // OP_SCALE's linear representation
int IntValue; // TERM_INTLIT's Value int IntValue; // TERM_INTLIT's Value
int ID; // LV_IDENT's Symbols[] index. int ID; // LV_IDENT's Symbols[] index.
} Value; } Value;
@ -190,14 +192,21 @@ enum StructureType {
int Tokenise(struct Token* Token); int Tokenise(struct Token* Token);
int TypesCompatible(int* Left, int* Right, int STRICT);
void VerifyToken(int Type, char* TokenExpected); void VerifyToken(int Type, char* TokenExpected);
void RejectToken(struct Token* Token); void RejectToken(struct Token* Token);
static int ReadIdentifier(int Char, char* Buffer, int Limit); static int ReadIdentifier(int Char, char* Buffer, int Limit);
static int ReadKeyword(char* Str); static int ReadKeyword(char* Str);
/* * * * * * * * * * * * * * * * * * * *
* * * * * T Y P E S * * * * * *
* * * * * * * * * * * * * * * * * * * */
struct ASTNode* MutateType(struct ASTNode* Tree, int RightType, int Operation);
int TypeIsInt(int Type);
int TypeIsPtr(int Type);
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * S Y N T A X T R E E * * * * * * * * * * * * S Y N T A X T R E E * * * * * *
@ -318,6 +327,8 @@ int NewLabel(void);
void AsJmp(int Label); void AsJmp(int Label);
void AsLabel(int Label); void AsLabel(int Label);
int AsShl(int Register, int Val);
int AsReturn(int Register, int FuncID); int AsReturn(int Register, int FuncID);
int AsCall(int Register, int FuncID); int AsCall(int Register, int FuncID);

View File

@ -83,6 +83,18 @@ int AssembleTree(struct ASTNode* Node, int Register, int ParentOp) {
case OP_DIVIDE: case OP_DIVIDE:
return AsDiv(LeftVal, RightVal); return AsDiv(LeftVal, RightVal);
case OP_SCALE:
// We can (ab)use the powers of 2 to do
// efficient scaling with bitshifting.
switch(Node->Value.Size) {
case 2: return AsShl(LeftVal, 1);
case 4: return AsShl(LeftVal, 2);
case 8: return AsShl(LeftVal, 3);
default:
RightVal = AsLoad(Node->Value.Size);
return AsMul(LeftVal, RightVal);
}
case OP_ADDRESS: case OP_ADDRESS:
return AsAddr(Node->Value.ID); return AsAddr(Node->Value.ID);
@ -344,6 +356,12 @@ int AsDiv(int Left, int Right) {
return Left; return Left;
} }
int AsShl(int Register, int Val) {
printf("\tShifting %s to the left by %d bits.\n", Registers[Register], Val);
fprintf(OutputFile, "\tsalq\t$%d, %s\n", Val, Registers[Register]);
return Register;
}
int AsLdVar(int ID) { int AsLdVar(int ID) {
int Reg = RetrieveRegister(); int Reg = RetrieveRegister();

View File

@ -9,6 +9,9 @@
#undef extern_ #undef extern_
#include <errno.h> #include <errno.h>
int TypeSizes[9] = { 0, 1, 4, 8, 0, 8, 8, 8, 8}; // in BYTES
char* TokenStrings[] = { "+", "-", "*", "/", "int" }; char* TokenStrings[] = { "+", "-", "*", "/", "int" };
char* TokenNames[] = { char* TokenNames[] = {
"End of file", "End of file",

View File

@ -144,35 +144,11 @@ struct ASTNode* ParsePrimary(void) {
} }
struct ASTNode* ParseNewASTNode(void) {
//fprintf(stdout, "New node requested.");
struct ASTNode* LeftNode, *RightNode;
int NodeType;
LeftNode = ParsePrimary(); // Fetches next token!
// If there's just a number, then this is the AST Node.
// Return, as the root of the tree is the end of the tree.
if(CurrentToken.type == LI_EOF)
return(LeftNode);
NodeType = ParseTokenToOperation(CurrentToken.type);
Tokenise(&CurrentToken);
RightNode = ParseNewASTNode();
return ConstructASTNode(NodeType, LeftNode->ExprType, LeftNode, NULL, RightNode, 0);
}
struct ASTNode* ParsePrecedenceASTNode(int PreviousTokenPrecedence) { struct ASTNode* ParsePrecedenceASTNode(int PreviousTokenPrecedence) {
struct ASTNode* LeftNode, *RightNode; struct ASTNode* LeftNode, *RightNode;
struct ASTNode* LeftTemp, *RightTemp;
int LeftType, RightType; int LeftType, RightType;
int NodeType; int NodeType, OpType;
LeftNode = PrefixStatement(); LeftNode = PrefixStatement();
@ -190,8 +166,45 @@ struct ASTNode* ParsePrecedenceASTNode(int PreviousTokenPrecedence) {
LeftType = LeftNode->ExprType; LeftType = LeftNode->ExprType;
RightType = RightNode->ExprType; RightType = RightNode->ExprType;
if(!TypesCompatible(&LeftType, &RightType, 0)) /**
Die("Assignment between incompatible types"); * While parsing this node, we may need to widen some types.
* This requires a few functions and checks.
*/
OpType = ParseTokenToOperation(NodeType);
LeftTemp = MutateType(LeftNode, RightType, OpType);
RightTemp = MutateType(RightNode, LeftType, OpType);
/**
* If both are null, the types are incompatible.
*/
if(LeftTemp == NULL && RightTemp == NULL)
Die("Incompatible types in parsing nodes");
/**
* If the left was valid, or valid for
* expansion, then it will be non-null.
*
* If it was valid, then this will be
* equivalent to LeftNode = LeftNode
*/
if(LeftTemp)
LeftNode = LeftTemp;
/**
* Same here, but there is a higher chance
* for the right node to be incompatible due
* to the nature of widening types.
*/
if(RightTemp)
RightNode = RightTemp;
/**
* Checks over, back to normal parsing.
*/
if(LeftType) if(LeftType)
LeftNode = ConstructASTBranch(LeftType, RightNode->ExprType, LeftNode, 0); LeftNode = ConstructASTBranch(LeftType, RightNode->ExprType, LeftNode, 0);
@ -439,29 +452,3 @@ void ParseGlobals() {
} }
/* void ParseStatements() {
while(1) {
switch(CurrentToken.type) {
case KW_PRINT:
PrintStatement();
break;
case TY_INT:
BeginVariableDeclaration();
break;
case TY_IDENTIFIER:
AssignVariable();
break;
case LI_EOF:
return;
default:
DieDecimal("Syntax error; Token", CurrentToken.type);
}
}
} */

View File

@ -8,110 +8,6 @@
#include <Data.h> #include <Data.h>
/*
* Turn a token type into its appropriate
* primitive type.
*
* This is where we do redirections like:
* short -> s16
* long -> s64
* int -> s32
* char -> u8
*
*/
static int TypeSize[9] = { 0, 1, 4, 8, 0, 8, 8, 8, 8}; // in BYTES
int PrimitiveSize(int Type) {
if(Type < RET_NONE || Type > PTR_VOID)
DieDecimal("Checking size of bad data type", Type);
return TypeSize[Type];
}
/*
* Given two types, determine if they are compatible.
*
* Depending on the value of STRICT, it will try to
* fit the right value into the left value.
*
* This is valid, for ie. a char into an int, as int is larger than char.
* This is called widening the char.
*
* If STRICT is set, it will only allow widening the left to the right.
* This means you cannot `char a; int b; b = 15000; a = b;`
* As this would shrink the int and lose resolution.
*
* NOTE: THIS IS NOT THE DEFAULT BEHAVIOUR
* By default, you CAN shrink an int into a char, a la shifting down.
*
*
*/
int TypesCompatible(int* Left, int* Right, int STRICT) {
int LeftSize, RightSize;
// Same types are compatible. No shrinking required
if(*Left == *Right) {
*Left = *Right = 0;
return 1;
}
LeftSize = PrimitiveSize(*Left);
RightSize = PrimitiveSize(*Right);
// Types of size 0 are incompatible
if((LeftSize == 0) || (RightSize == 0))
return 0;
/* char x;
* int y;
* y = 15;
*
* x = y;
* x needs to be widened, y copied in, then x shrunk back down
* AKA, the left must be widened.
*/
if(LeftSize < RightSize) {
*Left = OP_WIDEN;
*Right = 0;
return 1;
}
/*
* char x;
* int y;
*
* x = 15;
*
* y = x;
* x must be widened to fit into y.
* if STRICT mode, this is not allowed.
* By default, this is valid.
*
*/
if(LeftSize > RightSize) {
if(STRICT)
return 0; // Not compatible if STRICT
*Left = 0;
*Right = OP_WIDEN;
return 1; // Compatible by default
}
/*
* Any other cases left, by default, are compatible.
*
*/
*Left = *Right = 0;
return 1;
}
/* /*
* Handles the declaration of a type of a variable. * Handles the declaration of a type of a variable.
@ -208,7 +104,8 @@ struct ASTNode* ReturnStatement() {
ReturnType = Tree->ExprType; ReturnType = Tree->ExprType;
FunctionType = Symbols[CurrentFunction].Type; FunctionType = Symbols[CurrentFunction].Type;
if(!TypesCompatible(&ReturnType, &FunctionType, 0)) Tree = MutateType(Tree, FunctionType, 0);
if(!Tree)
Die("Returning a value of incorrect type for function"); Die("Returning a value of incorrect type for function");
@ -225,13 +122,18 @@ struct ASTNode* ReturnStatement() {
} }
/* /*
* Handles the assignment of variables. * Handles Identifiers.
* *
* You can assign variables with an assignment, * This is called for any of:
* - Calling a function
* - Assigning an lvalue variable
* - Performing arithmetic on a variable
* - Performing arithmetic with the return values of function calls
*
* For the case where you're assigning an l-value;
* You can assign with another assignment,
* a statement, a function or a literal. * a statement, a function or a literal.
* *
* This means we need to do some recursive parsing.
*
*/ */
struct ASTNode* ParseIdentifier() { struct ASTNode* ParseIdentifier() {
@ -259,8 +161,9 @@ struct ASTNode* ParseIdentifier() {
LeftType = Left->ExprType; LeftType = Left->ExprType;
RightType = Right->ExprType; RightType = Right->ExprType;
if(!TypesCompatible(&LeftType, &RightType, 1)) Left = MutateType(Left, RightType, 0);
Die("Incompatible variable types"); if(!Left)
Die("Incompatible types in assignment");
if(LeftType) if(LeftType)
Left = ConstructASTBranch(LeftType, Right->ExprType, Left, 0); Left = ConstructASTBranch(LeftType, Right->ExprType, Left, 0);
@ -363,7 +266,8 @@ struct ASTNode* PrintStatement(void) {
LeftType = RET_INT; LeftType = RET_INT;
RightType = Tree->ExprType; RightType = Tree->ExprType;
if(!TypesCompatible(&LeftType, &RightType, 0)) Tree = MutateType(Tree, RightType, 0);
if(!Tree)
DieDecimal("Attempting to print an invalid type:", RightType); DieDecimal("Attempting to print an invalid type:", RightType);
if(RightType) if(RightType)

202
src/Types.c Normal file
View File

@ -0,0 +1,202 @@
/*************/
/*GEMWIRE */
/* ERYTHRO*/
/*************/
#include <Defs.h>
#include <Data.h>
/*
* Turn a token type into its appropriate
* primitive type.
*
* This is where we do redirections like:
* short -> s16
* long -> s64
* int -> s32
* char -> u8
*
*/
int PrimitiveSize(int Type) {
if(Type < RET_NONE || Type > PTR_VOID)
DieDecimal("Checking size of bad data type", Type);
return TypeSizes[Type];
}
/*
* Given two types, determine if they are compatible.
*
* Depending on the value of STRICT, it will try to
* fit the right value into the left value.
*
* This is valid, for ie. a char into an int, as int is larger than char.
* This is called widening the char.
*
* If STRICT is set, it will only allow widening the left to the right.
* This means you cannot `char a; int b; b = 15000; a = b;`
* As this would shrink the int and lose resolution.
*
* NOTE: THIS IS NOT THE DEFAULT BEHAVIOUR
* By default, you CAN shrink an int into a char, a la shifting down.
*
*
*/
int TypesCompatible(int* Left, int* Right, int STRICT) {
int LeftSize, RightSize;
// Same types are compatible. No shrinking required
if(*Left == *Right) {
*Left = *Right = 0;
return 1;
}
LeftSize = PrimitiveSize(*Left);
RightSize = PrimitiveSize(*Right);
// Types of size 0 are incompatible
if((LeftSize == 0) || (RightSize == 0))
return 0;
/* char x;
* int y;
* y = 15;
*
* x = y;
* x needs to be widened, y copied in, then x shrunk back down
* AKA, the left must be widened.
*/
if(LeftSize < RightSize) {
*Left = OP_WIDEN;
*Right = 0;
return 1;
}
/*
* char x;
* int y;
*
* x = 15;
*
* y = x;
* x must be widened to fit into y.
* if STRICT mode, this is not allowed.
* By default, this is valid.
*
*/
if(LeftSize > RightSize) {
if(STRICT)
return 0; // Not compatible if STRICT
*Left = 0;
*Right = OP_WIDEN;
return 1; // Compatible by default
}
/*
* Any other cases left, by default, are compatible.
*
*/
*Left = *Right = 0;
return 1;
}
/**
* Given an operation on two types, we need to be able to
* determine if the operation is valid for both types,
* as well as modify the types if the operation is
* theoretically valid but requires some changes.
*
* An example of the latter is assigning an int literal into
* a char, or squeezing down the int into the char type.
*
* If the operation is not valid, this will return NULL.
* If the operaton is valid without changes, this will return Tree.
* If the operation is valid with changes, this will perform
* the changes and return the new tree.
*
* This also serves to consolidate some of the gross widening
* code that TypesCompatible led us to.
*/
struct ASTNode* MutateType(struct ASTNode* Tree, int RightType, int Operation) {
int LeftType;
int LeftSize, RightSize;
LeftType = Tree->ExprType;
if(TypeIsInt(LeftType) && TypeIsInt(RightType)) {
// Short-circuit for valid types
if(LeftType == RightType) {
return Tree;
}
LeftSize = PrimitiveSize(LeftType);
RightSize = PrimitiveSize(RightType);
/**
* LeftSize > RightSize:
* char x = 15000;
*
* (The left branch of the tree contains the current AST)
*
*/
if(LeftSize > RightSize)
return NULL;
/**
* RightSize > LeftSize:
* char x = 5;
* int y = x;
*
* We have to widen x into an int in order for this to be compatible
* BUT it is possible!
*/
if(RightSize > LeftSize)
return ConstructASTBranch(OP_WIDEN, RightType, Tree, 0);
}
// Left branch pointers are compatible if we're not doing operations
if(TypeIsPtr(LeftType)) {
if(Operation == 0 && LeftType == RightType)
return Tree;
}
// Otherwise, we can perform some scaling for pointer addition & subtraction
if(Operation == OP_ADD || Operation == OP_SUBTRACT) {
/**
* Left int, right pointer:
* int x = 5;
* int* y;
*
* x = *(y + 1);
*/
if(TypeIsInt(LeftType) && TypeIsPtr(RightType)) {
RightSize = PrimitiveSize(ValueAt(RightType));
if(RightSize > 1)
return ConstructASTBranch(OP_SCALE, RightType, Tree, RightSize);
}
}
// If all else fails, we've constructed a combination of types that are not compatible.
// ie. left or right is a void.
// You cannot do pointer arithmetic on void type.
return NULL;
}