blob: ea681818f6c28c7e6ef915d614a9938a91706e2c [file] [log] [blame]
/*
* Copyright (C) 2017 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
"use strict";
// This is a combined LHS/RHS evaluator that passes around EPtr's to everything.
class Evaluator extends Visitor {
constructor(program)
{
super();
this._program = program;
}
// You must snapshot if you use a value in rvalue context. For example, a call expression will
// snapshot all of its arguments immedaitely upon executing them. In general, it should not be
// possible for a pointer returned from a visit method in rvalue context to live across any effects.
_snapshot(type, dstPtr, srcPtr)
{
let size = type.size;
if (size == null)
throw new Error("Cannot get size of type: " + type + " (size = " + size + ", constructor = " + type.constructor.name + ")");
if (!dstPtr)
dstPtr = new EPtr(new EBuffer(size), 0);
dstPtr.copyFrom(srcPtr, size);
return dstPtr;
}
runFunc(func)
{
return EBuffer.disallowAllocation(
() => this._runBody(func.returnType, func.returnEPtr, func.body));
}
_runBody(type, ptr, block)
{
if (!ptr)
throw new Error("Null ptr");
try {
block.visit(this);
// FIXME: We should have a check that there is no way to drop out of a function without
// returning unless the function returns void.
return null;
} catch (e) {
if (e == BreakException || e == ContinueException)
throw new Error("Should not see break/continue at function scope");
if (e instanceof ReturnException) {
let result = this._snapshot(type, ptr, e.value);
return result;
}
throw e;
}
}
visitFunctionLikeBlock(node)
{
for (let i = 0; i < node.argumentList.length; ++i) {
node.parameters[i].ePtr.copyFrom(
node.argumentList[i].visit(this),
node.parameters[i].type.size);
}
let result = this._runBody(node.returnType, node.returnEPtr, node.body);
return result;
}
visitReturn(node)
{
throw new ReturnException(node.value ? node.value.visit(this) : null);
}
visitVariableDecl(node)
{
if (!node.ePtr.buffer)
throw new Error("eptr without buffer in " + node);
node.type.populateDefaultValue(node.ePtr.buffer, node.ePtr.offset);
if (node.initializer)
node.ePtr.copyFrom(node.initializer.visit(this), node.type.size);
}
visitAssignment(node)
{
let target = node.lhs.visit(this);
let source = node.rhs.visit(this);
target.copyFrom(source, node.type.size);
return target;
}
visitIdentityExpression(node)
{
return node.target.visit(this);
}
visitDereferenceExpression(node)
{
let ptr = node.ptr.visit(this).loadValue();
if (!ptr)
throw new WTrapError(node.origin.originString, "Null dereference");
return ptr;
}
visitMakePtrExpression(node)
{
let ptr = node.lValue.visit(this);
return node.ePtr.box(ptr);
}
visitMakeArrayRefExpression(node)
{
return node.ePtr.box(new EArrayRef(node.lValue.visit(this), node.numElements.visit(this).loadValue()));
}
visitConvertPtrToArrayRefExpression(node)
{
return node.ePtr.box(new EArrayRef(node.lValue.visit(this).loadValue(), 1));
}
visitCommaExpression(node)
{
let result;
for (let expression of node.list)
result = expression.visit(this);
// This should almost snapshot, except that tail-returning a pointer is totally OK.
return result;
}
visitVariableRef(node)
{
return node.variable.ePtr;
}
visitGenericLiteral(node)
{
return node.ePtr.box(node.valueForSelectedType);
}
visitNullLiteral(node)
{
return node.ePtr.box(null);
}
visitBoolLiteral(node)
{
return node.ePtr.box(node.value);
}
visitEnumLiteral(node)
{
return node.ePtr.box(node.member.value.unifyNode.valueForSelectedType);
}
visitLogicalNot(node)
{
let result = !node.operand.visit(this).loadValue();
return node.ePtr.box(result);
}
visitLogicalExpression(node)
{
let lhs = node.left.visit(this).loadValue();
let rhs = node.right.visit(this).loadValue();
let result;
switch (node.text) {
case "&&":
result = lhs && rhs;
break;
case "||":
result = lhs || rhs;
break;
default:
throw new Error("Unknown type of logical expression");
}
return node.ePtr.box(result);
}
visitIfStatement(node)
{
if (node.conditional.visit(this).loadValue())
return node.body.visit(this);
else if (node.elseBody)
return node.elseBody.visit(this);
}
visitWhileLoop(node)
{
while (node.conditional.visit(this).loadValue()) {
try {
node.body.visit(this);
} catch (e) {
if (e == BreakException)
break;
if (e == ContinueException)
continue;
throw e;
}
}
}
visitDoWhileLoop(node)
{
do {
try {
node.body.visit(this);
} catch (e) {
if (e == BreakException)
break;
if (e == ContinueException)
continue;
throw e;
}
} while (node.conditional.visit(this).loadValue());
}
visitForLoop(node)
{
for (node.initialization ? node.initialization.visit(this) : true;
node.condition ? node.condition.visit(this).loadValue() : true;
node.increment ? node.increment.visit(this) : true) {
try {
node.body.visit(this);
} catch (e) {
if (e == BreakException)
break;
if (e == ContinueException)
continue;
throw e;
}
}
}
visitSwitchStatement(node)
{
let findAndRunCast = predicate => {
for (let i = 0; i < node.switchCases.length; ++i) {
let switchCase = node.switchCases[i];
if (predicate(switchCase)) {
try {
for (let j = i; j < node.switchCases.length; ++j)
node.switchCases[j].visit(this);
} catch (e) {
if (e != BreakException)
throw e;
}
return true;
}
}
return false;
};
let value = node.value.visit(this).loadValue();
let found = findAndRunCast(switchCase => {
if (switchCase.isDefault)
return false;
return node.type.unifyNode.valuesEqual(
value, switchCase.value.unifyNode.valueForSelectedType);
});
if (found)
return;
found = findAndRunCast(switchCase => switchCase.isDefault);
if (!found)
throw new Error("Switch statement did not find case");
}
visitBreak(node)
{
throw BreakException;
}
visitContinue(node)
{
throw ContinueException;
}
visitTrapStatement(node)
{
throw new WTrapError(node.origin.originString, "Trap statement");
}
visitAnonymousVariable(node)
{
node.type.populateDefaultValue(node.ePtr.buffer, node.ePtr.offset);
}
visitCallExpression(node)
{
// We evaluate inlined ASTs, so this can only be a native call.
let callArguments = [];
for (let i = 0; i < node.argumentList.length; ++i) {
let argument = node.argumentList[i];
let type = node.nativeFuncInstance.parameterTypes[i];
if (!type || !argument)
throw new Error("Cannot get type or argument; i = " + i + ", argument = " + argument + ", type = " + type + "; in " + node);
let argumentValue = argument.visit(this);
if (!argumentValue)
throw new Error("Null argument value, i = " + i + ", node = " + node);
callArguments.push(() => {
let result = this._snapshot(type, null, argumentValue);
return result;
});
}
// For simplicity, we allow intrinsics to just allocate new buffers, and we allocate new
// buffers when snapshotting their arguments. This is not observable to the user, so it's OK.
let result = EBuffer.allowAllocation(
() => node.func.implementation(callArguments.map(thunk => thunk()), node));
result = this._snapshot(node.nativeFuncInstance.returnType, node.resultEPtr, result);
return result;
}
}