From a0d2575a83ff216faefd2e5b1859c5f0c66b2092 Mon Sep 17 00:00:00 2001 From: Curle Date: Mon, 14 Sep 2020 02:05:24 +0100 Subject: [PATCH] Refactor pointer arithmetic --- include/Data.h | 1 + include/Defs.h | 15 +++- src/Assembler.c | 18 +++++ src/Main.c | 3 + src/Parser.c | 95 ++++++++++------------ src/Statements.c | 128 ++++-------------------------- src/Types.c | 202 +++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 294 insertions(+), 168 deletions(-) create mode 100644 src/Types.c diff --git a/include/Data.h b/include/Data.h index c28aa5d..9ccbef3 100644 --- a/include/Data.h +++ b/include/Data.h @@ -16,6 +16,7 @@ extern_ struct SymbolTable Symbols[SYMBOLS]; +extern_ int TypeSizes[9]; extern_ char* TypeNames[9]; extern_ char* TokenStrings[]; diff --git a/include/Defs.h b/include/Defs.h index b63797d..8a45bbe 100644 --- a/include/Defs.h +++ b/include/Defs.h @@ -107,6 +107,7 @@ enum SyntaxOps { 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_SCALE, // We have a pointer that needs to be scaled! OP_CALL, // Call a function OP_RET, // Return from a function @@ -128,6 +129,7 @@ struct ASTNode { struct ASTNode* Middle; struct ASTNode* Right; union { + int Size; // OP_SCALE's linear representation int IntValue; // TERM_INTLIT's Value int ID; // LV_IDENT's Symbols[] index. } Value; @@ -190,14 +192,21 @@ enum StructureType { int Tokenise(struct Token* Token); -int TypesCompatible(int* Left, int* Right, int STRICT); - void VerifyToken(int Type, char* TokenExpected); void RejectToken(struct Token* Token); static int ReadIdentifier(int Char, char* Buffer, int Limit); 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 * * * * * * @@ -318,6 +327,8 @@ int NewLabel(void); void AsJmp(int Label); void AsLabel(int Label); +int AsShl(int Register, int Val); + int AsReturn(int Register, int FuncID); int AsCall(int Register, int FuncID); diff --git a/src/Assembler.c b/src/Assembler.c index a84fa35..5ee92c3 100644 --- a/src/Assembler.c +++ b/src/Assembler.c @@ -83,6 +83,18 @@ int AssembleTree(struct ASTNode* Node, int Register, int ParentOp) { case OP_DIVIDE: 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: return AsAddr(Node->Value.ID); @@ -344,6 +356,12 @@ int AsDiv(int Left, int Right) { 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 Reg = RetrieveRegister(); diff --git a/src/Main.c b/src/Main.c index c62e5c8..db97ac8 100644 --- a/src/Main.c +++ b/src/Main.c @@ -9,6 +9,9 @@ #undef extern_ #include + +int TypeSizes[9] = { 0, 1, 4, 8, 0, 8, 8, 8, 8}; // in BYTES + char* TokenStrings[] = { "+", "-", "*", "/", "int" }; char* TokenNames[] = { "End of file", diff --git a/src/Parser.c b/src/Parser.c index 0d1b2bd..ffbd2a2 100644 --- a/src/Parser.c +++ b/src/Parser.c @@ -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* LeftNode, *RightNode; + struct ASTNode* LeftTemp, *RightTemp; int LeftType, RightType; - int NodeType; + int NodeType, OpType; LeftNode = PrefixStatement(); @@ -190,8 +166,45 @@ struct ASTNode* ParsePrecedenceASTNode(int PreviousTokenPrecedence) { LeftType = LeftNode->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) 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); - } - } -} */ - - diff --git a/src/Statements.c b/src/Statements.c index 6aa8bd3..dfaa7be 100644 --- a/src/Statements.c +++ b/src/Statements.c @@ -8,110 +8,6 @@ #include -/* - * 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. @@ -208,7 +104,8 @@ struct ASTNode* ReturnStatement() { ReturnType = Tree->ExprType; 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"); @@ -225,12 +122,17 @@ struct ASTNode* ReturnStatement() { } /* - * Handles the assignment of variables. + * Handles Identifiers. * - * You can assign variables with an assignment, - * a statement, a function or a literal. + * 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 * - * This means we need to do some recursive parsing. + * For the case where you're assigning an l-value; + * You can assign with another assignment, + * a statement, a function or a literal. * */ @@ -259,8 +161,9 @@ struct ASTNode* ParseIdentifier() { LeftType = Left->ExprType; RightType = Right->ExprType; - if(!TypesCompatible(&LeftType, &RightType, 1)) - Die("Incompatible variable types"); + Left = MutateType(Left, RightType, 0); + if(!Left) + Die("Incompatible types in assignment"); if(LeftType) Left = ConstructASTBranch(LeftType, Right->ExprType, Left, 0); @@ -363,7 +266,8 @@ struct ASTNode* PrintStatement(void) { LeftType = RET_INT; RightType = Tree->ExprType; - if(!TypesCompatible(&LeftType, &RightType, 0)) + Tree = MutateType(Tree, RightType, 0); + if(!Tree) DieDecimal("Attempting to print an invalid type:", RightType); if(RightType) diff --git a/src/Types.c b/src/Types.c new file mode 100644 index 0000000..2b592ce --- /dev/null +++ b/src/Types.c @@ -0,0 +1,202 @@ + +/*************/ +/*GEMWIRE */ +/* ERYTHRO*/ +/*************/ + +#include +#include + +/* + * 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; +}