/* +--------------------------------------------------------------------------+ ** | _ _ _ | ** | | \ | | (_) | ** | | \| | ___ _ __ _ | ** | | . ` |/ _ \| |/ _` | | ** | | |\ | (_) | | (_| | | ** | |_| \_|\___/| |\__,_| | ** | _/ | | ** | |__/ | ** +--------------------------------------------------------------------------+ ** | Copyright (c) 2022 Francesco Cozzuto | ** +--------------------------------------------------------------------------+ ** | This file is part of The Noja Interpreter. | ** | | ** | The Noja Interpreter is free software: you can redistribute it and/or | ** | modify it under the terms of the GNU General Public License as published | ** | by the Free Software Foundation, either version 3 of the License, or (at | ** | your option) any later version. | ** | | ** | The Noja Interpreter is distributed in the hope that it will be useful, | ** | but WITHOUT ANY WARRANTY; without even the implied warranty of | ** | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General | ** | Public License for more details. | ** | | ** | You should have received a copy of the GNU General Public License along | ** | with The Noja Interpreter. If not, see . | ** +--------------------------------------------------------------------------+ ** | WHAT IS THIS FILE? | ** | | ** | This file implements the routines that transform the AST into a list of | ** | bytecodes. The functionalities of this file are exposed through the | ** | `compile` function, that takes as input an `AST` and outputs an | ** | `Executable`. | ** | | ** | The function that does the heavy lifting is `emitInstrForNode` which | ** | walks the tree and writes instructions to the `ExeBuilder`. | ** | | ** | Some semantic errors are catched at this phase, in which case, they are | ** | reported by filling out the `error` structure and aborting. It's also | ** | possible that the compilation fails bacause of internal errors (which | ** | usually means "out of memory"). | ** +--------------------------------------------------------------------------+ */ #include #include #include #include "../utils/defs.h" #include "codegenctx.h" #include "compile.h" #include "ASTi.h" static void emitInstr_POP(CodegenContext *ctx, long long int op0, int off, int len) { Operand opv[1] = { { .type = OPTP_INT, .as_int = op0 } }; CodegenContext_EmitInstr(ctx, OPCODE_POP, opv, 1, off, len); } static void emitInstr_POP1(CodegenContext *ctx, int off, int len) { emitInstr_POP(ctx, 1, off, len); } static void emitInstr_ASS(CodegenContext *ctx, const char *name, int off, int len) { Operand opv[] = { { .type = OPTP_STRING, .as_string = name }, }; CodegenContext_EmitInstr(ctx, OPCODE_ASS, opv, 1, off, len); } static void emitInstr_RETURN(CodegenContext *ctx, long long int op0, int off, int len) { Operand opv[1] = { { .type = OPTP_INT, .as_int = op0 } }; CodegenContext_EmitInstr(ctx, OPCODE_RETURN, opv, 1, off, len); } static void emitInstr_JUMP(CodegenContext *ctx, Label *op0, int off, int len) { Operand opv[1] = { { .type = OPTP_PROMISE, .as_promise = Label_ToPromise(op0) } }; CodegenContext_EmitInstr(ctx, OPCODE_JUMP, opv, 1, off, len); } static void emitInstr_JUMPIFNOTANDPOP(CodegenContext *ctx, Label *op0, int off, int len) { Operand opv[1] = { { .type = OPTP_PROMISE, .as_promise = Label_ToPromise(op0) } }; CodegenContext_EmitInstr(ctx, OPCODE_JUMPIFNOTANDPOP, opv, 1, off, len); } static void emitInstr_JUMPIFANDPOP(CodegenContext *ctx, long long int op0, int off, int len) { Operand opv[1] = { { .type = OPTP_INT, .as_int = op0 } }; CodegenContext_EmitInstr(ctx, OPCODE_JUMPIFANDPOP, opv, 1, off, len); } static void emitInstr_JUMPIFANDPOP_2(CodegenContext *ctx, Label *op0, int off, int len) { Operand opv[1] = { { .type = OPTP_PROMISE, .as_promise = Label_ToPromise(op0) } }; CodegenContext_EmitInstr(ctx, OPCODE_JUMPIFANDPOP, opv, 1, off, len); } static void emitInstr_PUSHTRU(CodegenContext *ctx, int off, int len) { CodegenContext_EmitInstr(ctx, OPCODE_PUSHTRU, NULL, 0, off, len); } static void emitInstr_PUSHFLS(CodegenContext *ctx, int off, int len) { CodegenContext_EmitInstr(ctx, OPCODE_PUSHFLS, NULL, 0, off, len); } static void emitInstrForNode(CodegenContext *ctx, Node *node, Label *label_break); static Opcode exprkind_to_opcode(ExprKind kind) { switch(kind) { case EXPR_NOT: return OPCODE_NOT; case EXPR_POS: return OPCODE_POS; case EXPR_NEG: return OPCODE_NEG; case EXPR_ADD: return OPCODE_ADD; case EXPR_SUB: return OPCODE_SUB; case EXPR_MUL: return OPCODE_MUL; case EXPR_DIV: return OPCODE_DIV; case EXPR_EQL: return OPCODE_EQL; case EXPR_NQL: return OPCODE_NQL; case EXPR_LSS: return OPCODE_LSS; case EXPR_LEQ: return OPCODE_LEQ; case EXPR_GRT: return OPCODE_GRT; case EXPR_GEQ: return OPCODE_GEQ; case EXPR_AND: return OPCODE_AND; case EXPR_OR: return OPCODE_OR; default: UNREACHABLE; break; } UNREACHABLE; return -1; } static void emitInstrForFuncCallNode(CodegenContext *ctx, CallExprNode *expr, Label *label_break, int returns) { Node *arg = expr->argv; while(arg) { emitInstrForNode(ctx, arg, label_break); arg = arg->next; } emitInstrForNode(ctx, expr->func, label_break); Operand ops[2]; ops[0] = (Operand) { .type = OPTP_INT, .as_int = expr->argc }; ops[1] = (Operand) { .type = OPTP_INT, .as_int = returns }; CodegenContext_EmitInstr(ctx, OPCODE_CALL, ops, 2, expr->base.base.offset, expr->base.base.length); } static void emitInstrForFuncNode(CodegenContext *ctx, FunctionNode *func) { Label *label_func = Label_New(ctx); Label *label_jump = Label_New(ctx); // Push function. { Operand ops[2] = { { .type = OPTP_PROMISE, .as_promise = Label_ToPromise(label_func) }, { .type = OPTP_INT, .as_int = func->argc }, }; CodegenContext_EmitInstr(ctx, OPCODE_PUSHFUN, ops, 2, func->base.offset, func->base.length); } emitInstr_ASS(ctx, func->name, func->base.offset, func->base.length); // Assign variable emitInstr_POP1(ctx, func->base.offset, func->base.length); // Pop function object emitInstr_JUMP(ctx, label_jump, func->base.offset, func->base.length); // Jump after the function code Label_SetHere(label_func, ctx); // This is the function code index. // Compile the function body. { // Assign the arguments. ArgumentNode *arg = (ArgumentNode*) func->argv; while(arg) { emitInstr_ASS(ctx, arg->name, arg->base.offset, arg->base.length); emitInstr_POP1(ctx, arg->base.offset, arg->base.length); arg = (ArgumentNode*) arg->base.next; } emitInstrForNode(ctx, func->body, NULL); if(func->body->kind == NODE_EXPR) emitInstr_POP1(ctx, func->body->offset + func->body->length, 0); // Write a return instruction, just // in case it didn't already return. emitInstr_RETURN(ctx, 0, func->body->offset, 0); } // This is the first index after the function code. Label_SetHere(label_jump, ctx); Label_Free(label_func); Label_Free(label_jump); } static void flattenTupleTree(CodegenContext *ctx, ExprNode *root, ExprNode *tuple[], int max, int *count) { if(root->kind == EXPR_PAIR) { flattenTupleTree(ctx, (ExprNode*) ((OperExprNode*) root)->head, tuple, max, count); flattenTupleTree(ctx, (ExprNode*) ((OperExprNode*) root)->head->next, tuple, max, count); } else { if(max == *count) { CodegenContext_ReportErrorAndJump(ctx, 0, "Static buffer is too small"); UNREACHABLE; } tuple[(*count)++] = root; } } static void emitInstrForAssignmentNode(CodegenContext *ctx, OperExprNode *asgn, Label *label_break) { Node *lop, *rop; lop = asgn->head; rop = lop->next; ExprNode *tuple[32]; int count = 0; flattenTupleTree(ctx, (ExprNode*) lop, tuple, sizeof(tuple)/sizeof(tuple[0]), &count); assert(count > 0); if(count == 1) /* No tuple. */ emitInstrForNode(ctx, rop, label_break); else { if(((ExprNode*) rop)->kind == EXPR_CALL) emitInstrForFuncCallNode(ctx, (CallExprNode*) rop, label_break, count); else { CodegenContext_ReportErrorAndJump(ctx, 0, "Assigning to %d variables only 1 value", count); UNREACHABLE; } } for(int i = 0; i < count; i += 1) { ExprNode *tuple_item = tuple[count-i-1]; switch(tuple_item->kind) { case EXPR_IDENT: { const char *name = ((IdentExprNode*) tuple_item)->val; emitInstr_ASS(ctx, name, tuple_item->base.offset, tuple_item->base.length); break; } case EXPR_SELECT: { Node *idx = ((IndexSelectionExprNode*) tuple_item)->idx; Node *set = ((IndexSelectionExprNode*) tuple_item)->set; emitInstrForNode(ctx, set, label_break); emitInstrForNode(ctx, idx, label_break); CodegenContext_EmitInstr(ctx, OPCODE_INSERT2, NULL, 0, tuple_item->base.offset, tuple_item->base.length); break; } default: CodegenContext_ReportErrorAndJump(ctx, 0, "Assigning to something that it can't be assigned to"); UNREACHABLE; } if(i+1 < count) emitInstr_POP1(ctx, asgn->base.base.offset, 0); } } static void emitInstrForExprNode(CodegenContext *ctx, ExprNode *expr, Label *label_break) { switch(expr->kind) { case EXPR_PAIR: CodegenContext_ReportErrorAndJump(ctx, 0, "Tuple outside of assignment or return statement"); UNREACHABLE; return; // For the compiler warning. case EXPR_NOT: case EXPR_POS: case EXPR_NEG: case EXPR_ADD: case EXPR_SUB: case EXPR_MUL: case EXPR_DIV: case EXPR_EQL: case EXPR_NQL: case EXPR_LSS: case EXPR_LEQ: case EXPR_GRT: case EXPR_GEQ: { OperExprNode *oper = (OperExprNode*) expr; for(Node *operand = oper->head; operand; operand = operand->next) emitInstrForNode(ctx, operand, label_break); CodegenContext_EmitInstr(ctx, exprkind_to_opcode(expr->kind), NULL, 0, expr->base.offset, expr->base.length); return; } case EXPR_AND: { OperExprNode *oper = (OperExprNode*) expr; /* * * JUMPIFNOTANDPOP false; * * JUMPIFNOTANDPOP false; * PUSHTRU; * JUMP end; * false: * PUSHFLS; * end: * */ Label *label_end = Label_New(ctx); Label *label_false = Label_New(ctx); for(Node *operand = oper->head; operand; operand = operand->next) { emitInstrForNode(ctx, operand, label_break); emitInstr_JUMPIFNOTANDPOP(ctx, label_false, expr->base.offset, expr->base.length); } emitInstr_PUSHTRU(ctx, expr->base.offset, expr->base.length); emitInstr_JUMP(ctx, label_end, expr->base.offset, expr->base.length); Label_SetHere(label_false, ctx); emitInstr_PUSHFLS(ctx, expr->base.offset, expr->base.length); Label_SetHere(label_end, ctx); return; } case EXPR_OR: { OperExprNode *oper = (OperExprNode*) expr; /* * * JUMPIFANDPOP true; * * JUMPIFANDPOP true; * PUSHFLS; * JUMP end; * true: * PUSHTRU; * JUMP end; * end: * */ Label *label_end = Label_New(ctx); Label *label_true = Label_New(ctx); for(Node *operand = oper->head; operand; operand = operand->next) { emitInstrForNode(ctx, operand, label_break); emitInstr_JUMPIFANDPOP_2(ctx, label_true, expr->base.offset, expr->base.length); } emitInstr_PUSHFLS(ctx, expr->base.offset, expr->base.length); emitInstr_JUMP(ctx, label_end, expr->base.offset, expr->base.length); Label_SetHere(label_true, ctx); emitInstr_PUSHTRU(ctx, expr->base.offset, expr->base.length); Label_SetHere(label_end, ctx); return; } case EXPR_ASS: emitInstrForAssignmentNode(ctx, (OperExprNode*) expr, label_break); return; case EXPR_INT: { IntExprNode *p = (IntExprNode*) expr; Operand op = { .type = OPTP_INT, .as_int = p->val }; CodegenContext_EmitInstr(ctx, OPCODE_PUSHINT, &op, 1, expr->base.offset, expr->base.length); return; } case EXPR_FLOAT: { FloatExprNode *p = (FloatExprNode*) expr; Operand op = { .type = OPTP_FLOAT, .as_float = p->val }; CodegenContext_EmitInstr(ctx, OPCODE_PUSHFLT, &op, 1, expr->base.offset, expr->base.length); return; } case EXPR_STRING: { StringExprNode *p = (StringExprNode*) expr; Operand op = { .type = OPTP_STRING, .as_string = p->val }; CodegenContext_EmitInstr(ctx, OPCODE_PUSHSTR, &op, 1, expr->base.offset, expr->base.length); return; } case EXPR_IDENT: { IdentExprNode *p = (IdentExprNode*) expr; Operand op = { .type = OPTP_STRING, .as_string = p->val }; CodegenContext_EmitInstr(ctx, OPCODE_PUSHVAR, &op, 1, expr->base.offset, expr->base.length); return; } case EXPR_LIST: { // PUSHLST // PUSHINT // // INSERT ListExprNode *l = (ListExprNode*) expr; Operand op; op = (Operand) { .type = OPTP_INT, .as_int = l->itemc }; CodegenContext_EmitInstr(ctx, OPCODE_PUSHLST, &op, 1, expr->base.offset, expr->base.length); Node *item = l->items; int i = 0; while(item) { op = (Operand) { .type = OPTP_INT, .as_int = i }; CodegenContext_EmitInstr(ctx, OPCODE_PUSHINT, &op, 1, item->offset, item->length); emitInstrForNode(ctx, item, label_break); CodegenContext_EmitInstr(ctx, OPCODE_INSERT, NULL, 0, item->offset, item->length); i += 1; item = item->next; } return; } case EXPR_MAP: { MapExprNode *m = (MapExprNode*) expr; Operand op; op = (Operand) { .type = OPTP_INT, .as_int = m->itemc }; CodegenContext_EmitInstr(ctx, OPCODE_PUSHMAP, &op, 1, expr->base.offset, expr->base.length); Node *key = m->keys; Node *item = m->items; while(item) { emitInstrForNode(ctx, key, label_break); emitInstrForNode(ctx, item, label_break); CodegenContext_EmitInstr(ctx, OPCODE_INSERT, NULL, 0, item->offset, item->length); key = key->next; item = item->next; } return; } case EXPR_CALL: emitInstrForFuncCallNode(ctx, (CallExprNode*) expr, label_break, 1); return; case EXPR_SELECT: { IndexSelectionExprNode *sel = (IndexSelectionExprNode*) expr; emitInstrForNode(ctx, sel->set, label_break); emitInstrForNode(ctx, sel->idx, label_break); CodegenContext_EmitInstr(ctx, OPCODE_SELECT, NULL, 0, expr->base.offset, expr->base.length); return; } case EXPR_NONE: CodegenContext_EmitInstr(ctx, OPCODE_PUSHNNE, NULL, 0, expr->base.offset, expr->base.length); return; case EXPR_TRUE: CodegenContext_EmitInstr(ctx, OPCODE_PUSHTRU, NULL, 0, expr->base.offset, expr->base.length); return; case EXPR_FALSE: CodegenContext_EmitInstr(ctx, OPCODE_PUSHFLS, NULL, 0, expr->base.offset, expr->base.length); return; default: UNREACHABLE; break; } } static void emitInstrForIfElseNode(CodegenContext *ctx, IfElseNode *ifelse, Label *label_break) { emitInstrForNode(ctx, ifelse->condition, label_break); if(ifelse->false_branch) { Label *label_else = Label_New(ctx); Label *label_done = Label_New(ctx); emitInstr_JUMPIFNOTANDPOP(ctx, label_else, ifelse->condition->offset, ifelse->condition->length); emitInstrForNode(ctx, ifelse->true_branch, label_break); if(ifelse->true_branch->kind == NODE_EXPR) emitInstr_POP(ctx, 1, ifelse->true_branch->offset, 0); emitInstr_JUMP(ctx, label_done, ifelse->base.offset, ifelse->base.length); Label_SetHere(label_else, ctx); emitInstrForNode(ctx, ifelse->false_branch, label_break); if(ifelse->false_branch->kind == NODE_EXPR) emitInstr_POP1(ctx, ifelse->false_branch->offset, 0); Label_SetHere(label_done, ctx); Label_Free(label_else); Label_Free(label_done); } else { Label *label_done = Label_New(ctx); emitInstr_JUMPIFNOTANDPOP(ctx, label_done, ifelse->condition->offset, ifelse->condition->length); emitInstrForNode(ctx, ifelse->true_branch, label_break); if(ifelse->true_branch->kind == NODE_EXPR) emitInstr_POP1(ctx, ifelse->true_branch->offset, 0); Label_SetHere(label_done, ctx); Label_Free(label_done); } } static void emitInstrForWhileLoopNode(CodegenContext *ctx, WhileNode *loop, Label *label_break) { /* * start: * * JUMPIFNOTANDPOP end * * JUMP start * end: */ Label *label_start = Label_New(ctx); Label *label_end = Label_New(ctx); Label_SetHere(label_start, ctx); emitInstrForNode(ctx, loop->condition, label_break); emitInstr_JUMPIFNOTANDPOP(ctx, label_end, loop->condition->offset, loop->condition->length); emitInstrForNode(ctx, loop->body, label_end); if(loop->body->kind == NODE_EXPR) emitInstr_POP1(ctx, loop->body->offset, 0); emitInstr_JUMP(ctx, label_start, loop->base.offset, loop->base.length); Label_SetHere(label_end, ctx); Label_Free(label_start); Label_Free(label_end); } static void emitInstrForDoWhileLoopNode(CodegenContext *ctx, DoWhileNode *loop, Label *label_break) { /* * start: * * * JUMPIFANDPOP start */ Label *label_end = Label_New(ctx); long long int start = CodegenContext_InstrCount(ctx); emitInstrForNode(ctx, loop->body, label_end); if(loop->body->kind == NODE_EXPR) emitInstr_POP1(ctx, loop->body->offset, 0); emitInstrForNode(ctx, loop->condition, label_break); emitInstr_JUMPIFANDPOP(ctx, start, loop->condition->offset, loop->condition->length); Label_SetHere(label_end, ctx); Label_Free(label_end); } static void emitInstrForNode(CodegenContext *ctx, Node *node, Label *label_break) { assert(node != NULL); switch(node->kind) { case NODE_EXPR: emitInstrForExprNode(ctx, (ExprNode*) node, label_break); return; case NODE_BREAK: if(label_break == NULL) CodegenContext_ReportErrorAndJump(ctx, 0, "Break not inside a loop"); emitInstr_JUMP(ctx, label_break, node->offset, node->length); return; case NODE_IFELSE: emitInstrForIfElseNode(ctx, (IfElseNode*) node, label_break); return; case NODE_WHILE: emitInstrForWhileLoopNode(ctx, (WhileNode*) node, label_break); return; case NODE_DOWHILE: emitInstrForDoWhileLoopNode(ctx, (DoWhileNode*) node, label_break); return; case NODE_COMP: { CompoundNode *comp = (CompoundNode*) node; Node *stmt = comp->head; while(stmt) { emitInstrForNode(ctx, stmt, label_break); if(stmt->kind == NODE_EXPR) emitInstr_POP1(ctx, stmt->offset, 0); stmt = stmt->next; } return; } case NODE_RETURN: { ReturnNode *ret = (ReturnNode*) node; ExprNode *tuple[32]; int count = 0; flattenTupleTree(ctx, (ExprNode*) ret->val, tuple, sizeof(tuple)/sizeof(tuple[0]), &count); for(int i = 0; i < count; i += 1) emitInstrForNode(ctx, (Node*) tuple[i], label_break); emitInstr_RETURN(ctx, count, ret->base.offset, ret->base.length); return; } case NODE_FUNC: emitInstrForFuncNode(ctx, (FunctionNode*) node); return; default: UNREACHABLE; } UNREACHABLE; } /* Symbol: codegen * * Serializes an AST into bytecode format. * * * Arguments: * * ast: The AST to be serialized. * alloc: The allocator that will be used to get new * memory. (optional) * error: Error information structure that is filled out if * an error occurres. * * * Returns: * A pointer to an `Executable` that is the object that * contains the bytecode. If an error occurres, NULL is * returned and the `error` structure is filled out. * */ Executable *codegen(AST *ast, BPAlloc *alloc, Error *error) { assert(ast != NULL); assert(error != NULL); CodegenContext *ctx = CodegenContext_New(error, alloc); if(ctx == NULL) { Error_Report(error, 1, "No memory"); return NULL; } bool jumped = CodegenContext_SetOrCatchJump(ctx); if(jumped) { CodegenContext_Free(ctx); return NULL; } emitInstrForNode(ctx, ast->root, NULL); emitInstr_RETURN(ctx, 0, Source_GetSize(ast->src), 0); return CodegenContext_MakeExecutableAndFree(ctx, ast->src); }