From c9372aa2d06b0a57507abbb8c16d60f31802c087 Mon Sep 17 00:00:00 2001 From: ademyrodev Date: Sun, 18 Aug 2024 04:55:54 +0200 Subject: [PATCH 01/10] implement IS_NIL, IS_ZERO and IS_NEG_ONE instructions (can't test them yet because they will just get constant folded --- include/chunk.h | 3 + include/ir.h | 11 +++- src/compiler/compiler.c | 4 +- src/compiler/emit.c | 30 +++++++--- src/compiler/opt.c | 124 +++++++++++++++++++++++++++++++++------- src/ir/ir.c | 3 +- src/vm/debug.c | 9 +++ src/vm/vm.c | 17 ++++-- 8 files changed, 165 insertions(+), 36 deletions(-) diff --git a/include/chunk.h b/include/chunk.h index 2b7f270..bf4faf6 100644 --- a/include/chunk.h +++ b/include/chunk.h @@ -12,6 +12,9 @@ typedef enum { OP_NIL, OP_NEG, OP_NOT, + OP_IS_NIL, + OP_IS_ZERO, + OP_IS_NEG_ONE, OP_ADD, OP_SUB, OP_MUL, diff --git a/include/ir.h b/include/ir.h index 47b4ebb..bbb2f70 100644 --- a/include/ir.h +++ b/include/ir.h @@ -20,6 +20,14 @@ typedef enum { NODE_BINOP } NodeType; +typedef enum { + UNOP_NEG = 1 << 0, + UNOP_NOT = 1 << 1, + UNOP_IS_ZERO = 1 << 2, + UNOP_IS_NEG_ONE = 1 << 3, + UNOP_IS_NIL = 1 << 4 +} UnOpType; + typedef struct Node Node; typedef struct { @@ -39,6 +47,7 @@ typedef struct { typedef struct { Tok op; + UnOpType opType; Node *operand; } UnOp; @@ -67,7 +76,7 @@ Node *newInt(TypeTable *table, long value, Loc loc); Node *newFloat(TypeTable *table, double value, Loc loc); Node *newBool(TypeTable *table, bool value, Loc loc); Node *newNil(TypeTable *table, Loc loc); -Node *newUnOp(TypeTable *table, Tok op, Node *operand); +Node *newUnOp(TypeTable *table, Tok op, UnOpType type, Node *operand); Node *newBinOp(TypeTable *table, Node *left, Tok op, Node *right); void freeNode(Node *node); diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index 2946af2..2e731a8 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -353,7 +353,7 @@ static Node *unary(Ctx *ctx) { unaryNegationErr(ctx, op, tok, operand); } - return newUnOp(ctx->types, op, operand); + return newUnOp(ctx->types, op, UNOP_NEG, operand); } case TOK_NOT: { @@ -363,7 +363,7 @@ static Node *unary(Ctx *ctx) { unaryNegationErr(ctx, op, tok, operand); } - return newUnOp(ctx->types, op, operand); + return newUnOp(ctx->types, op, UNOP_NEG, operand); } default: diff --git a/src/compiler/emit.c b/src/compiler/emit.c index 7a5ae89..e1f4d07 100644 --- a/src/compiler/emit.c +++ b/src/compiler/emit.c @@ -59,19 +59,35 @@ static void emitBinOp(Ctx *ctx, BinOp binOp) { static void emitUnOp(Ctx *ctx, UnOp unOp) { emitNode(ctx, unOp.operand); - const Tok op = unOp.op; + Loc loc = unOp.op.loc; + const UnOpType op = unOp.opType; - switch (op.type) { - case TOK_MINUS: - emit(ctx, OP_NEG, op.loc); + switch (op) { + case UNOP_NEG: + emit(ctx, OP_NEG, loc); break; - case TOK_NOT: - emit(ctx, OP_NOT, op.loc); + case UNOP_NOT: + emit(ctx, OP_NOT, loc); break; default: - // will add more with time + // yeah this is pretty silly + if (op & UNOP_IS_NIL) { + emit(ctx, OP_IS_NIL, loc); + } + + if (op & UNOP_IS_ZERO) { + emit(ctx, OP_IS_ZERO, loc); + } + + if (op & UNOP_IS_NEG_ONE) { + emit(ctx, OP_IS_NEG_ONE, loc); + } + + if (op & UNOP_NEG) { + emit(ctx, OP_NEG, loc); + } break; } } diff --git a/src/compiler/opt.c b/src/compiler/opt.c index 7519437..48a5587 100644 --- a/src/compiler/opt.c +++ b/src/compiler/opt.c @@ -1,6 +1,6 @@ #include "opt.h" -static bool isConst(Node *node) { +static inline bool isConst(Node *node) { return ( node->type == NODE_INT || node->type == NODE_FLOAT || @@ -9,21 +9,25 @@ static bool isConst(Node *node) { ); } -static bool isFoldable(Node *node) { +static inline bool isFoldable(Node *node) { return ( isConst(node) || node->type == NODE_UNOP ); } -static bool isUnOp(Node *node) { +static inline bool isUnOp(Node *node) { return node->type == NODE_UNOP; } -static TypeKind getTypeKind(Node *node) { +static inline TypeKind getTypeKind(Node *node) { return node->valType.kind; } +static inline bool eitherAre(Node *a, Node *b, TypeKind kind) { + return getTypeKind(a) == kind || getTypeKind(b) == kind; +} + static double getUnOpValue(Node *node); static double getNumberValue(Node *node); @@ -67,6 +71,11 @@ static bool getBoolValue(Node *node) { return NODE_AS_BOOL(node).value; } +// please always check if both a and b are numbers before using this function +static inline bool eitherNumsEq(Node *a, Node *b, double value) { + return getNumberValue(a) == value || getNumberValue(b) == value; +} + static void constFoldInt(Node *node) { BinOp *binOp = &NODE_AS_BINOP(node); @@ -170,21 +179,6 @@ static void constFoldBool(Node *node) { Node *right = binOp->right; if (op.type == TOK_EQUAL || op.type == TOK_NEQUAL) { - if (!typesMatch(left->valType, right->valType)) { - freeNode(left); - freeNode(right); - - node->type = NODE_BOOL; - - Bool result = { - .value = false, - .loc = op.loc - }; - - node->as.b = result; - return; - } - if (getTypeKind(left) == TYPE_NIL) { freeNode(left); freeNode(right); @@ -303,6 +297,63 @@ static void constFold(Node *node) { } } +static void eqSpecialization(Node *node) { + BinOp binOp = NODE_AS_BINOP(node); + + Tok op = binOp.op; + + Node *left = binOp.left; + Node *right = binOp.right; + + const bool isNeg = op.type == TOK_NEQUAL; + + if (eitherAre(left, right, TYPE_NIL)) { + const bool isLeft = getTypeKind(left) == TYPE_NIL; + + node->type = NODE_UNOP; + + UnOp unOp = { + .op = binOp.op, + .opType = UNOP_IS_NIL | (isNeg && UNOP_NEG), + .operand = isLeft ? left : right + }; + + freeNode(isLeft ? right : left); + + node->as.unOp = unOp; + return; + } + + // we can assume that left and right have the same type + // because we always do a typesMatch() check in optBinOp() + if (!isNum(left)) { + return; + } + + if (eitherNumsEq(left, right, 0.0F) || eitherNumsEq(left, right, -1.0F)) { + const double leftValue = getNumberValue(left); + + // the edge case where the binOp looks like this: + // -1 == 0 + // is impossible because it would’ve been constant folded earlier, + // and so we’d be left with a ( Bool false ) node. + const bool isLeft = leftValue == 0.0F || leftValue == -1.0F; + const bool isZero = leftValue == 0.0F; + + node->type = NODE_UNOP; + + UnOp unOp = { + .op = binOp.op, + .opType = (isZero ? UNOP_IS_ZERO : UNOP_IS_NEG_ONE) | isNeg && UNOP_NEG, + .operand = isLeft ? left : right + }; + + freeNode(isLeft ? right : left); + + node->as.unOp = unOp; + } +} + void optNode(Node *node) { switch (node->type) { case NODE_BINOP: @@ -325,12 +376,43 @@ void optBinOp(Node *node) { // TODO: implement unary canonicalization BinOp *binOp = &NODE_AS_BINOP(node); - optNode(binOp->left); - optNode(binOp->right); + Tok op = binOp->op; + + Node *left = binOp->left; + Node *right = binOp->right; + + optNode(left); + optNode(right); + + if ( + !isNum(node) && + !typesMatch(left->valType, right->valType) + ) { + node->type = NODE_BOOL; + + Bool falseNode = { + .value = false, + .loc = binOp->op.loc + }; + + freeNode(left); + freeNode(right); + + node->as.b = falseNode; + return; + } if (isFoldable(binOp->left) && isFoldable(binOp->right)) { constFold(node); } + + if (node->type != NODE_BINOP) { + return; + } + + if (op.type == TOK_EQUAL || op.type == TOK_NEQUAL) { + eqSpecialization(node); + } } void optUnOp(Node *node) { diff --git a/src/ir/ir.c b/src/ir/ir.c index 6bc6c6c..0b11a72 100644 --- a/src/ir/ir.c +++ b/src/ir/ir.c @@ -117,9 +117,10 @@ Node *newNil(TypeTable *table, Loc loc) { return node; } -Node *newUnOp(TypeTable *table, Tok op, Node *operand) { +Node *newUnOp(TypeTable *table, Tok op, UnOpType opType, Node *operand) { UnOp unOp = { .op = op, + .opType = opType, .operand = operand }; diff --git a/src/vm/debug.c b/src/vm/debug.c index c1a2396..9fe3582 100644 --- a/src/vm/debug.c +++ b/src/vm/debug.c @@ -82,6 +82,15 @@ size_t disasmInstr(Chunk *ch, size_t offset) { case OP_NOT: return simpleInstr("not", offset); + case OP_IS_NIL: + return simpleInstr("isnil", offset); + + case OP_IS_ZERO: + return simpleInstr("isz", offset); + + case OP_IS_NEG_ONE: + return simpleInstr("isneg1", offset); + case OP_ADD: return simpleInstr("add", offset); diff --git a/src/vm/vm.c b/src/vm/vm.c index b5bdcce..b75bc2d 100644 --- a/src/vm/vm.c +++ b/src/vm/vm.c @@ -100,10 +100,19 @@ static Aftermath run(VM *vm) { break; case OP_NOT: - vm->stackTop[-1] = BOOL_VAL( - IS_VAL_NIL(vm->stackTop[-1]) || - (IS_VAL_BOOL(vm->stackTop[-1]) && !VAL_AS_BOOL(vm->stackTop[-1])) - ); + vm->stackTop[-1] = BOOL_VAL(!VAL_AS_BOOL(vm->stackTop[-1])); + break; + + case OP_IS_NIL: + vm->stackTop[-1] = BOOL_VAL(!IS_VAL_NIL(vm->stackTop[-1])); + break; + + case OP_IS_ZERO: + vm->stackTop[-1] = BOOL_VAL(VAL_AS_NUM(vm->stackTop[-1]) == 0); + break; + + case OP_IS_NEG_ONE: + vm->stackTop[-1] = BOOL_VAL(VAL_AS_NUM(vm->stackTop[-1]) == -1); break; case OP_ADD: From 43b119e91910aebbf53c039d6ac18dbdb58450be Mon Sep 17 00:00:00 2001 From: ademyrodev Date: Sun, 18 Aug 2024 08:19:39 +0200 Subject: [PATCH 02/10] add specific constant instructions --- include/chunk.h | 5 +++- src/compiler/emit.c | 57 ++++++++++++++++++++++++++++++++++----------- src/vm/debug.c | 13 +++++++++-- src/vm/vm.c | 14 ++++++++++- 4 files changed, 72 insertions(+), 17 deletions(-) diff --git a/include/chunk.h b/include/chunk.h index bf4faf6..f17ccfb 100644 --- a/include/chunk.h +++ b/include/chunk.h @@ -10,11 +10,14 @@ typedef enum { OP_TRUE, OP_FALSE, OP_NIL, + OP_ZERO, + OP_ONE, + OP_MINUS_ONE, OP_NEG, OP_NOT, OP_IS_NIL, OP_IS_ZERO, - OP_IS_NEG_ONE, + OP_IS_MINUS_ONE, OP_ADD, OP_SUB, OP_MUL, diff --git a/src/compiler/emit.c b/src/compiler/emit.c index e1f4d07..0dbcc35 100644 --- a/src/compiler/emit.c +++ b/src/compiler/emit.c @@ -82,7 +82,7 @@ static void emitUnOp(Ctx *ctx, UnOp unOp) { } if (op & UNOP_IS_NEG_ONE) { - emit(ctx, OP_IS_NEG_ONE, loc); + emit(ctx, OP_IS_MINUS_ONE, loc); } if (op & UNOP_NEG) { @@ -92,6 +92,45 @@ static void emitUnOp(Ctx *ctx, UnOp unOp) { } } +static void emitInt(Ctx *ctx, Int node) { + switch (node.value) { + case -1L: + emit(ctx, OP_MINUS_ONE, node.loc); + break; + + case 0L: + emit(ctx, OP_ZERO, node.loc); + break; + + case 1L: + emit(ctx, OP_ONE, node.loc); + break; + + default: + emitConst(ctx, NUM_VAL((double)node.value), node.loc); + break; + } +} + +static void emitFloat(Ctx *ctx, Float node) { + if (node.value == -1) { + emit(ctx, OP_MINUS_ONE, node.loc); + return; + } + + if (node.value == 0) { + emit(ctx, OP_ZERO, node.loc); + return; + } + + if (node.value == 1) { + emit(ctx, OP_ONE, node.loc); + return; + } + + emitConst(ctx, NUM_VAL(node.value), node.loc); +} + Chunk *currChunk(Ctx *ctx) { return ctx->currCh; } @@ -123,21 +162,13 @@ void emitNode(Ctx *ctx, Node *node) { emitUnOp(ctx, NODE_AS_UNOP(node)); break; - case NODE_INT: { - Int i = NODE_AS_INT(node); - - Val val = NUM_VAL((double)i.value); - emitConst(ctx, val, i.loc); + case NODE_INT: + emitInt(ctx, NODE_AS_INT(node)); break; - } - case NODE_FLOAT: { - Float f = NODE_AS_FLOAT(node); - - Val val = NUM_VAL(f.value); - emitConst(ctx, val, f.loc); + case NODE_FLOAT: + emitFloat(ctx, NODE_AS_FLOAT(node)); break; - } case NODE_BOOL: { Bool b = NODE_AS_BOOL(node); diff --git a/src/vm/debug.c b/src/vm/debug.c index 9fe3582..dfc273b 100644 --- a/src/vm/debug.c +++ b/src/vm/debug.c @@ -75,6 +75,15 @@ size_t disasmInstr(Chunk *ch, size_t offset) { case OP_NIL: return simpleInstr("nil", offset); + + case OP_ZERO: + return simpleInstr("pushz", offset); + + case OP_MINUS_ONE: + return simpleInstr("pushm1", offset); + + case OP_ONE: + return simpleInstr("push1", offset); case OP_NEG: return simpleInstr("neg", offset); @@ -88,8 +97,8 @@ size_t disasmInstr(Chunk *ch, size_t offset) { case OP_IS_ZERO: return simpleInstr("isz", offset); - case OP_IS_NEG_ONE: - return simpleInstr("isneg1", offset); + case OP_IS_MINUS_ONE: + return simpleInstr("ism1", offset); case OP_ADD: return simpleInstr("add", offset); diff --git a/src/vm/vm.c b/src/vm/vm.c index b75bc2d..c1fa24c 100644 --- a/src/vm/vm.c +++ b/src/vm/vm.c @@ -95,6 +95,18 @@ static Aftermath run(VM *vm) { push(vm, NIL_VAL); break; + case OP_ZERO: + push(vm, NUM_VAL(0)); + break; + + case OP_ONE: + push(vm, NUM_VAL(1)); + break; + + case OP_MINUS_ONE: + push(vm, NUM_VAL(-1)); + break; + case OP_NEG: vm->stackTop[-1] = NUM_VAL(-VAL_AS_NUM(vm->stackTop[-1])); break; @@ -111,7 +123,7 @@ static Aftermath run(VM *vm) { vm->stackTop[-1] = BOOL_VAL(VAL_AS_NUM(vm->stackTop[-1]) == 0); break; - case OP_IS_NEG_ONE: + case OP_IS_MINUS_ONE: vm->stackTop[-1] = BOOL_VAL(VAL_AS_NUM(vm->stackTop[-1]) == -1); break; From a539f849cf0a749c6109d26b348213fab0f06cf0 Mon Sep 17 00:00:00 2001 From: ademyrodev Date: Sun, 18 Aug 2024 08:38:30 +0200 Subject: [PATCH 03/10] add bitwise operators --- include/tok.h | 2 ++ src/lexer/lexer.c | 12 ++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/include/tok.h b/include/tok.h index bb60051..0fb1033 100644 --- a/include/tok.h +++ b/include/tok.h @@ -19,6 +19,8 @@ typedef enum { TOK_NEQUAL, TOK_ASSIGN, TOK_EQUAL, TOK_GREATER, TOK_GREATER_EQUAL, TOK_LESS, TOK_LESS_EQUAL, + TOK_SHL, TOK_SHR, TOK_BIT_AND, TOK_BIT_XOR, + TOK_EXCLAM, TOK_QUESTION, TOK_AND, TOK_CLASS, TOK_ELSE, TOK_END, diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 972ebca..75533bf 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -405,6 +405,12 @@ Tok nextTok(Lexer *lexer) { case '*': return makeTok(lexer, TOK_STAR); + case '&': + return makeTok(lexer, TOK_BIT_AND); + + case '^': + return makeTok(lexer, TOK_BIT_XOR); + case '!': return makeTok( lexer, @@ -420,13 +426,15 @@ Tok nextTok(Lexer *lexer) { case '>': return makeTok( lexer, - match(lexer, '=') ? TOK_GREATER_EQUAL : TOK_GREATER + match(lexer, '=') ? TOK_GREATER_EQUAL : + match(lexer, '>') ? TOK_SHR : TOK_GREATER ); case '<': return makeTok( lexer, - match(lexer, '=') ? TOK_LESS_EQUAL : TOK_LESS + match(lexer, '=') ? TOK_LESS_EQUAL : + match(lexer, '<') ? TOK_SHL : TOK_LESS ); case '.': From 47ce6ee53c00b6f86eaef6469828e5db73eb9266 Mon Sep 17 00:00:00 2001 From: ademyrodev Date: Sun, 18 Aug 2024 09:44:05 +0200 Subject: [PATCH 04/10] implement bitwise operators --- include/chunk.h | 5 ++ src/compiler/compiler.c | 105 ++++++++++++++++++++++++++++++++++++++-- src/compiler/emit.c | 55 ++++----------------- src/compiler/opt.c | 56 ++++++++++++++------- src/ir/ir.c | 7 +++ src/vm/debug.c | 15 ++++++ src/vm/vm.c | 30 +++++++++++- 7 files changed, 205 insertions(+), 68 deletions(-) diff --git a/include/chunk.h b/include/chunk.h index f17ccfb..20e6ee4 100644 --- a/include/chunk.h +++ b/include/chunk.h @@ -22,6 +22,11 @@ typedef enum { OP_SUB, OP_MUL, OP_DIV, + OP_SHL, + OP_SHR, + OP_BIT_AND, + OP_BIT_XOR, + OP_BIT_OR, OP_EQ, OP_NEQ, OP_GREATER, diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index 2e731a8..d262f63 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -116,6 +116,17 @@ static void binOpTypeErr(Ctx *ctx, Node *left, Tok op, Node *right) { actionName = "divide"; break; + case TOK_SHL: + case TOK_SHR: + actionName = "shift"; + break; + + case TOK_BIT_AND: + case TOK_PIPE: + case TOK_BIT_XOR: + actionName = "perform bitwise operation on"; + break; + default: actionName = "compare"; break; @@ -247,8 +258,12 @@ static void endCompiler(Ctx *ctx) { } static Node *expr(Ctx *ctx); +static Node *bitOr(Ctx *ctx); +static Node *bitXor(Ctx *ctx); +static Node *bitAnd(Ctx *ctx); static Node *equality(Ctx *ctx); static Node *comparison(Ctx *ctx); +static Node *bitShift(Ctx *ctx); static Node *term(Ctx *ctx); static Node *factor(Ctx *ctx); static Node *unary(Ctx *ctx); @@ -257,7 +272,72 @@ static Node *intLiteral(Ctx *ctx); static Node *floatLiteral(Ctx *ctx); static Node *expr(Ctx *ctx) { - return equality(ctx); + return bitOr(ctx); +} + +static Node *bitOr(Ctx *ctx) { + Node *left = bitXor(ctx); + + while (check(ctx, TOK_PIPE)) { + Tok op = consume(ctx); + + Node *right = bitXor(ctx); + + // in the future, allow enum flag values. still gotta determine a syntax + // for them. a good candidate could be: + // enum Flags for & + // ... + // end + // but that’s really unintuitive. we’ll see--i’d like not to have to + // introduce a specific ‘bitwise’ keyword. and `enum X for &` has its + // charm, too. + if (!checkType(left, TYPE_INT) || !checkType(right, TYPE_INT)) { + binOpTypeErr(ctx, left, op, right); + } + + Node *binOp = newBinOp(ctx->types, left, op, right); + left = binOp; + } + + return left; +} + +static Node *bitXor(Ctx *ctx) { + Node *left = bitAnd(ctx); + + while (check(ctx, TOK_BIT_XOR)) { + Tok op = consume(ctx); + + Node *right = bitAnd(ctx); + + if (!checkType(left, TYPE_INT) || !checkType(right, TYPE_INT)) { + binOpTypeErr(ctx, left, op, right); + } + + Node *binOp = newBinOp(ctx->types, left, op, right); + left = binOp; + } + + return left; +} + +static Node *bitAnd(Ctx *ctx) { + Node *left = equality(ctx); + + while (check(ctx, TOK_BIT_AND)) { + Tok op = consume(ctx); + + Node *right = equality(ctx); + + if (!checkType(left, TYPE_INT) || !checkType(right, TYPE_INT)) { + binOpTypeErr(ctx, left, op, right); + } + + Node *binOp = newBinOp(ctx->types, left, op, right); + left = binOp; + } + + return left; } static Node *equality(Ctx *ctx) { @@ -276,7 +356,7 @@ static Node *equality(Ctx *ctx) { } static Node *comparison(Ctx *ctx) { - Node *left = term(ctx); + Node *left = bitShift(ctx); while ( checkEither(ctx, TOK_LESS, TOK_GREATER) || @@ -284,7 +364,7 @@ static Node *comparison(Ctx *ctx) { ) { Tok op = consume(ctx); - Node *right = term(ctx); + Node *right = bitShift(ctx); if (!isNum(left) || !isNum(right)) { binOpTypeErr(ctx, left, op, right); @@ -297,6 +377,25 @@ static Node *comparison(Ctx *ctx) { return left; } +static Node *bitShift(Ctx *ctx) { + Node *left = term(ctx); + + while (checkEither(ctx, TOK_SHL, TOK_SHR)) { + Tok op = consume(ctx); + + Node *right = comparison(ctx); + + if (!checkType(left, TYPE_INT) || !checkType(right, TYPE_INT)) { + binOpTypeErr(ctx, left, op, right); + } + + Node *binOp = newBinOp(ctx->types, left, op, right); + left = binOp; + } + + return left; +} + static Node *term(Ctx *ctx) { Node *left = factor(ctx); diff --git a/src/compiler/emit.c b/src/compiler/emit.c index 0dbcc35..a0f88b8 100644 --- a/src/compiler/emit.c +++ b/src/compiler/emit.c @@ -1,59 +1,22 @@ #include "emit.h" #include "chunk.h" +static uint8_t binOpTable[] = { + OP_ADD, OP_SUB, OP_MUL, OP_DIV, + OP_SHL, OP_SHR, OP_BIT_AND, OP_BIT_XOR, + OP_BIT_OR, OP_EQ, OP_NEQ, OP_GREATER, + OP_LESS, OP_GREATER_EQ, OP_LESS_EQ +}; + static void emitBinOp(Ctx *ctx, BinOp binOp) { emitNode(ctx, binOp.left); emitNode(ctx, binOp.right); const Tok op = binOp.op; - // TODO: make this a table sometime and use it like this: - // uint8_t opcode = binOpTable[op.type - TOK_PLUS]; - switch (op.type) { - case TOK_PLUS: - emit(ctx, OP_ADD, op.loc); - break; - - case TOK_MINUS: - emit(ctx, OP_SUB, op.loc); - break; - - case TOK_STAR: - emit(ctx, OP_MUL, op.loc); - break; - - case TOK_SLASH: - emit(ctx, OP_DIV, op.loc); - break; - - case TOK_EQUAL: - emit(ctx, OP_EQ, op.loc); - break; - - case TOK_NEQUAL: - emit(ctx, OP_NEQ, op.loc); - break; + uint8_t opcode = binOpTable[op.type - TOK_PLUS]; - case TOK_GREATER: - emit(ctx, OP_GREATER, op.loc); - break; - - case TOK_LESS: - emit(ctx, OP_LESS, op.loc); - break; - - case TOK_GREATER_EQUAL: - emit(ctx, OP_GREATER_EQ, op.loc); - break; - - case TOK_LESS_EQUAL: - emit(ctx, OP_LESS_EQ, op.loc); - break; - - default: - // shouldn’t ever happen - break; - } + emit(ctx, opcode, op.loc); } static void emitUnOp(Ctx *ctx, UnOp unOp) { diff --git a/src/compiler/opt.c b/src/compiler/opt.c index 48a5587..50a660f 100644 --- a/src/compiler/opt.c +++ b/src/compiler/opt.c @@ -103,6 +103,26 @@ static void constFoldInt(Node *node) { result = leftValue / rightValue; break; + case TOK_SHL: + result = leftValue << rightValue; + break; + + case TOK_SHR: + result = leftValue >> rightValue; + break; + + case TOK_BIT_AND: + result = leftValue & rightValue; + break; + + case TOK_BIT_XOR: + result = leftValue ^ rightValue; + break; + + case TOK_PIPE: + result = leftValue | rightValue; + break; + default: // this shouldn’t ever happen; but the compiler complains // if there’s no default case @@ -179,6 +199,24 @@ static void constFoldBool(Node *node) { Node *right = binOp->right; if (op.type == TOK_EQUAL || op.type == TOK_NEQUAL) { + if ( + !isNum(node) && + !typesMatch(left->valType, right->valType) + ) { + node->type = NODE_BOOL; + + Bool falseNode = { + .value = false, + .loc = binOp->op.loc + }; + + freeNode(left); + freeNode(right); + + node->as.b = falseNode; + return; + } + if (getTypeKind(left) == TYPE_NIL) { freeNode(left); freeNode(right); @@ -384,24 +422,6 @@ void optBinOp(Node *node) { optNode(left); optNode(right); - if ( - !isNum(node) && - !typesMatch(left->valType, right->valType) - ) { - node->type = NODE_BOOL; - - Bool falseNode = { - .value = false, - .loc = binOp->op.loc - }; - - freeNode(left); - freeNode(right); - - node->as.b = falseNode; - return; - } - if (isFoldable(binOp->left) && isFoldable(binOp->right)) { constFold(node); } diff --git a/src/ir/ir.c b/src/ir/ir.c index 0b11a72..1ad831c 100644 --- a/src/ir/ir.c +++ b/src/ir/ir.c @@ -52,6 +52,13 @@ static Type inferBinOp(TypeTable *table, BinOp node) { case TOK_SLASH: return *table->floatType; + case TOK_SHL: + case TOK_SHR: + case TOK_BIT_AND: + case TOK_BIT_XOR: + case TOK_PIPE: + return *table->intType; + default: return *table->boolType; } diff --git a/src/vm/debug.c b/src/vm/debug.c index dfc273b..09e32b4 100644 --- a/src/vm/debug.c +++ b/src/vm/debug.c @@ -112,6 +112,21 @@ size_t disasmInstr(Chunk *ch, size_t offset) { case OP_DIV: return simpleInstr("div", offset); + case OP_SHL: + return simpleInstr("shl", offset); + + case OP_SHR: + return simpleInstr("shr", offset); + + case OP_BIT_AND: + return simpleInstr("band", offset); + + case OP_BIT_XOR: + return simpleInstr("xor", offset); + + case OP_BIT_OR: + return simpleInstr("bor", offset); + case OP_EQ: return simpleInstr("eq", offset); diff --git a/src/vm/vm.c b/src/vm/vm.c index c1fa24c..d60c2db 100644 --- a/src/vm/vm.c +++ b/src/vm/vm.c @@ -48,6 +48,13 @@ static Aftermath run(VM *vm) { \ push(vm, valType(a op b)); \ } while (false) +#define BIT_OP(op) \ + do { \ + int b = (int)VAL_AS_NUM(pop(vm)); \ + int a = (int)VAL_AS_NUM(pop(vm)); \ + \ + push(vm, NUM_VAL(a op b)); \ + } while (false) while (true) { #ifdef DEBUG_EXEC @@ -142,7 +149,27 @@ static Aftermath run(VM *vm) { case OP_DIV: BIN_OP(NUM_VAL, /); break; - + + case OP_SHL: + BIT_OP(<<); + break; + + case OP_SHR: + BIT_OP(>>); + break; + + case OP_BIT_AND: + BIT_OP(&); + break; + + case OP_BIT_XOR: + BIT_OP(^); + break; + + case OP_BIT_OR: + BIT_OP(|); + break; + case OP_EQ: { Val b = pop(vm); Val a = pop(vm); @@ -189,6 +216,7 @@ static Aftermath run(VM *vm) { #undef READ_BYTE #undef READ_CONST #undef BIN_OP +#undef BIT_OP } Aftermath interpret(const char *fname, VM *vm, const char *src) { From 9d3cfc4f0ceec5061a97c921933a26ff06740dbc Mon Sep 17 00:00:00 2001 From: ademyrodev Date: Sun, 18 Aug 2024 10:42:54 +0200 Subject: [PATCH 05/10] make a couple changes --- src/compiler/opt.c | 46 +++++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/src/compiler/opt.c b/src/compiler/opt.c index 50a660f..2c4689b 100644 --- a/src/compiler/opt.c +++ b/src/compiler/opt.c @@ -199,24 +199,6 @@ static void constFoldBool(Node *node) { Node *right = binOp->right; if (op.type == TOK_EQUAL || op.type == TOK_NEQUAL) { - if ( - !isNum(node) && - !typesMatch(left->valType, right->valType) - ) { - node->type = NODE_BOOL; - - Bool falseNode = { - .value = false, - .loc = binOp->op.loc - }; - - freeNode(left); - freeNode(right); - - node->as.b = falseNode; - return; - } - if (getTypeKind(left) == TYPE_NIL) { freeNode(left); freeNode(right); @@ -343,6 +325,24 @@ static void eqSpecialization(Node *node) { Node *left = binOp.left; Node *right = binOp.right; + if ( + !isNum(node) && + !typesMatch(left->valType, right->valType) + ) { + node->type = NODE_BOOL; + + Bool falseNode = { + .value = false, + .loc = op.loc + }; + + freeNode(left); + freeNode(right); + + node->as.b = falseNode; + return; + } + const bool isNeg = op.type == TOK_NEQUAL; if (eitherAre(left, right, TYPE_NIL)) { @@ -363,7 +363,7 @@ static void eqSpecialization(Node *node) { } // we can assume that left and right have the same type - // because we always do a typesMatch() check in optBinOp() + // because we always do a typesMatch() check before getting here if (!isNum(left)) { return; } @@ -378,18 +378,20 @@ static void eqSpecialization(Node *node) { const bool isLeft = leftValue == 0.0F || leftValue == -1.0F; const bool isZero = leftValue == 0.0F; + const UnOpType opType = isZero ? UNOP_IS_ZERO : UNOP_IS_NEG_ONE; + node->type = NODE_UNOP; UnOp unOp = { .op = binOp.op, - .opType = (isZero ? UNOP_IS_ZERO : UNOP_IS_NEG_ONE) | isNeg && UNOP_NEG, + .opType = opType | isNeg && UNOP_NEG, .operand = isLeft ? left : right }; freeNode(isLeft ? right : left); node->as.unOp = unOp; - } + } } void optNode(Node *node) { @@ -430,6 +432,8 @@ void optBinOp(Node *node) { return; } + // TODO: implement strength reduction once we have variables + if (op.type == TOK_EQUAL || op.type == TOK_NEQUAL) { eqSpecialization(node); } From d1ebcee4fdf1e5bec812a19b0a1a25bc914519f3 Mon Sep 17 00:00:00 2001 From: ademyrodev Date: Sun, 18 Aug 2024 10:45:06 +0200 Subject: [PATCH 06/10] use specialized boolean instructions --- src/compiler/emit.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/compiler/emit.c b/src/compiler/emit.c index a0f88b8..282b9f4 100644 --- a/src/compiler/emit.c +++ b/src/compiler/emit.c @@ -136,8 +136,7 @@ void emitNode(Ctx *ctx, Node *node) { case NODE_BOOL: { Bool b = NODE_AS_BOOL(node); - Val val = BOOL_VAL(b.value); - emitConst(ctx, val, b.loc); + emit(ctx, b.value ? OP_TRUE : OP_FALSE, b.loc); break; } From 0e068b09a9bcfc505ff58fdfba1d719290ff3234 Mon Sep 17 00:00:00 2001 From: ademyrodev Date: Mon, 19 Aug 2024 01:53:12 +0200 Subject: [PATCH 07/10] fix valgrind errors and other constant fold ones. --- "\\" | 519 +++++++++++++++++++++++++++++++++++++ src/compiler/compiler.c | 17 +- src/compiler/opt.c | 25 +- src/main/main.c | 4 +- test/expressions/test.neve | 2 +- 5 files changed, 534 insertions(+), 33 deletions(-) create mode 100644 "\\" diff --git "a/\\" "b/\\" new file mode 100644 index 0000000..577cb21 --- /dev/null +++ "b/\\" @@ -0,0 +1,519 @@ +#include "opt.h" + +#include + +static inline bool isConst(Node *node) { + return ( + node->type == NODE_INT || + node->type == NODE_FLOAT || + node->type == NODE_BOOL || + node->type == NODE_NIL + ); +} + +static inline bool isFoldable(Node *node) { + return ( + isConst(node) || + node->type == NODE_UNOP + ); +} + +static inline bool isUnOp(Node *node) { + return node->type == NODE_UNOP; +} + +static inline TypeKind getTypeKind(Node *node) { + return node->valType.kind; +} + +static inline bool eitherAre(Node *a, Node *b, TypeKind kind) { + return getTypeKind(a) == kind || getTypeKind(b) == kind; +} + +static double getUnOpValue(Node *node); +static double getNumberValue(Node *node); + +static double getUnOpValue(Node *node) { + UnOp unOp = NODE_AS_UNOP(node); + + switch (unOp.op.type) { + case TOK_MINUS: + return -getNumberValue(unOp.operand); + + default: + return getNumberValue(unOp.operand); + } +} + +static double getNumberValue(Node *node) { + if (node->type == NODE_UNOP) { + return getUnOpValue(node); + } + + if (getTypeKind(node) == TYPE_FLOAT) { + return NODE_AS_FLOAT(node).value; + } + + return (double)NODE_AS_INT(node).value; +} + +static bool getBoolValue(Node *node) { + if (node->type == NODE_UNOP) { + UnOp unOp = NODE_AS_UNOP(node); + + switch (unOp.op.type) { + case TOK_NOT: + return !getBoolValue(unOp.operand); + + default: + return getBoolValue(unOp.operand); + } + } + + return NODE_AS_BOOL(node).value; +} + +// please always check if both a and b are numbers before using this function +static inline bool eitherNumsEq(Node *a, Node *b, double value) { + return getNumberValue(a) == value || getNumberValue(b) == value; +} + +static void constFoldInt(Node *node) { + BinOp *binOp = &NODE_AS_BINOP(node); + + long leftValue = (long)getNumberValue(binOp->left); + long rightValue = (long)getNumberValue(binOp->right); + + Loc loc = binOp->op.loc; + TokType op = binOp->op.type; + + long result; + switch (op) { + case TOK_PLUS: + result = leftValue + rightValue; + break; + + case TOK_MINUS: + result = leftValue - rightValue; + break; + + case TOK_STAR: + result = leftValue * rightValue; + break; + + case TOK_SLASH: + result = leftValue / rightValue; + break; + + case TOK_SHL: + result = leftValue << rightValue; + break; + + case TOK_SHR: + result = leftValue >> rightValue; + break; + + case TOK_BIT_AND: + result = leftValue & rightValue; + break; + + case TOK_BIT_XOR: + result = leftValue ^ rightValue; + break; + + case TOK_PIPE: + result = leftValue | rightValue; + break; + + default: + // this shouldn’t ever happen; but the compiler complains + // if there’s no default case + result = -1L; + break; + } + + freeNode(binOp->left); + freeNode(binOp->right); + + node->type = NODE_INT; + + Int resultNode = { + .value = result, + .loc = loc + }; + + node->as.i = resultNode; +} + +static void constFoldFloat(Node *node) { + BinOp *binOp = &NODE_AS_BINOP(node); + + double leftValue = getNumberValue(binOp->left); + double rightValue = getNumberValue(binOp->right); + + Loc loc = binOp->op.loc; + TokType op = binOp->op.type; + + double result; + switch (op) { + case TOK_PLUS: + result = leftValue + rightValue; + break; + + case TOK_MINUS: + result = leftValue - rightValue; + break; + + case TOK_STAR: + result = leftValue * rightValue; + break; + + case TOK_SLASH: + result = leftValue / rightValue; + break; + + default: + // this shouldn’t ever happen; but the compiler complains + // if there’s no default case + result = -1L; + break; + } + + freeNode(binOp->left); + freeNode(binOp->right); + + node->type = NODE_FLOAT; + + Float resultNode = { + .value = result, + .loc = loc + }; + + node->as.f = resultNode; +} + +static void constFoldBool(Node *node) { + BinOp *binOp = &NODE_AS_BINOP(node); + + Tok op = binOp->op; + + Node *left = binOp->left; + Node *right = binOp->right; + + if (op.type == TOK_EQUAL || op.type == TOK_NEQUAL) { + if ( + !isNum(node) && + !typesMatch(left->valType, right->valType) + ) { + node->type = NODE_BOOL; + + Bool falseNode = { + .value = op.type == TOK_NEQUAL, + .loc = op.loc + }; + + freeNode(left); + freeNode(right); + + node->as.b = falseNode; + return; + } + + if (getTypeKind(left) == TYPE_NIL) { + freeNode(left); + freeNode(right); + + node->type = NODE_BOOL; + + Bool result = { + .value = true, + .loc = op.loc + }; + + node->as.b = result; + } + + if (getTypeKind(left) == TYPE_BOOL) { + const bool leftValue = getBoolValue(left); + const bool rightValue = getBoolValue(right); + + const bool equal = rightValue && leftValue; + + freeNode(left); + freeNode(right); + + node->type = NODE_BOOL; + + Bool result = { + .value = op.type == TOK_NEQUAL ? !equal : equal, + .loc = op.loc + }; + + node->as.b = result; + return; + } + + if (isNum(left)) { + const double leftValue = getNumberValue(left); + const double rightValue = getNumberValue(right); + + const bool equal = leftValue == rightValue; + + freeNode(left); + freeNode(right); + + node->type = NODE_BOOL; + + Bool result = { + .value = op.type == TOK_NEQUAL ? !equal : equal, + .loc = op.loc + }; + + node->as.b = result; + + return; + } + } else { + const double leftValue = getNumberValue(left); + const double rightValue = getNumberValue(right); + + bool result; + + switch (op.type) { + case TOK_GREATER: + result = leftValue > rightValue; + break; + + case TOK_LESS: + result = leftValue < rightValue; + break; + + case TOK_GREATER_EQUAL: + result = leftValue >= rightValue; + break; + + case TOK_LESS_EQUAL: + result = leftValue <= rightValue; + break; + + default: + result = false; + break; + } + + freeNode(left); + freeNode(right); + + node->type = NODE_BOOL; + + Bool resultNode = { + .value = result, + .loc = op.loc + }; + + node->as.b = resultNode; + return; + } +} + +static void constFold(Node *node) { + // the type is always known before optimization, so there’s no need + // to call inferType(). + switch (getTypeKind(node)) { + case TYPE_INT: + constFoldInt(node); + break; + + case TYPE_FLOAT: + constFoldFloat(node); + break; + + case TYPE_BOOL: + constFoldBool(node); + break; + + default: + break; + } +} + +static void eqSpecialization(Node *node) { + BinOp binOp = NODE_AS_BINOP(node); + + Tok op = binOp.op; + + Node *left = binOp.left; + Node *right = binOp.right; + + const bool isNeg = op.type == TOK_NEQUAL; + + if (eitherAre(left, right, TYPE_NIL)) { + const bool isLeft = getTypeKind(left) == TYPE_NIL; + + node->type = NODE_UNOP; + + UnOp unOp = { + .op = binOp.op, + .opType = UNOP_IS_NIL | (isNeg && UNOP_NEG), + .operand = isLeft ? left : right + }; + + freeNode(isLeft ? right : left); + + node->as.unOp = unOp; + return; + } + + // we can assume that left and right have the same type + // because we always do a typesMatch() check before getting here + if (!isNum(left)) { + return; + } + + if (eitherNumsEq(left, right, 0.0F) || eitherNumsEq(left, right, -1.0F)) { + const double leftValue = getNumberValue(left); + + // the edge case where the binOp looks like this: + // -1 == 0 + // is impossible because it would’ve been constant folded earlier, + // and so we’d be left with a ( Bool false ) node. + const bool isLeft = leftValue == 0.0F || leftValue == -1.0F; + const bool isZero = leftValue == 0.0F; + + const UnOpType opType = isZero ? UNOP_IS_ZERO : UNOP_IS_NEG_ONE; + + node->type = NODE_UNOP; + + UnOp unOp = { + .op = binOp.op, + .opType = opType | isNeg && UNOP_NEG, + .operand = isLeft ? left : right + }; + + freeNode(isLeft ? right : left); + + node->as.unOp = unOp; + } +} + +void optNode(Node *node) { + switch (node->type) { + case NODE_BINOP: + optBinOp(node); + break; + + case NODE_UNOP: + optUnOp(node); + break; + + case NODE_INT: + case NODE_FLOAT: + case NODE_BOOL: + case NODE_NIL: + break; + } +} + +void optBinOp(Node *node) { + // TODO: implement unary canonicalization + BinOp *binOp = &NODE_AS_BINOP(node); + + Tok op = binOp->op; + + Node *left = binOp->left; + Node *right = binOp->right; + + optNode(left); + optNode(right); + + if (isFoldable(binOp->left) && isFoldable(binOp->right)) { + constFold(node); + } + + if (node->type != NODE_BINOP) { + return; + } + + // TODO: implement strength reduction once we have variables + + if (op.type == TOK_EQUAL || op.type == TOK_NEQUAL) { + eqSpecialization(node); + } +} + +void optUnOp(Node *node) { + UnOp *unOp = &NODE_AS_UNOP(node); + + Node *nodeOperand = unOp->operand; + UnOp *operand = &NODE_AS_UNOP(unOp->operand); + + optNode(unOp->operand); + + if (isUnOp(unOp->operand) && operand->op.type == unOp->op.type) { + node->type = operand->operand->type; + node->as = operand->operand->as; + + freeNode(nodeOperand); + } + + if (isConst(unOp->operand)) { + TypeKind kind = getTypeKind(unOp->operand); + + switch (kind) { + case TYPE_BOOL: + case TYPE_NIL: { + // we can assume that the operand is `not` thanks to prior type + // checking + const bool operandValue = getBoolValue(unOp->operand); + + freeNode(unOp->operand); + + node->type = NODE_BOOL; + + Bool negated = { + .value = !operandValue, + .loc = unOp->op.loc + }; + + node->as.b = negated; + break; + } + + case TYPE_INT: { + // else, we can assume that the operator is `-` + const long operandValue = (long)getNumberValue(unOp->operand); + + freeNode(unOp->operand); + + node->type = NODE_INT; + + Int simplified = { + .value = -operandValue, + .loc = unOp->op.loc + }; + + node->as.i = simplified; + break; + } + + case TYPE_FLOAT: { + const double operandValue = getNumberValue(unOp->operand); + + freeNode(unOp->operand); + + node->type = NODE_FLOAT; + + Float simplified = { + .value = -operandValue, + .loc = unOp->op.loc + }; + + node->as.f = simplified; + break; + } + + default: + break; + } + } +} diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index d262f63..e141c0d 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -348,6 +348,10 @@ static Node *equality(Ctx *ctx) { Node *right = comparison(ctx); + if (!typesMatch(left->valType, right->valType)) { + binOpTypeErr(ctx, left, op, right); + } + Node *binOp = newBinOp(ctx->types, left, op, right); left = binOp; } @@ -596,23 +600,18 @@ bool compile(const char *fname, const char *src, Chunk *ch) { const bool hadErrs = newMod.errCount != 0; -#ifdef DEBUG_COMPILE if (!hadErrs) { +#ifdef DEBUG_COMPILE fprintf(stderr, "unoptimized:\n"); prettyPrint(ast); - } #endif - - optNode(ast); - + optNode(ast); #ifdef DEBUG_COMPILE - if (!hadErrs) { fprintf(stderr, "optimized:\n"); prettyPrint(ast); - } #endif - - emitNode(&ctx, ast); + emitNode(&ctx, ast); + } freeNode(ast); diff --git a/src/compiler/opt.c b/src/compiler/opt.c index 2c4689b..b45c5f6 100644 --- a/src/compiler/opt.c +++ b/src/compiler/opt.c @@ -225,7 +225,7 @@ static void constFoldBool(Node *node) { node->type = NODE_BOOL; Bool result = { - .value = (op.type == TOK_EQUAL) && equal, + .value = op.type == TOK_NEQUAL ? !equal : equal, .loc = op.loc }; @@ -245,7 +245,7 @@ static void constFoldBool(Node *node) { node->type = NODE_BOOL; Bool result = { - .value = (op.type == TOK_EQUAL) && equal, + .value = op.type == TOK_NEQUAL ? !equal : equal, .loc = op.loc }; @@ -325,24 +325,6 @@ static void eqSpecialization(Node *node) { Node *left = binOp.left; Node *right = binOp.right; - if ( - !isNum(node) && - !typesMatch(left->valType, right->valType) - ) { - node->type = NODE_BOOL; - - Bool falseNode = { - .value = false, - .loc = op.loc - }; - - freeNode(left); - freeNode(right); - - node->as.b = falseNode; - return; - } - const bool isNeg = op.type == TOK_NEQUAL; if (eitherAre(left, right, TYPE_NIL)) { @@ -363,7 +345,8 @@ static void eqSpecialization(Node *node) { } // we can assume that left and right have the same type - // because we always do a typesMatch() check before getting here + // because checking equality on two different types results in + // a compiler error if (!isNum(left)) { return; } diff --git a/src/main/main.c b/src/main/main.c index a03061a..59785f5 100644 --- a/src/main/main.c +++ b/src/main/main.c @@ -49,10 +49,10 @@ static void repl() { VM vm = newVM(); while (true) { resetStack(&vm); - printf("? "); + fputs("? ", stdout); if (!fgets(line, (int)lim, stdin)) { - printf("\n"); + fputs("\n", stdout); break; } diff --git a/test/expressions/test.neve b/test/expressions/test.neve index 3232661..749a259 100644 --- a/test/expressions/test.neve +++ b/test/expressions/test.neve @@ -1 +1 @@ -not (5 - 4 > 3 * 2 == not nil) +false & 1 From bd2692909f7fb82f979f09ec66641feff2568714 Mon Sep 17 00:00:00 2001 From: ademyrodev Date: Mon, 19 Aug 2024 01:53:31 +0200 Subject: [PATCH 08/10] rm \ --- "\\" | 519 ----------------------------------------------------------- 1 file changed, 519 deletions(-) delete mode 100644 "\\" diff --git "a/\\" "b/\\" deleted file mode 100644 index 577cb21..0000000 --- "a/\\" +++ /dev/null @@ -1,519 +0,0 @@ -#include "opt.h" - -#include - -static inline bool isConst(Node *node) { - return ( - node->type == NODE_INT || - node->type == NODE_FLOAT || - node->type == NODE_BOOL || - node->type == NODE_NIL - ); -} - -static inline bool isFoldable(Node *node) { - return ( - isConst(node) || - node->type == NODE_UNOP - ); -} - -static inline bool isUnOp(Node *node) { - return node->type == NODE_UNOP; -} - -static inline TypeKind getTypeKind(Node *node) { - return node->valType.kind; -} - -static inline bool eitherAre(Node *a, Node *b, TypeKind kind) { - return getTypeKind(a) == kind || getTypeKind(b) == kind; -} - -static double getUnOpValue(Node *node); -static double getNumberValue(Node *node); - -static double getUnOpValue(Node *node) { - UnOp unOp = NODE_AS_UNOP(node); - - switch (unOp.op.type) { - case TOK_MINUS: - return -getNumberValue(unOp.operand); - - default: - return getNumberValue(unOp.operand); - } -} - -static double getNumberValue(Node *node) { - if (node->type == NODE_UNOP) { - return getUnOpValue(node); - } - - if (getTypeKind(node) == TYPE_FLOAT) { - return NODE_AS_FLOAT(node).value; - } - - return (double)NODE_AS_INT(node).value; -} - -static bool getBoolValue(Node *node) { - if (node->type == NODE_UNOP) { - UnOp unOp = NODE_AS_UNOP(node); - - switch (unOp.op.type) { - case TOK_NOT: - return !getBoolValue(unOp.operand); - - default: - return getBoolValue(unOp.operand); - } - } - - return NODE_AS_BOOL(node).value; -} - -// please always check if both a and b are numbers before using this function -static inline bool eitherNumsEq(Node *a, Node *b, double value) { - return getNumberValue(a) == value || getNumberValue(b) == value; -} - -static void constFoldInt(Node *node) { - BinOp *binOp = &NODE_AS_BINOP(node); - - long leftValue = (long)getNumberValue(binOp->left); - long rightValue = (long)getNumberValue(binOp->right); - - Loc loc = binOp->op.loc; - TokType op = binOp->op.type; - - long result; - switch (op) { - case TOK_PLUS: - result = leftValue + rightValue; - break; - - case TOK_MINUS: - result = leftValue - rightValue; - break; - - case TOK_STAR: - result = leftValue * rightValue; - break; - - case TOK_SLASH: - result = leftValue / rightValue; - break; - - case TOK_SHL: - result = leftValue << rightValue; - break; - - case TOK_SHR: - result = leftValue >> rightValue; - break; - - case TOK_BIT_AND: - result = leftValue & rightValue; - break; - - case TOK_BIT_XOR: - result = leftValue ^ rightValue; - break; - - case TOK_PIPE: - result = leftValue | rightValue; - break; - - default: - // this shouldn’t ever happen; but the compiler complains - // if there’s no default case - result = -1L; - break; - } - - freeNode(binOp->left); - freeNode(binOp->right); - - node->type = NODE_INT; - - Int resultNode = { - .value = result, - .loc = loc - }; - - node->as.i = resultNode; -} - -static void constFoldFloat(Node *node) { - BinOp *binOp = &NODE_AS_BINOP(node); - - double leftValue = getNumberValue(binOp->left); - double rightValue = getNumberValue(binOp->right); - - Loc loc = binOp->op.loc; - TokType op = binOp->op.type; - - double result; - switch (op) { - case TOK_PLUS: - result = leftValue + rightValue; - break; - - case TOK_MINUS: - result = leftValue - rightValue; - break; - - case TOK_STAR: - result = leftValue * rightValue; - break; - - case TOK_SLASH: - result = leftValue / rightValue; - break; - - default: - // this shouldn’t ever happen; but the compiler complains - // if there’s no default case - result = -1L; - break; - } - - freeNode(binOp->left); - freeNode(binOp->right); - - node->type = NODE_FLOAT; - - Float resultNode = { - .value = result, - .loc = loc - }; - - node->as.f = resultNode; -} - -static void constFoldBool(Node *node) { - BinOp *binOp = &NODE_AS_BINOP(node); - - Tok op = binOp->op; - - Node *left = binOp->left; - Node *right = binOp->right; - - if (op.type == TOK_EQUAL || op.type == TOK_NEQUAL) { - if ( - !isNum(node) && - !typesMatch(left->valType, right->valType) - ) { - node->type = NODE_BOOL; - - Bool falseNode = { - .value = op.type == TOK_NEQUAL, - .loc = op.loc - }; - - freeNode(left); - freeNode(right); - - node->as.b = falseNode; - return; - } - - if (getTypeKind(left) == TYPE_NIL) { - freeNode(left); - freeNode(right); - - node->type = NODE_BOOL; - - Bool result = { - .value = true, - .loc = op.loc - }; - - node->as.b = result; - } - - if (getTypeKind(left) == TYPE_BOOL) { - const bool leftValue = getBoolValue(left); - const bool rightValue = getBoolValue(right); - - const bool equal = rightValue && leftValue; - - freeNode(left); - freeNode(right); - - node->type = NODE_BOOL; - - Bool result = { - .value = op.type == TOK_NEQUAL ? !equal : equal, - .loc = op.loc - }; - - node->as.b = result; - return; - } - - if (isNum(left)) { - const double leftValue = getNumberValue(left); - const double rightValue = getNumberValue(right); - - const bool equal = leftValue == rightValue; - - freeNode(left); - freeNode(right); - - node->type = NODE_BOOL; - - Bool result = { - .value = op.type == TOK_NEQUAL ? !equal : equal, - .loc = op.loc - }; - - node->as.b = result; - - return; - } - } else { - const double leftValue = getNumberValue(left); - const double rightValue = getNumberValue(right); - - bool result; - - switch (op.type) { - case TOK_GREATER: - result = leftValue > rightValue; - break; - - case TOK_LESS: - result = leftValue < rightValue; - break; - - case TOK_GREATER_EQUAL: - result = leftValue >= rightValue; - break; - - case TOK_LESS_EQUAL: - result = leftValue <= rightValue; - break; - - default: - result = false; - break; - } - - freeNode(left); - freeNode(right); - - node->type = NODE_BOOL; - - Bool resultNode = { - .value = result, - .loc = op.loc - }; - - node->as.b = resultNode; - return; - } -} - -static void constFold(Node *node) { - // the type is always known before optimization, so there’s no need - // to call inferType(). - switch (getTypeKind(node)) { - case TYPE_INT: - constFoldInt(node); - break; - - case TYPE_FLOAT: - constFoldFloat(node); - break; - - case TYPE_BOOL: - constFoldBool(node); - break; - - default: - break; - } -} - -static void eqSpecialization(Node *node) { - BinOp binOp = NODE_AS_BINOP(node); - - Tok op = binOp.op; - - Node *left = binOp.left; - Node *right = binOp.right; - - const bool isNeg = op.type == TOK_NEQUAL; - - if (eitherAre(left, right, TYPE_NIL)) { - const bool isLeft = getTypeKind(left) == TYPE_NIL; - - node->type = NODE_UNOP; - - UnOp unOp = { - .op = binOp.op, - .opType = UNOP_IS_NIL | (isNeg && UNOP_NEG), - .operand = isLeft ? left : right - }; - - freeNode(isLeft ? right : left); - - node->as.unOp = unOp; - return; - } - - // we can assume that left and right have the same type - // because we always do a typesMatch() check before getting here - if (!isNum(left)) { - return; - } - - if (eitherNumsEq(left, right, 0.0F) || eitherNumsEq(left, right, -1.0F)) { - const double leftValue = getNumberValue(left); - - // the edge case where the binOp looks like this: - // -1 == 0 - // is impossible because it would’ve been constant folded earlier, - // and so we’d be left with a ( Bool false ) node. - const bool isLeft = leftValue == 0.0F || leftValue == -1.0F; - const bool isZero = leftValue == 0.0F; - - const UnOpType opType = isZero ? UNOP_IS_ZERO : UNOP_IS_NEG_ONE; - - node->type = NODE_UNOP; - - UnOp unOp = { - .op = binOp.op, - .opType = opType | isNeg && UNOP_NEG, - .operand = isLeft ? left : right - }; - - freeNode(isLeft ? right : left); - - node->as.unOp = unOp; - } -} - -void optNode(Node *node) { - switch (node->type) { - case NODE_BINOP: - optBinOp(node); - break; - - case NODE_UNOP: - optUnOp(node); - break; - - case NODE_INT: - case NODE_FLOAT: - case NODE_BOOL: - case NODE_NIL: - break; - } -} - -void optBinOp(Node *node) { - // TODO: implement unary canonicalization - BinOp *binOp = &NODE_AS_BINOP(node); - - Tok op = binOp->op; - - Node *left = binOp->left; - Node *right = binOp->right; - - optNode(left); - optNode(right); - - if (isFoldable(binOp->left) && isFoldable(binOp->right)) { - constFold(node); - } - - if (node->type != NODE_BINOP) { - return; - } - - // TODO: implement strength reduction once we have variables - - if (op.type == TOK_EQUAL || op.type == TOK_NEQUAL) { - eqSpecialization(node); - } -} - -void optUnOp(Node *node) { - UnOp *unOp = &NODE_AS_UNOP(node); - - Node *nodeOperand = unOp->operand; - UnOp *operand = &NODE_AS_UNOP(unOp->operand); - - optNode(unOp->operand); - - if (isUnOp(unOp->operand) && operand->op.type == unOp->op.type) { - node->type = operand->operand->type; - node->as = operand->operand->as; - - freeNode(nodeOperand); - } - - if (isConst(unOp->operand)) { - TypeKind kind = getTypeKind(unOp->operand); - - switch (kind) { - case TYPE_BOOL: - case TYPE_NIL: { - // we can assume that the operand is `not` thanks to prior type - // checking - const bool operandValue = getBoolValue(unOp->operand); - - freeNode(unOp->operand); - - node->type = NODE_BOOL; - - Bool negated = { - .value = !operandValue, - .loc = unOp->op.loc - }; - - node->as.b = negated; - break; - } - - case TYPE_INT: { - // else, we can assume that the operator is `-` - const long operandValue = (long)getNumberValue(unOp->operand); - - freeNode(unOp->operand); - - node->type = NODE_INT; - - Int simplified = { - .value = -operandValue, - .loc = unOp->op.loc - }; - - node->as.i = simplified; - break; - } - - case TYPE_FLOAT: { - const double operandValue = getNumberValue(unOp->operand); - - freeNode(unOp->operand); - - node->type = NODE_FLOAT; - - Float simplified = { - .value = -operandValue, - .loc = unOp->op.loc - }; - - node->as.f = simplified; - break; - } - - default: - break; - } - } -} From b8245e97ad3fd140cdaa12fd862302db566fc3a6 Mon Sep 17 00:00:00 2001 From: ademyrodev Date: Mon, 19 Aug 2024 02:01:57 +0200 Subject: [PATCH 09/10] fix a couple oversights --- src/compiler/compiler.c | 2 +- src/compiler/opt.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index e141c0d..1f856af 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -466,7 +466,7 @@ static Node *unary(Ctx *ctx) { unaryNegationErr(ctx, op, tok, operand); } - return newUnOp(ctx->types, op, UNOP_NEG, operand); + return newUnOp(ctx->types, op, UNOP_NOT, operand); } default: diff --git a/src/compiler/opt.c b/src/compiler/opt.c index b45c5f6..5eaec5b 100644 --- a/src/compiler/opt.c +++ b/src/compiler/opt.c @@ -334,7 +334,7 @@ static void eqSpecialization(Node *node) { UnOp unOp = { .op = binOp.op, - .opType = UNOP_IS_NIL | (isNeg && UNOP_NEG), + .opType = isNeg ? UNOP_IS_NIL : UNOP_IS_NIL | UNOP_NEG, .operand = isLeft ? left : right }; @@ -367,7 +367,7 @@ static void eqSpecialization(Node *node) { UnOp unOp = { .op = binOp.op, - .opType = opType | isNeg && UNOP_NEG, + .opType = isNeg ? opType : opType | UNOP_NEG, .operand = isLeft ? left : right }; From 7d8a261e20230b02f56843b0fc806a7cfc91ffad Mon Sep 17 00:00:00 2001 From: ademyrodev Date: Mon, 19 Aug 2024 02:13:26 +0200 Subject: [PATCH 10/10] make Nil type incompatible with 'not' & fix example in test/expressions/test.neve --- src/compiler/compiler.c | 2 +- src/ir/ir.c | 6 ++++++ test/expressions/test.neve | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index 1f856af..e930bba 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -460,7 +460,7 @@ static Node *unary(Ctx *ctx) { } case TOK_NOT: { - if (!checkType(operand, TYPE_BOOL) && !checkType(operand, TYPE_NIL)) { + if (!checkType(operand, TYPE_BOOL)) { Tok tok = ctx->parser.prev; unaryNegationErr(ctx, op, tok, operand); diff --git a/src/ir/ir.c b/src/ir/ir.c index 1ad831c..256f17c 100644 --- a/src/ir/ir.c +++ b/src/ir/ir.c @@ -24,6 +24,12 @@ static void freeBinOp(BinOp *node) { } static Type inferUnOp(TypeTable *table, UnOp node) { + Tok op = node.op; + + if (op.type == TOK_NOT) { + return *table->boolType; + } + return inferType(table, node.operand); } diff --git a/test/expressions/test.neve b/test/expressions/test.neve index 749a259..06d8273 100644 --- a/test/expressions/test.neve +++ b/test/expressions/test.neve @@ -1 +1 @@ -false & 1 +not (5 - 4 > 3 * 2 == true)