All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.oracle.truffle.js.nodes.JSNodeDecoder Maven / Gradle / Ivy

/*
 * Copyright (c) 2018, 2024, 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 com.oracle.truffle.js.nodes;

import java.lang.reflect.Array;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;

import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.FrameSlotKind;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.api.strings.TruffleString;
import com.oracle.truffle.js.codec.BinaryDecoder;
import com.oracle.truffle.js.codec.NodeDecoder;
import com.oracle.truffle.js.nodes.control.BreakTarget;
import com.oracle.truffle.js.nodes.control.ContinueTarget;
import com.oracle.truffle.js.runtime.BigInt;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.builtins.JSFunctionData;
import com.oracle.truffle.js.runtime.objects.Dead;
import com.oracle.truffle.js.runtime.objects.Null;
import com.oracle.truffle.js.runtime.objects.Undefined;

public class JSNodeDecoder {
    public enum Bytecode {
        ID_NOP,

        /** Create node using factory method. */
        ID_NODE,

        /** Return result. */
        ID_RETURN,
        /** Load integer constant into register. */
        ID_LDC_INT,
        /** Load long constant into register. */
        ID_LDC_LONG,
        /** Load boolean constant into register. */
        ID_LDC_BOOLEAN,
        /** Load double constant into register. */
        ID_LDC_DOUBLE,
        /** Load enum constant into register. */
        ID_LDC_ENUM,
        /** Load string constant into register. */
        ID_LDC_STRING,
        /** Load string constant into register. */
        ID_LDC_JAVA_STRING,
        /** Load singleton constant into register. */
        ID_LDC_SINGLETON,
        /** Load BigInt constant into register. */
        ID_LDC_BIGINT,

        /** Load argument into register. */
        ID_LD_ARG,
        /** Move register to register. */
        ID_MOV,

        /** Create dynamic array from registers. */
        ID_COLLECT_ARRAY,
        /** Create ArrayList from registers. */
        ID_COLLECT_LIST,
        /** Create CallTarget. */
        ID_CALL_TARGET,
        /** Create FrameDescriptor. */
        ID_FRAME_DESCRIPTOR,
        /** Add FrameSlot to FrameDescriptor. */
        ID_JSFRAME_SLOT,
        /** Create SourceSection. */
        ID_SOURCE_SECTION,

        /** Create JSFunctionData. */
        ID_FUNCTION_DATA,
        /** Fix up JSFunctionData. */
        ID_FUNCTION_DATA_NAME_FIXUP,

        /** Create {@link BreakTarget} or {@link ContinueTarget}. */
        ID_JUMP_TARGET,

        ID_CALL_EXTRACTED,
        ID_CALL_EXTRACTED_LAZY,

        ID_NODE_SOURCE_SECTION_FIXUP,
        ID_NODE_TAGS_FIXUP;

        static final Bytecode[] bcValues = values();
    }

    public static final int BREAK_TARGET_LABEL = 1;
    public static final int BREAK_TARGET_SWITCH = 2;
    public static final int CONTINUE_TARGET_LOOP = 3;
    public static final int CONTINUE_TARGET_UNLABELED_LOOP = 4;

    public static final int CONTEXT_ARG = -1;
    public static final int SOURCE_ARG = -2;

    private static final boolean VERBOSE = false;
    private static final NodeDecoder GEN = NodeFactoryDecoderGen.create();

    private static final Object[] SINGLETONS = new Object[]{null, Undefined.instance, Null.instance, Dead.instance()};

    public static int getSingletonIndex(Object singleton) {
        return Arrays.asList(SINGLETONS).indexOf(singleton);
    }

    public static int getChecksum() {
        return GEN.getChecksum();
    }

    public Object decodeNode(NodeDecoder.DecoderState state, NodeFactory nodeFactory, JSContext context, Source source) {
        while (state.hasRemaining()) {
            Bytecode bc = Bytecode.bcValues[state.getBytecode()];
            switch (bc) {
                case ID_NOP:
                    break;
                case ID_NODE: {
                    Object node = GEN.decodeNode(state, nodeFactory);
                    int dest = state.getReg();
                    if (dest >= 0) {
                        state.setObjReg(dest, node);
                    }
                    break;
                }
                case ID_RETURN:
                    return state.getObject();
                case ID_LDC_INT:
                    storeResult(state, state.getInt());
                    break;
                case ID_LDC_LONG:
                    storeResult(state, state.getLong());
                    break;
                case ID_LDC_BOOLEAN:
                    storeResult(state, state.getBoolean());
                    break;
                case ID_LDC_DOUBLE:
                    storeResult(state, state.getDouble());
                    break;
                case ID_LDC_ENUM:
                    storeResult(state, GEN.getClasses()[state.getInt()].getEnumConstants()[state.getInt()]);
                    break;
                case ID_LDC_STRING:
                    storeResult(state, state.getString());
                    break;
                case ID_LDC_JAVA_STRING:
                    storeResult(state, state.getString().toJavaStringUncached());
                    break;
                case ID_LDC_SINGLETON:
                    storeResult(state, SINGLETONS[state.getInt()]);
                    break;
                case ID_LDC_BIGINT:
                    storeResult(state, BigInt.valueOf(state.getString().toJavaStringUncached()));
                    break;
                case ID_MOV:
                    state.setObjReg(state.getReg(), state.getObjReg(state.getReg()));
                    break;
                case ID_LD_ARG:
                    int argIndex = state.getInt();
                    Object argument;
                    if (argIndex == CONTEXT_ARG) {
                        argument = context;
                    } else if (argIndex == SOURCE_ARG) {
                        argument = source;
                    } else {
                        assert argIndex >= 0 : argIndex;
                        argument = state.getArgument(argIndex);
                    }
                    storeResult(state, argument);
                    break;
                case ID_COLLECT_ARRAY: {
                    int componentTypeIndex = state.getInt();
                    int length = state.getInt();
                    Object array = Array.newInstance(GEN.getClasses()[componentTypeIndex], length);
                    if (array instanceof Object[]) {
                        Object[] objArray = (Object[]) array;
                        for (int i = 0; i < length; i++) {
                            Object value = state.getObject();
                            objArray[i] = value;
                        }
                    } else {
                        for (int i = 0; i < length; i++) {
                            Object value = state.getObject();
                            Array.set(array, i, value);
                        }
                    }
                    storeResult(state, array);
                    break;
                }
                case ID_COLLECT_LIST: {
                    int length = state.getInt();
                    ArrayList array = new ArrayList<>(length);
                    for (int i = 0; i < length; i++) {
                        array.add(state.getObject());
                    }
                    storeResult(state, array);
                    break;
                }

                case ID_CALL_TARGET:
                    storeResult(state, ((RootNode) state.getObject()).getCallTarget());
                    break;
                case ID_FRAME_DESCRIPTOR: {
                    int numberOfIndexedSlots = state.getInt();
                    FrameDescriptor.Builder b = FrameDescriptor.newBuilder(numberOfIndexedSlots).defaultValue(Undefined.instance);
                    Object[] names = new Object[numberOfIndexedSlots];
                    int[] flags = new int[numberOfIndexedSlots];
                    byte[] tags = new byte[numberOfIndexedSlots];
                    for (int i = 0; i < numberOfIndexedSlots; i++) {
                        names[i] = state.getObject();
                    }
                    for (int i = 0; i < numberOfIndexedSlots; i++) {
                        flags[i] = state.getInt();
                    }
                    for (int i = 0; i < numberOfIndexedSlots; i++) {
                        tags[i] = (byte) state.getInt();
                    }
                    for (int i = 0; i < numberOfIndexedSlots; i++) {
                        b.addSlot(FrameSlotKind.fromTag(tags[i]), names[i], flags[i]);
                    }
                    FrameDescriptor frameDescriptor = b.build();
                    storeResult(state, frameDescriptor);
                    break;
                }
                case ID_JSFRAME_SLOT: {
                    Object identifier = state.getObject();
                    int index = state.getInt();
                    int flags = state.getInt();
                    int tag = state.getInt();
                    FrameSlotKind kind = FrameSlotKind.fromTag((byte) tag);
                    JSFrameSlot frameSlot = new JSFrameSlot(index, identifier, flags, kind);
                    storeResult(state, frameSlot);
                    break;
                }
                case ID_SOURCE_SECTION: {
                    Source src = (Source) state.getObject();
                    int charIndex = state.getInt();
                    int charLength = state.getInt();
                    SourceSection sourceSection;
                    if ((charIndex < 0 || charLength < 0) || (src.getCharacters().length() == 0 && charIndex + charLength > 0)) {
                        sourceSection = src.createUnavailableSection();
                    } else {
                        sourceSection = src.createSection(charIndex, charLength);
                    }
                    storeResult(state, sourceSection);
                    break;
                }
                case ID_FUNCTION_DATA: {
                    JSContext ctx = (JSContext) state.getObject();
                    int length = state.getInt();
                    TruffleString functionName = state.getString();
                    int flags = state.getInt32();
                    JSFunctionData functionData = JSFunctionData.create(ctx, null, null, null, length, functionName, flags);
                    storeResult(state, functionData);
                    break;
                }
                case ID_FUNCTION_DATA_NAME_FIXUP: {
                    JSFunctionData functionData = (JSFunctionData) state.getObject();
                    TruffleString name = state.getString();
                    functionData.setName(name);
                    break;
                }
                case ID_JUMP_TARGET:
                    storeResult(state, createJumpTarget(state.getInt()));
                    break;
                case ID_CALL_EXTRACTED: {
                    final int position = state.getInt32();
                    if (VERBOSE) {
                        System.err.println("callex pos:" + position);
                    }
                    final Object[] arguments = getObjectArray(state);
                    final ByteBuffer buffer = state.getBuffer();
                    NodeDecoder.DecoderState extracted = new NodeDecoder.DecoderState(new BinaryDecoder(buffer, position), arguments);
                    storeResult(state, decodeNode(extracted, nodeFactory, context, source));
                    break;
                }
                case ID_CALL_EXTRACTED_LAZY: {
                    int bcPos = state.getBuffer().position() - 1;
                    final int position = state.getInt32();
                    if (VERBOSE) {
                        System.err.println("callex-lazy pos:" + position + " @" + bcPos);
                    }
                    JSFunctionData functionData = (JSFunctionData) state.getObject();
                    final Object[] arguments = getObjectArray(state);
                    final ByteBuffer buffer = state.getBuffer();
                    functionData.setLazyInit(new JSFunctionData.Initializer() {
                        @Override
                        public void initializeRoot(JSFunctionData fd) {
                            synchronized (this) {
                                if (fd.getRootNode() == null) {
                                    if (VERBOSE) {
                                        System.out.println("Decoding: " + fd.getName());
                                    }
                                    NodeDecoder.DecoderState extracted = new NodeDecoder.DecoderState(new BinaryDecoder(buffer, position), arguments);
                                    decodeNode(extracted, nodeFactory, context, source);
                                    fd.releaseLazyInit();
                                }
                            }
                        }
                    });
                    break;
                }
                case ID_NODE_SOURCE_SECTION_FIXUP: {
                    JavaScriptNode jsnode = (JavaScriptNode) state.getObject();
                    int charIndex = state.getInt();
                    int charLength = state.getInt();
                    if ((charIndex < 0 || charLength < 0) || (source.getCharacters().length() == 0 && charIndex + charLength > 0)) {
                        jsnode.setSourceSection(source.createUnavailableSection());
                    } else {
                        jsnode.setSourceSection(source, charIndex, charLength);
                    }
                    break;
                }
                case ID_NODE_TAGS_FIXUP: {
                    JavaScriptNode jsnode = (JavaScriptNode) state.getObject();
                    boolean hasStatementTag = state.getBoolean();
                    boolean hasCallTag = state.getBoolean();
                    boolean hasExpressionTag = state.getBoolean();
                    boolean hasRootBodyTag = state.getBoolean();
                    if (hasStatementTag) {
                        jsnode.addStatementTag();
                    }
                    if (hasCallTag) {
                        jsnode.addCallTag();
                    }
                    if (hasExpressionTag) {
                        jsnode.addExpressionTag();
                    }
                    if (hasRootBodyTag) {
                        jsnode.addRootBodyTag();
                    }
                    break;
                }
                default:
                    throw new IllegalStateException("invalid bytecode " + bc);
            }
        }
        throw new IllegalStateException("reached end of buffer without return");
    }

    private static void storeResult(NodeDecoder.DecoderState state, Object value) {
        state.setObjReg(state.getReg(), value);
    }

    private static BreakTarget createJumpTarget(int type) {
        switch (type) {
            case BREAK_TARGET_LABEL:
                return BreakTarget.forLabel(null, -1);
            case BREAK_TARGET_SWITCH:
                return BreakTarget.forSwitch();
            case CONTINUE_TARGET_LOOP:
                return ContinueTarget.forLoop(null, -1);
            case CONTINUE_TARGET_UNLABELED_LOOP:
                return ContinueTarget.forUnlabeledLoop();
        }
        throw new IllegalStateException("invalid jump target");
    }

    private static Object[] getObjectArray(NodeDecoder.DecoderState state) {
        int length = state.getInt();
        Object[] array = new Object[length];
        for (int i = 0; i < length; i++) {
            array[i] = state.getObject();
        }
        return array;
    }
}