diff -r b029d63565a2 -r c1ba166f3fc4 src/BytecodeCompiler.cpp --- a/src/BytecodeCompiler.cpp Mon Sep 17 00:10:01 2012 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1576 +0,0 @@ -/* vim: set ts=4 sw=4 tw=99 et: - * - * Copyright (C) 2012 David Anderson - * - * This file is part of JITCraft. - * - * JITCraft 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. - * - * Foobar 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 - * JITCraft. If not, see http://www.gnu.org/licenses/. - */ -#include -#include "BytecodeCompiler.h" -#include "Scopes.h" -#include "Modules.h" -#include "Types.h" -#include "Array.h" -#include "Strings.h" -#include "Zone.h" -#include "Environments.h" -#include "Box.h" -#include "Heap-inl.h" - -using namespace ke; - -typedef BytecodeEmitter::Label Label; - -#define __ emitter_. - -BytecodeCompiler::BytecodeCompiler(Zone *zone, ParseTree *tree, ModuleData *module) - : zone_(zone), - tree_(tree), - emitter_(zone->pool()), - locals_(NULL), - scope_(NULL), - moduleData_(module), - loop_(NULL) -{ -} - -static inline TokenKind -ToCompare(Expression *expr) -{ - if (expr->isBinaryExpression()) { - BinaryExpression *b = expr->toBinaryExpression(); - if (b->token() >= TOK_EQUALS && b->token() <= TOK_OR) - return b->token(); - } else if (expr->isUnaryExpression()) { - UnaryExpression *u = expr->toUnaryExpression(); - if (u->token() == TOK_NOT) - return TOK_NOT; - } - return TOK_EOF; -} - -static inline Opcode -CompareTokenToJump(TokenKind kind) -{ - switch (kind) { - case TOK_EQUALS: - return OP_JEQ; - case TOK_NOTEQUALS: - return OP_JNE; - case TOK_GT: - return OP_JG; - case TOK_GE: - return OP_JGE; - case TOK_LT: - return OP_JL; - default: - assert(kind == TOK_LE); - return OP_JLE; - } -} - -static inline Opcode -TokenToOpcode(TokenKind kind) -{ - switch (kind) { - case TOK_PLUS: - return OP_ADD; - case TOK_MINUS: - return OP_SUB; - case TOK_STAR: - return OP_MUL; - case TOK_SLASH: - return OP_DIV; - case TOK_PERCENT: - return OP_MOD; - case TOK_SHR: - return OP_SHR; - case TOK_USHR: - return OP_USHR; - case TOK_SHL: - return OP_SHL; - case TOK_BITAND: - return OP_BITAND; - case TOK_BITOR: - return OP_BITOR; - case TOK_GT: - return OP_GT; - case TOK_GE: - return OP_GE; - case TOK_LT: - return OP_LT; - case TOK_LE: - return OP_LE; - case TOK_EQUALS: - return OP_EQ; - case TOK_NOTEQUALS: - return OP_NE; - default: - assert(kind == TOK_BITXOR); - return OP_BITXOR; - } -} - -void -BytecodeCompiler::recordLifoSlot(Scope *scope) -{ - if (!scope) - return; - - if (Variable *var = scope->lifoSlot()) { - locals_->set(zone_, var->slot(), var->type()); - __ savelifo(var->slot()); - } -} - -void -BytecodeCompiler::unwindTo(Scope *scope) -{ - // Note: we only need to unwind to the outermost scope's LIFO slot. - Variable *var = NULL; - for (Scope *current = scope_; current != scope; current = current->enclosing()) { - if (Variable *slot = current->lifoSlot()) - var = slot; - } - - if (var) - __ unwindlifo(var->slot()); -} - -void -BytecodeCompiler::visit(NameProxy *name) -{ - switch (name->binding()->kind()) { - case BoundName::VARIABLE: - { - Variable *var = name->binding()->toVariable(); - - if (var->location() == Variable::STACK) { - __ getlocal(var); - assert(!name->type()->isReference()); - } else if (var->location() == Variable::ENVIRONMENT) { - getEnvSlot(var); - } else { - assert(var->location() == Variable::PARAMETER); - __ getarg(var); - - // If this argument is actually a reference, dereference it now. - if (var->type()->isReference()) - __ getref(var->type()->contained()); - } - break; - } - - case BoundName::FUNCTION: - { - NamedFunction *fun = name->binding()->toNamedFunction(); - __ module(); - __ getmodule_fn(fun->slot()); - break; - } - - default: - { - NamedConstant *constant = name->binding()->toNamedConstant(); - __ int_(constant->value().toInt()); - break; - } - } -} - -void -BytecodeCompiler::visit(ExpressionStatement *node) -{ - // Expressions leave their value on the top of the stack, so we make sure - // to balance the stack if they're used as a statement. - node->expression()->accept(this); - if (!node->expression()->type()->isVoid()) - __ pop(); -} - -static inline bool -IsCompositeValueType(Type *type) -{ - return type->isStruct() || - (type->isArray() && type->isFixedLength()); -} - -static inline Array * -StringLiteralToArray(Zone *zone, StringLiteral *node) -{ - Local type(zone, node->type()); - Local map(zone, ArrayMap::Attach(zone, type)); - if (!map) - return NULL; - - return Array::NewString(zone, - map, - node->string()->raw(), - node->string()->length(), - Heap::Tenure_Old); -} - -void -BytecodeCompiler::visit(Assignment *node) -{ - Local left(zone_, node->type()); - Local right(zone_, node->expression()->type()); - - // The first step is three sub-steps: - // - // (1) Bind the left-hand side. - // -- If a cumulative operation (+=, etc): - // (2) Save a copy of the binding on the stack. - // (3) Dereference the binding to get a value. - // - if (node->lvalue()->isNameProxy()) { - NameProxy *proxy = node->lvalue()->toNameProxy(); - Variable *var = proxy->binding()->toVariable(); - - // We need to fetch the variable under the following circumstances: - // (1) The variable is a reference, since we'll store into the ref. - // (2) The variable is a fixed array, since we need to copy into it. - // (3) The variable is a struct, since we need to copy into it. - if (var->type()->isReference() || IsCompositeValueType(var->type())) - __ get(var); - - // Get the value if needed. - if (node->token() != TOK_ASSIGN) { - // += etc is not allowed on arrays. - assert(!var->type()->isArray()); - - if (var->location() == Variable::ENVIRONMENT) - getEnvSlot(var); - - if (var->type()->isReference()) { - __ dup(); - __ getref(var->type()->contained()); - } else if (var->location() != Variable::ENVIRONMENT) { - __ get(var); - } - } - } else if (node->lvalue()->isFieldExpression()) { - FieldExpression *expr = node->lvalue()->toFieldExpression(); - __ note_position(expr->pos()); - - expr->left()->accept(this); - - if (node->token() != TOK_ASSIGN) - __ dup(); - - if (node->token() != TOK_ASSIGN || IsCompositeValueType(left)) - __ getfield(expr->fieldIndex(), left); - } else { - IndexExpression *index = node->lvalue()->toIndexExpression(); - __ note_position(index->pos()); - - index->left()->accept(this); - index->right()->accept(this); - - if (node->token() != TOK_ASSIGN) - __ dup2(); - - if (node->token() != TOK_ASSIGN || IsCompositeValueType(left)) - __ getelem(left); - } - - if (node->token() == TOK_ASSIGN) { - // Special case: if the left-hand side is a string literal, we must - // spawn a new copy (:TODO: something about const?) - if (node->expression()->isStringLiteral() && !left->isFixedLength()) { - Local string(zone_, - StringLiteralToArray(zone_, node->expression()->toStringLiteral())); - if (!string) - return; - - Local arrayMap(zone_, ArrayMap::cast(string->map())); - __ note_position(node->expression()->pos()); - __ int_(string->length()); - __ newarray(OP_NEWSIZED, arrayMap, Heap::Tenure_Default); - __ object(string); - __ copyarray(); - } else { - node->expression()->accept(this); - } - } else { - visitForALU(left, node->expression()); - - TokenKind alu = TokenKind(int(node->token()) - (TOK_ASSIGN_ADD - TOK_PLUS)); - __ note_position(node->pos()); - __ binary(TokenToOpcode(alu)); - } - - // Emit the store. - if (left->isArray() && left->isFixedLength()) { - // (x = y) returns the left-hand side; if |x| is a fixed-length array, - // then |y| is as well, and we perform a deep copy. - assert(right->isFixedLength()); - __ note_position(node->pos()); - __ copyarray(); - } else if (left->isStruct()) { - // Structs have value semantics, so emit a full copy. We don't break - // this down into u-ops, to avoid unnecessary bytecode explosion. - assert(right->isStruct()); - __ note_position(node->pos()); - __ copystruct(); - } else if (node->lvalue()->isNameProxy()) { - NameProxy *proxy = node->lvalue()->toNameProxy(); - Variable *var = proxy->binding()->toVariable(); - - if (var->type()->isReference()) - __ setref(var->type()->contained()); - else if (var->location() == Variable::ENVIRONMENT) - setEnvSlot(var); - else - __ set(var); - } else if (node->lvalue()->isFieldExpression()) { - FieldExpression *expr = node->lvalue()->toFieldExpression(); - __ setfield(expr->fieldIndex(), left); - } else { - __ setelem(left); - } -} - -void -BytecodeCompiler::visit(IncDecExpression *node) -{ - // In the postfix case, the logic looks like: - // (1) Bind -> REF - // (2) Dup -> REF REF - // (3) Fetch -> REF VAL - // (4) Swap -> VAL REF - // (5) Pick -> VAL REF VAL - // (6) Add -> VAL REF VAL+1 - // (7) Store -> VAL VAL+1 - // (8) Drop -> VAL - // - // In the prefix case, the logic looks like: - // (1) Bind -> REF - // (2) Dup -> REF REF - // (3) Fetch -> REF VAL - // -- - // -- - // (6) Add -> REF VAL+1 - // (7) Store -> VAL+1 - // -- - // - // This is squirrely enough such that we don't bother sharing too much - // with the above. - // - // Note: We simulate a "Pick" operation with a dup2+pop. - - // This is steps (1) to (5). - Expression *expr = node->expression(); - if (expr->isNameProxy()) { - NameProxy *proxy = expr->toNameProxy(); - Variable *var = proxy->binding()->toVariable(); - - // Bind. - if (var->location() == Variable::ENVIRONMENT) - getEnvSlot(var); - else if (var->type()->isReference() || var->type()->isArray()) - __ get(var); - - // Dup and fetch. - if (var->type()->isReference()) { - __ dup(); - __ getref(node->type()); - if (node->postfix()) { - __ swap(); - __ pick2(); - } - } else { - assert(!var->type()->isArray()); - if (var->location() != Variable::ENVIRONMENT) - __ get(var); - if (node->postfix()) - __ dup(); - } - } else if (expr->isFieldExpression()) { - FieldExpression *field = expr->toFieldExpression(); - - field->left()->accept(this); - __ dup(); - __ getfield(field->fieldIndex(), field->type()); - - if (node->postfix()) { - // The stack looks like this now: - // OBJECT VALUE - __ swap(); // VALUE OBJECT - __ pick2(); // VALUE OBJECT VALUE - } - } else { - IndexExpression *index = expr->toIndexExpression(); - __ note_position(index->pos()); - - index->left()->accept(this); - index->right()->accept(this); - - assert(!node->type()->isArray()); - __ dup2(); - __ getelem(node->type()); - - if (node->postfix()) { - // The stack looks like this now: - // ARRAY INDEX VALUE - // - // So we must use a PICK operation to rotate the stack into position. - __ roll3(); // INDEX VALUE ARRAY - __ roll3(); // VALUE ARRAY INDEX - __ pick3(); // VALUE ARRAY INDEX VALUE - } - } - - // Perform step (6). - if (node->type()->isFloat()) { - __ float_(1.0); - } else { - assert(node->type()->isInt32()); - __ int_(1); - } - - __ note_position(node->pos()); - if (node->token() == TOK_INCREMENT) - __ binary(OP_ADD); - else - __ binary(OP_SUB); - - // Perform step (7). - if (expr->isNameProxy()) { - NameProxy *proxy = expr->toNameProxy(); - Variable *var = proxy->binding()->toVariable(); - - if (var->type()->isReference()) - __ setref(node->type()); - else if (var->location() == Variable::ENVIRONMENT) - setEnvSlot(var); - else - __ set(var); - } else if (expr->isFieldExpression()) { - FieldExpression *field = expr->toFieldExpression(); - __ setfield(field->fieldIndex(), field->type()); - } else { - assert(expr->isIndexExpression()); - __ setelem(node->type()); - } - - // Perform step (8). - if (node->postfix()) - __ pop(); -} - -void -BytecodeCompiler::visitForALU(Type *needed, Expression *node) -{ - node->accept(this); - if (needed->isFloat() && !node->type()->isFloat()) - __ cvt_i2f(); - else - assert(needed->primitive() == node->type()->primitive()); -} - -static inline Type * -GetCoercionType(TokenKind tok, Type *left, Type *right) -{ - if (left->isFloat()) - return left; - return right; -} - -void -BytecodeCompiler::visit(UnaryExpression *node) -{ - Local out(zone_, ((Expression *)node)->type()); - - switch (node->token()) { - case TOK_MINUS: - { - Local type(zone_, node->expression()->type()); - node->expression()->accept(this); - - if (out->isFloat()) - __ neg_f(); - else - __ neg(); - break; - } - - case TOK_NOT: - { - Local type(zone_, node->expression()->type()); - node->expression()->accept(this); - - if (out->isFloat()) - __ not_f(); - else - __ not_(); - break; - } - - case TOK_LABEL: - { - Local from(zone_, node->expression()->type()); - - node->expression()->accept(this); - if (!from->isFloat() && out->isFloat()) - __ bits_f2i(); - else if (from->isFloat() && !out->isFloat()) - __ bits_i2f(); - break; - } - - case TOK_SIZEOF: - { - Local type(zone_, node->expression()->type()); - if (type->isFixedLength()) { - __ int_(type->fixedLength()); - } else { - node->expression()->accept(this); - __ sizeof_(); - } - break; - } - - default: - assert(node->token() == TOK_TILDE); - node->expression()->accept(this); - __ bitnot(); - break; - } -} - -void -BytecodeCompiler::visit(BinaryExpression *node) -{ - if (node->token() == TOK_AND || node->token() == TOK_OR) { - Label success, failure, done; - visitForTest(node, &success, &failure, &success); - __ bind(&success); - __ int_(1); - __ drop(1); // Otherwise, the stack depth will be +1 too much. - __ jump(&done); - __ bind(&failure); - __ int_(0); - __ bind(&done); - return; - } - - Local leftType(zone_, node->left()->type()); - Local rightType(zone_, node->right()->type()); - Local coerce(zone_, GetCoercionType(node->token(), leftType, rightType)); - - visitForALU(coerce, node->left()); - visitForALU(coerce, node->right()); - - Opcode op = TokenToOpcode(node->token()); - if (node->type()->isFloat()) - op = (Opcode)(int(op) + (OP_ADD_F - OP_ADD)); - - __ note_position(node->pos()); - __ binary(op); -} - -void -BytecodeCompiler::visit(IndexExpression *node) -{ - node->left()->accept(this); - node->right()->accept(this); - __ note_position(node->pos()); - __ getelem(node->type()); -} - -void -BytecodeCompiler::visit(FieldExpression *node) -{ - node->left()->accept(this); - __ getfield(node->fieldIndex(), node->type()); -} - -void -BytecodeCompiler::visit(ReturnStatement *node) -{ - if (node->expression()) { - node->expression()->accept(this); - __ return_(); - } else { - __ returnvoid(); - } -} - -void -BytecodeCompiler::visitForTest(Expression *expression, - Label *trueBranch, - Label *falseBranch, - Label *fallthrough) -{ - TokenKind token = ToCompare(expression); - if (token == TOK_EOF || token == TOK_NOT) { - if (token == TOK_EOF) - expression->accept(this); - else - expression->toUnaryExpression()->expression()->accept(this); - - bool jumpOnTrue = (fallthrough == falseBranch) ^ (token == TOK_NOT); - - Opcode op = jumpOnTrue ? OP_JT : OP_JF; - if (expression->type()->isFloat()) - op = (Opcode)(int(op) + (OP_JL_F - OP_JL)); - - if (fallthrough == falseBranch) - __ j(op, trueBranch); - else - __ j(op, falseBranch); - return; - } - - BinaryExpression *binary = expression->toBinaryExpression(); - - if (token == TOK_AND || token == TOK_OR) { - Label next; - if (token == TOK_AND) - visitForTest(binary->left(), &next, falseBranch, &next); - else - visitForTest(binary->left(), trueBranch, &next, &next); - __ bind(&next); - visitForTest(binary->right(), trueBranch, falseBranch, fallthrough); - return; - } - - Opcode op = CompareTokenToJump(token); - if (fallthrough == trueBranch) - op = Invert(op); - - Local leftType(zone_, binary->left()->type()); - Local rightType(zone_, binary->right()->type()); - Local coerce(zone_, GetCoercionType(binary->token(), leftType, rightType)); - - visitForALU(coerce, binary->left()); - visitForALU(coerce, binary->right()); - if (coerce->isFloat()) - op = (Opcode)(int(op) + (OP_JL_F - OP_JL)); - - if (fallthrough == trueBranch) - __ j(op, falseBranch); - else - __ j(op, trueBranch); -} - -void -BytecodeCompiler::visit(ForStatement *node) -{ - AutoEnterScope forScope(this, node->scope()); - - recordLifoSlot(node->scope()); - - Label join, update; - LoopScope loop(scope_, &join, &update, &loop_); - - if (node->initialization()) - node->initialization()->accept(this); - - Label header, test; - if (node->condition()) - __ jump(&test); - - __ bind(&header); - node->body()->accept(this); - - __ bind(&update); - if (node->update()) - node->update()->accept(this); - - if (node->condition()) { - __ bind(&test); - visitForTest(node->condition(), &header, &join, &join); - } else { - __ jump(&header); - } - - __ bind(&join); - - if (node->scope()) - unwindTo(node->scope()->enclosing()); - assert(__ stackDepth() == 0); -} - -void -BytecodeCompiler::visit(WhileStatement *node) -{ - Label header, join; - - __ bind(&header); - - LoopScope loop(scope_, &join, &header, &loop_); - - if (node->token() == TOK_WHILE) { - Label body; - - visitForTest(node->condition(), &body, &join, &body); - __ bind(&body); - node->body()->accept(this); - __ jump(&header); - } else { - node->body()->accept(this); - visitForTest(node->condition(), &header, &join, &join); - } - - __ bind(&join); -} - -void -BytecodeCompiler::visit(BreakStatement *node) -{ - unwindTo(loop_->scope()); - __ jump(loop_->break_()); -} - -void -BytecodeCompiler::visit(ContinueStatement *node) -{ - unwindTo(loop_->scope()); - __ jump(loop_->continue_()); -} - -void -BytecodeCompiler::visit(BlockStatement *node) -{ - AutoEnterScope blockScope(this, node->scope()); - - recordLifoSlot(node->scope()); - - for (size_t i = 0; i < node->statements()->length(); i++) { - Statement *statement = node->statements()->at(i); - statement->accept(this); - assert(__ stackDepth() == 0); - } - - // We don't bother emitting an "unwind" instruction for function scopes, - // since this is taken care of by the calling convention. - if (node->scope() && node->scope()->enclosing()->kind() != Scope::FUNCTION) - unwindTo(node->scope()->enclosing()); -} - -void -BytecodeCompiler::visit(IntegerLiteral *node) -{ - __ int_(node->value()); -} - -void -BytecodeCompiler::visit(BooleanLiteral *node) -{ - if (node->token() == TOK_TRUE) - __ true_(); - else - __ false_(); -} - -void -BytecodeCompiler::visit(FloatLiteral *node) -{ - __ float_((float)node->value()); -} - -void -BytecodeCompiler::visit(StringLiteral *node) -{ - Local array(zone_, StringLiteralToArray(zone_, node)); - if (!array) - return; - - __ note_position(node->pos()); - __ object(array); -} - -void -BytecodeCompiler::getEnvSlot(Variable *var) -{ - assert(var->scope()->kind() == Scope::GLOBAL); - __ getglobal(var); -} - -void -BytecodeCompiler::setEnvSlot(Variable *var) -{ - assert(var->scope()->kind() == Scope::GLOBAL); - __ setglobal(var); -} - -static bool -FillArrayLiteral_String(Zone *zone, Handle array, ArrayLiteral *literal, unsigned *maxsize) -{ - Local string(zone); - for (size_t i = 0; i < literal->expressions()->length(); i++) { - Expression *expr = literal->expressions()->at(i); - StringLiteral *lit = expr->toStringLiteral(); - if ((string = StringLiteralToArray(zone, lit)) == NULL) - return false; - - array->writeObject(zone, i, string); - - if (string->length() > *maxsize) - *maxsize = string->length(); - - assert(!array->type()->contained()->isFixedLength() || - array->type()->contained()->fixedLength() >= string->length()); - } - - return true; -} - -static bool -FillArrayLiteral_Primitive(Zone *zone, Handle array, ArrayLiteral *literal) -{ - assert(array->type()->fixedLength() == literal->expressions()->length()); - - for (size_t i = 0; i < literal->expressions()->length(); i++) { - Expression *expr = literal->expressions()->at(i); - if (IntegerLiteral *lit = expr->asIntegerLiteral()) { - array->writeInt32(i, lit->value()); - continue; - } - if (BooleanLiteral *lit = expr->asBooleanLiteral()) { - if (lit->token() == TOK_TRUE) - array->writeInt32(i, 1); - else - array->writeInt32(i, 0); - continue; - } - if (FloatLiteral *lit = expr->asFloatLiteral()) { - array->writeFloat(i, lit->value()); - continue; - } - - // Unrecognized literal type. - assert(false); - } - - return true; -} - -static bool -TraverseArrayLiteral(Zone *zone, ArrayLiteral *literal, Handle array, Handle type, - unsigned *maxsize) -{ - assert(type->fixedLength() == literal->expressions()->length()); - - Local contained(zone, type->contained()); - if (contained->isCharArray()) - return FillArrayLiteral_String(zone, array, literal, maxsize); - - if (!contained->isArray()) - return FillArrayLiteral_Primitive(zone, array, literal); - - Local subarray(zone); - for (size_t i = 0; i < literal->expressions()->length(); i++) { - Expression *expr = literal->expressions()->at(i); - subarray = Array::cast(array->readObject(i)); - if (!TraverseArrayLiteral(zone, expr->toArrayLiteral(), subarray, contained, maxsize)) - return false; - } - - return true; -} - -static Array * -BuildArrayLiteral(Zone *zone, ArrayLiteral *literal, Handle map, unsigned *maxsize) -{ - Local root(zone, Array::NewFixed(zone, map, Heap::Tenure_Old)); - if (!root) - return NULL; - - // This should have allocated everything in the array, so all we need to - // do is traverse down to the lowest levels and copy things. - Local type(zone, map->type()); - if (!TraverseArrayLiteral(zone, literal, root, type, maxsize)) - return NULL; - - return root; -} - -static inline bool -ArrayContainsCharBuffers(Type *type) -{ - if (type->contained()->isArray()) - return ArrayContainsCharBuffers(type->contained()); - return type->isCharArray(); -} - -void -BytecodeCompiler::visit(VariableDeclaration *node) -{ - Variable *var = node->variable(); - - Local type(zone_, var->type()); - if (type->isArray()) { - Local map(zone_, ArrayMap::Attach(zone_, type)); - __ note_position(node->pos()); - - Local right(zone_); - if (node->initialization()) - right = node->initialization()->type(); - - // There are a few methods for allocating, here: - // (1) If the array is fixed, - // (a) Initializer not present, - // NEWFIXED - // (b) Left is const non-string: - // - // (b) Otherwise: - // NEWFIXED - // COPYARRAY - // (2) If the array is not fixed, - // (a) Initializer is not present, - // NEWEMPTY - // (b) Initializer is not a string literal, - // or left is const: - // - // (c) Initializer is a string literal, - // NEWSIZED - // COPYARRAY - // - - Heap::Tenure tenure = var->scope()->kind() == Scope::GLOBAL - ? Heap::Tenure_Old - : Heap::Tenure_Default; - - if (Expression *init = node->initialization()) { - if (init->isArrayLiteral()) { - unsigned maxsize = 0; - Local array(zone_, BuildArrayLiteral(zone_, init->toArrayLiteral(), map, &maxsize)); - if (!array) - return; - - assert(type->isFixedLength()); - - if (type->isConst()) { - __ object(array); - } else if (ArrayContainsCharBuffers(type)) { - // This array contains mutable char buffers, so - // unfortunately this gets a little more complicated. We - // compute the maximum size needed for the innermost - // string, which is similar to SP1. This means we can use - // CopyArray rather than individually emit opcodes to clone - // every string. - Local child(zone_, type); - for (size_t i = 0; i < type->levels() - 1; i++) { - __ int_(child->fixedLength()); - child = child->contained(); - } - __ int_(maxsize); - __ newarray(OP_NEWSIZED, map, tenure); - __ object(array); - __ copyarray(); - } else { - __ newarray(OP_NEWFIXED, map, tenure); - __ object(array); - __ copyarray(); - } - } else if (type->isFixedLength()) { - // Note in the case of: - // const String:buffer[50] = "hello"; - // - // We want to preserve sizeof(buffer) == 50. - if (type->isConst() && !type->isCharArray()) { - init->accept(this); - } else { - __ newarray(OP_NEWFIXED, map, tenure); - init->accept(this); - __ copyarray(); - } - } else if (!type->isConst() && init->isStringLiteral()) { - // This is a degenerate case of ArrayLiteral. - __ int_(right->fixedLength()); - __ newarray(OP_NEWSIZED, map, tenure); - init->accept(this); - __ copyarray(); - } else if (type->isConst()) { - init->accept(this); - } - } else if (type->isFixedLength()) { - __ newarray(OP_NEWFIXED, map, tenure); - } else { - if (node->postDimensions()) { - for (size_t i = 0; i < node->postDimensions()->length(); i++) { - Expression *expr = node->postDimensions()->at(i); - if (expr) - expr->accept(this); - else - __ int_(0); - } - __ newarray(OP_NEWSIZED, map, tenure); - } else { - __ newarray(OP_NEWEMPTY, map, tenure); - } - } - } else if (type->isStruct()) { - Local map(zone_, StructMap::cast(type->newMap())); - - // Because structs can contain arrays, any sort of composition - // triggers non-LIFO allocation. This is just for correctness. - // We could do better by: - // (1) Limiting to structs that just (recursively) contain arrays, - // (2) Swap Stack for Default tenure in Struct::New - // (3) JIT analysis - if (var->scope()->kind() == Scope::GLOBAL) - __ newstruct(map, Heap::Tenure_Old); - else if (var->needsHeapAllocation() || map->composed()) - __ newstruct(map, Heap::Tenure_Default); - else - __ newstruct(map, Heap::Tenure_Stack); - } else { - if (node->initialization()) { - node->initialization()->accept(this); - } else { - if (type->isInt32OrEnum() || type->isBool()) { - __ int_(0); - } else { - assert(type->isFloat()); - __ float_(0); - } - } - } - - if (var->location() == Variable::STACK) { - __ setlocal(var); - locals_->set(zone_, var->slot(), var->type()); - } else { - assert(var->location() == Variable::ENVIRONMENT); - setEnvSlot(var); - } - - __ pop(); -} - -void -BytecodeCompiler::visit(CallExpression *node) -{ - Local funType(zone_, node->callee()->type()); - - // Sniff out natives, which have a specific opcode and are guaranteed to - // be called via name proxy. - NamedFunction *native = NULL; - if (funType->isNative()) - native = node->callee()->toNameProxy()->binding()->toNamedFunction(); - - Local given(zone_); - for (size_t i = node->arguments()->length() - 1; i < node->arguments()->length(); i--) { - Expression *arg = node->arguments()->at(i); - - given = arg->type(); - - // We need to compute a reference if the parameter was in the form: - // f(&a) - // Note this form does not allow: f({}) - // - // We also need to compute references for all variadic native - // arguments, but arrays and references are already in the - // appropriate form. - bool mustPushReference = false; - if (i >= funType->parameters()->length()) { - assert(funType->isNative() && funType->isNativeVariadic()); - mustPushReference = !given->isArray() && !given->isReference(); - } else if (funType->parameterAt(i)->isReference()) { - mustPushReference = !given->isReference(); - } - - // If a native is passed to a native, it must only be one level deep. - assert(!funType->isNative() || !given->isArray() || given->levels() == 1); - - if (mustPushReference) { - if (arg->isNameProxy()) { - // :TODO: test with functions and globals - // Compute a reference to the name. - Variable *var = arg->toNameProxy()->binding()->toVariable(); - __ note_position(node->pos()); - __ newref(var); - } else if (arg->isFieldExpression()) { - FieldExpression *expr = arg->toFieldExpression(); - expr->left()->accept(this); - __ note_position(expr->pos()); - __ fieldref_lifo(expr->fieldIndex()); - } else { - // We don't know how to, or can't, build a direct reference to - // this type. Make an indirect one, by taking the top of the - // stack and boxing it up. - arg->accept(this); - __ note_position(node->pos()); - __ slotref_lifo(); - } - } else { - arg->accept(this); - } - } - - unsigned argc = node->arguments()->length(); - - // If there are missing arguments, then we're not done and have to push - // default parameters. - Local box(zone_); - Local array(zone_); - Local map(zone_); - for (size_t i = node->arguments()->length(); i < funType->parameters()->length(); i++) { - box = Box::cast(funType->defaults()->at(i)); - - if (box->type()->isPod()) { - if (box->type()->pod() == PrimitiveType_Int32) - __ int_(box->toInt32()); - else if (box->type()->pod() == PrimitiveType_Float) - __ float_(box->toFloat()); - else - assert(false); - - if (funType->parameterAt(i)->isReference()) { - __ note_position(node->pos()); - __ slotref_lifo(); - } - } else { - assert(box->type()->isArray()); - - array = Array::cast(box->toObject()); - if (funType->parameterAt(i)->isConst()) { - __ object(array); - } else { - // We need to copy the array since it's mutable. Note that - // although COPYARRAY returns its left-hand side, this may - // change in the future, we do the same thing and dupe the - // newly allocated array. - map = ArrayMap::cast(array->map()); - - // Natives cannot escape array references, so we can allocate - // them on the LIFO stack. - if (funType->isNative()) - __ newarray(OP_NEWFIXED, map, Heap::Tenure_Stack); - else - __ newarray(OP_NEWFIXED, map, Heap::Tenure_Default); - __ dup(); - __ object(array); - __ copyarray(); - __ pop(); - } - } - - argc++; - } - - __ note_position(node->pos()); - if (native) { - __ callnative(native->slot(), argc); - __ drop(argc); - } else { - node->callee()->accept(this); - __ call(funType); - __ drop(argc + 1); - } -} - -void -BytecodeCompiler::visit(TernaryExpression *node) -{ - Label success, failure, join; - - visitForTest(node->condition(), &success, &failure, &success); - __ bind(&success); - node->left()->accept(this); - __ drop(1); - __ jump(&join); - __ bind(&failure); - node->right()->accept(this); - __ bind(&join); -} - -void -BytecodeCompiler::visit(IfStatement *node) -{ - Label join; - if (!node->ifFalse()) { - Label success; - - visitForTest(node->condition(), &success, &join, &success); - __ bind(&success); - node->ifTrue()->accept(this); - } else { - Label success, next; - - visitForTest(node->condition(), &success, &next, &success); - __ bind(&success); - node->ifTrue()->accept(this); - __ jump(&join); - __ bind(&next); - node->ifFalse()->accept(this); - } - - __ bind(&join); -} - -bool -BytecodeCompiler::tableswitch(SwitchStatement *node) -{ - CaseValueList *values = node->caseValueList(); - - if (values->length() < 3) - return false; - - // Find the delta between the largest and smallest value. - int hi = values->at(values->length() - 1).box.toInt(); - int lo = values->at(0).box.toInt(); - assert(hi > lo); - - unsigned tablesize = TableSwitchEntries(lo, hi); - if (tablesize > MAX_TABLESWITCH_ENTRIES) - return false; - - Label def, join; - - // Create the list of labels that each statement will bind to. - Label *labels = new Label[node->cases()->length()]; - - // Create the table of labels we'll use for each case. - Label **table = new Label *[tablesize]; - - // Link all cases, by default, to the default label. - for (size_t i = 0; i < tablesize; i++) - table[i] = &def; - - // Now patch actual cases to their correct labels. - for (size_t i = 0; i < values->length(); i++) { - CaseValue &v = values->at(i); - assert(v.statement < node->cases()->length()); - - unsigned index = unsigned(v.box.toInt() - lo); - assert(index < tablesize); - - table[index] = &labels[v.statement]; - } - - // Emit the tableswitch opcode and the jump table. - __ tableswitch(lo, hi, &def, table); - - // We're done with the jump table now. - delete [] table; - - // Emit each statement. - for (size_t i = 0; i < node->cases()->length(); i++) { - Statement *stmt = node->cases()->at(i)->statement(); - - __ bind(&labels[i]); - stmt->accept(this); - __ jump(&join); - } - - // We're done with the statement labels. - delete [] labels; - - // Emit the default case. - __ bind(&def); - if (node->defaultCase()) - node->defaultCase()->accept(this); - - __ bind(&join); - - return true; -} - -void -BytecodeCompiler::visit(SwitchStatement *node) -{ - node->expression()->accept(this); - - if (tableswitch(node)) - return; - - Label join; - - for (size_t i = 0; i < node->cases()->length(); i++) { - Case *c = node->cases()->at(i); - - Label success, fail; - if (c->others()) { - for (size_t j = 0; j < c->others()->length(); j++) { - Expression *expr = c->others()->at(j); - - __ dup(); - expr->accept(this); - __ j(OP_JEQ, &success); - } - } - - // The final test. - __ dup(); - c->expression()->accept(this); - __ j(OP_JNE, &fail); - __ bind(&success); - c->statement()->accept(this); - __ jump(&join); - __ bind(&fail); - } - - if (node->defaultCase()) - node->defaultCase()->accept(this); - - __ pop(); - __ bind(&join); -} - -void -BytecodeCompiler::visit(FunctionStatement *node) -{ - if (!node->fun()->used()) - return; - - if (node->fun()->isNative()) { - assert(!moduleData_->natives->at(node->fun()->slot())); - - Local type(zone_, node->fun()->type()); - Local name(zone_, node->fun()->name()); - Local native(zone_, Native::New(zone_, type, name)); - if (!native) - return; - - moduleData_->natives->set(zone_, node->fun()->slot(), native); - return; - } - - // If the function has no body, then it's just a declaration. - if (!node->body()) { - assert(node->fun()->isForward() || node->fun()->isPublic()); - return; - } - - BytecodeCompiler child(zone_, tree_, moduleData_); - Local fun(zone_, child.compileFunction(moduleData_, node)); - if (!fun) - return; - - assert(!moduleData_->module->getFunction(node->fun()->slot())); - moduleData_->module->setFunction(zone_, node->fun()->slot(), fun); -} - -void -BytecodeCompiler::visit(FunctionTypeStatement *node) -{ -} - -void -BytecodeCompiler::visit(EnumStatement *node) -{ -} - -void -BytecodeCompiler::visit(StructureStatement *node) -{ -} - -void -BytecodeCompiler::visit(ArrayLiteral *node) -{ - // We shouldn't reach these; they're handled manually when initializing - // declared arrays. - assert(false); -} - -Code * -BytecodeCompiler::compileCode(Scope *scope, unsigned lineno) -{ - // STOP doubles as RETURNVOID. - __ stop(); - - if (__ oom() || zone_->hasPendingException()) - return NULL; - - Local bytes(zone_, ByteArray::New(zone_, __ length() * sizeof(bytecode), Heap::Tenure_Old)); - if (!bytes) - return NULL; - memcpy(bytes->raw(), __ buffer(), __ length()); - - for (size_t i = 0; i < scope->maxContainedLocals(); i++) - assert(locals_->at(i)); - - Local objects(zone_, FixedArray::New(zone_, __ objects()->length(), Heap::Tenure_Old)); - if (!objects) - return NULL; - - for (size_t i = 0; i < __ objects()->length(); i++) - objects->set(zone_, i, *(__ objects()->at(i))); - - Local safepoints(zone_); - - // If any stack slots hold gc pointers at potential failure points in the - // bytecode, record them now. We do this by building a list of tuples; - // each tuple contains the pc offset, and a bitset containing which stack - // slots must be scanned by the gc. - if (__ oopRecords()->length()) { - size_t bytesPerSafepoint = Code::BytesPerSafepoint(__ maxStackDepth()); - size_t gcmapSize = bytesPerSafepoint * __ oopRecords()->length(); - safepoints = ByteArray::New(zone_, gcmapSize, Heap::Tenure_Old); - if (!safepoints) - return NULL; - - Address cursor = safepoints->raw(); - for (size_t i = 0; i < __ oopRecords()->length(); i++) { - OopRecord *record = __ oopRecords()->at(i); - assert(record->depth <= __ maxStackDepth()); - - *reinterpret_cast(cursor) = record->offset; - - unsigned char *bits = reinterpret_cast(cursor + sizeof(unsigned)); - for (unsigned i = 0; i < record->depth; i++) - bits[i / 8] |= (1 << (i % 8)); - - cursor += bytesPerSafepoint; - } - - assert(cursor == safepoints->raw() + gcmapSize); - } - - Local sourcemap(zone_); - if (__ sourcemapLength()) { - sourcemap = ByteArray::New(zone_, __ sourcemapLength(), Heap::Tenure_Old); - if (!sourcemap) - return NULL; - - memcpy(sourcemap->raw(), __ sourcemap(), __ sourcemapLength()); - } - - Local code(zone_); - - code = Code::New(zone_, - bytes, - locals_, - objects, - __ maxStackDepth(), - moduleData_->module, - safepoints, - sourcemap, - lineno); - return code; -} - -Function * -BytecodeCompiler::compileFunction(ModuleData *module, FunctionStatement *node) -{ - // Note: No need to record a life slot here, since it's part of the Frame. - AutoEnterScope functionScope(this, node->scope()); - - // Set the initial line, because line mappings are deltas. - __ setFirstLine(node->pos().line); - locals_ = FixedArray::New(zone_, node->scope()->maxContainedLocals(), Heap::Tenure_Old); - - node->body()->accept(this); - - unsigned nargs = node->scope()->numArguments(); - Local args(zone_, FixedArray::New(zone_, nargs, Heap::Tenure_Old)); - if (!args) - return NULL; - - for (size_t i = 0; i < node->scope()->numArguments(); i++) - args->set(zone_, i, node->scope()->getArgument(i)->type()); - - - Local code(zone_, compileCode(node->scope(), node->pos().line)); - if (!code) - return NULL; - - Local type(zone_, node->fun()->type()); - Local name(zone_, node->fun()->name()); - Local fun(zone_, Function::New(zone_, code, type, name, node->pos())); - if (!fun) - return NULL; - - // If this function had a forward, export it as a named public. - if (node->fun()->isPublic()) { - assert(node->fun()->isPublic()); - ScopedRoot root(fun); - if (!root) - return NULL; - if (!moduleData_->publics.append(root)) - return NULL; - } - -#ifndef NDEBUG - Disassemble(code, stdout); -#endif - - return fun; -} - -Module * -BytecodeCompiler::compile() -{ - AutoEnterScope scope(this, tree_->scope()); - - ModuleData moduleData; - - moduleData_ = &moduleData; - moduleData_->module = Module::New(zone_, tree_->scope()->numFunctions()); - - if (!moduleData_) - return NULL; - - moduleData_->natives = FixedArray::New(zone_, tree_->scope()->numNatives(), Heap::Tenure_Old); - if (!moduleData_->natives) - return NULL; - - for (size_t i = 0; i < tree_->statements()->length(); i++) { - Statement *statement = tree_->statements()->at(i); - statement->accept(this); - } - - if (zone_->hasPendingException()) - return NULL; - -#ifndef NDEBUG - for (size_t i = 0; i < tree_->scope()->numFunctions(); i++) - assert(moduleData_->module->getFunction(i)); - for (size_t i = 0; i < tree_->scope()->numNatives(); i++) - assert(moduleData_->natives->at(i)); -#endif - - if (moduleData_->publics.length()) { - Local fun(zone_); - Local list(zone_, FixedArray::New(zone_, moduleData_->publics.length(), - Heap::Tenure_Old)); - if (!list) - return NULL; - - for (size_t i = 0; i < moduleData_->publics.length(); i++) - list->set(zone_, i, moduleData_->publics[i]); - - moduleData_->module->setPublics(list); - } - - moduleData_->module->setNatives(moduleData_->natives); - - if (scope_->numEnvironmentVars()) { - Local globalMap(zone_, scope_->createEnvironmentMap(zone_)); - if (!globalMap) - return NULL; - - Local environment(zone_, Environment::New(zone_, globalMap)); - if (!environment) - return NULL; - - moduleData_->module->setEnvironment(environment); - } - - Local code(zone_, compileCode(tree_->scope(), 1)); - if (!code) - return NULL; - - Disassemble(code, stdout); - - moduleData_->module->setCode(code); - - return moduleData_->module; -}