/*
 * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * The Universal Permissive License (UPL), Version 1.0
 *
 * Subject to the condition set forth below, permission is hereby granted to any
 * person obtaining a copy of this software, associated documentation and/or
 * data (collectively the "Software"), free of charge and under any and all
 * copyright rights in the Software, and any and all patent rights owned or
 * freely licensable by each licensor hereunder covering either (i) the
 * unmodified Software as contributed to or provided by such licensor, or (ii)
 * the Larger Works (as defined below), to deal in both
 *
 * (a) the Software, and
 *
 * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
 * one is included with the Software each a "Larger Work" to which the Software
 * is contributed by such licensors),
 *
 * without restriction, including without limitation the rights to copy, create
 * derivative works of, display, perform, and distribute the Software and make,
 * use, sell, offer for sale, import, export, have made, and have sold the
 * Software and the Larger Work(s), and to sublicense the foregoing rights on
 * either these or other terms.
 *
 * This license is subject to the following condition:
 *
 * The above copyright notice and either this complete permission notice or at a
 * minimum a reference to the UPL must be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.graalvm.wasm;

import org.graalvm.wasm.collection.BooleanArrayList;
import org.graalvm.wasm.collection.ByteArrayList;
import org.graalvm.wasm.collection.IntArrayList;
import org.graalvm.wasm.exception.Failure;
import org.graalvm.wasm.exception.WasmException;
import org.graalvm.wasm.nodes.WasmBlockNode;

import java.util.ArrayList;

public class ExecutionState {
    private int profileCount;
    private final IntArrayList intConstants;
    private final ArrayList<int[]> branchTables;
    private final ByteArrayList stack;

    
Maximum number of values ever pushed on the stack during this execution.
/** * Maximum number of values ever pushed on the stack during this execution. */
private int maxStackSize;
Stack size at the beginning of each parent block.
/** * Stack size at the beginning of each parent block. */
private final IntArrayList ancestorsStackSizes;
Whereas each parent block is a loop body or not.
/** * Whereas each parent block is a loop body or not. */
private final BooleanArrayList ancestorsIsLoop;
Current WasmBlockNode's inclusive ancestors.
/** * Current {@link WasmBlockNode}'s inclusive ancestors. */
private final ArrayList<WasmBlockNode> ancestors;
Whereas the current point in code is reachable or not.
/** * Whereas the current point in code is reachable or not. */
private boolean reachable; public ExecutionState() { this.stack = new ByteArrayList(); this.maxStackSize = 0; this.profileCount = 0; this.intConstants = new IntArrayList(); this.ancestorsStackSizes = new IntArrayList(); this.ancestorsIsLoop = new BooleanArrayList(); this.ancestors = new ArrayList<>(); this.branchTables = new ArrayList<>(); this.reachable = true; } public boolean isReachable() { return this.reachable; } public void setReachable(boolean reachable) { this.reachable = reachable; } public void push(byte type) { stack.push(type); maxStackSize = Math.max(stack.size(), maxStackSize); } public byte pop() { final int blockStackSize = stack.size() - getStackSize(0); if (!isReachable() && blockStackSize == 0) { return -1; } Assert.assertIntGreater(blockStackSize, 0, "Cannot pop from the stack", Failure.EMPTY_STACK); return stack.popBack(); } public void popChecked(byte expectedType) { if (isReachable()) { assertTypesEqual(expectedType, pop()); } } private void topChecked(byte expectedType) { if (isReachable()) { final int blockStackSize = stack.size() - getStackSize(0); Assert.assertIntGreater(blockStackSize, 0, "Cannot pop from the stack", Failure.EMPTY_STACK); assertTypesEqual(expectedType, stack.top()); } } private static void assertTypesEqual(byte expectedType, byte actualType) { if (expectedType != actualType) { throw WasmException.format(Failure.UNEXPECTED_TYPE, "Expected type %s but got %s.", WasmType.toString(expectedType), WasmType.toString(actualType)); } } public void unwindStack(int size) { Assert.assertIntLessOrEqual(size, stackSize(), Failure.INVALID_STACK_SHRINK_SIZE); while (stackSize() > size) { stack.popBack(); } } public void useIntConstant(int constant) { intConstants.add(constant); } public int depth() { return ancestors.size(); } public int getStackSize(int offset) { if (depth() < offset + 1) { Assert.fail("Branch to offset " + offset + " larger than the nesting " + ancestorsStackSizes.size(), Failure.UNSPECIFIED_MALFORMED); } return ancestorsStackSizes.get(depth() - 1 - offset); } public void startBlock(WasmBlockNode block, boolean isLoopBody) { ancestors.add(block); ancestorsIsLoop.add(isLoopBody); ancestorsStackSizes.add(stackSize()); } public void endBlock() { ancestorsStackSizes.popBack(); ancestorsIsLoop.popBack(); ancestors.remove(ancestors.size() - 1); }
The continuation length is the number of values that should be on the stack when breaking from a break (BR, BR_IF or BR_TABLE) instruction to this block.

Given a block with type t1 -> t2, this is t2 (WasmNode.returnLength) if block is a normal block or the body of a condition, or t1 (WasmNode.inputLength()) if it is the body of a loop.

/** * The continuation length is the number of values that should be on the stack when breaking * from a break (BR, BR_IF or BR_TABLE) instruction to this block. * <p> * Given a block with type t1 -> t2, this is t2 ({@link WasmBlockNode#returnLength}) if block is * a normal block or the body of a condition, or t1 ({@link WasmBlockNode#inputLength()}) if it * is the body of a loop. */
public int getContinuationLength(int unwindLevel) { final int index = depth() - 1 - unwindLevel; final boolean isLoop = ancestorsIsLoop.get(index); final WasmBlockNode block = ancestors.get(index); return isLoop ? block.inputLength() : block.returnLength(); } public void checkContinuationType(int unwindLevel) { final int index = depth() - 1 - unwindLevel; final boolean isLoop = ancestorsIsLoop.get(index); final WasmBlockNode block = ancestors.get(index); if (isLoop) { Assert.assertIntEqual(block.inputLength(), 0, "A loop should not have parameters", Failure.LOOP_INPUT); } else { Assert.assertIntLessOrEqual(block.returnLength(), 1, "A block cannot return more than one value", Failure.MULTIPLE_RETURN_VALUES); if (block.returnLength() == 1) { topChecked(block.returnTypeId()); } } } public int getRootBlockReturnLength() { return ancestors.get(0).returnLength(); } public int stackSize() { return stack.size(); } public int maxStackSize() { return maxStackSize; } public int intConstantOffset() { return intConstants.size(); } public int[] intConstants() { return intConstants.toArray(); } public void saveBranchTable(int[] branchTable) { branchTables.add(branchTable); } public int branchTableOffset() { return branchTables.size(); } public int[][] branchTables() { return branchTables.toArray(new int[0][]); } public void incrementProfileCount() { ++profileCount; } public int profileCount() { return profileCount; } }