| /* |
| * 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; |
| } |
| } |
| |