/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package org.jruby.ir.persistence;

import org.jcodings.Encoding;
import org.jcodings.EncodingDB;
import org.jruby.RubyInstanceConfig;
import org.jruby.RubySymbol;
import org.jruby.ir.IRManager;
import org.jruby.ir.IRScope;
import org.jruby.ir.IRScopeType;
import org.jruby.ir.Operation;
import org.jruby.ir.instructions.*;
import org.jruby.ir.instructions.defined.GetErrorInfoInstr;
import org.jruby.ir.instructions.defined.RestoreErrorInfoInstr;
import org.jruby.ir.operands.*;
import org.jruby.parser.StaticScope;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jruby.runtime.RubyEvent;
import org.jruby.runtime.Signature;
import org.jruby.util.ByteList;

import static com.headius.backport9.buffer.Buffers.positionBuffer;

Author:enebo
/** * * @author enebo */
public class IRReaderStream implements IRReaderDecoder, IRPersistenceValues { private final ByteBuffer buf; private IRManager manager; private final List<IRScope> scopes = new ArrayList<>(); private IRScope currentScope = null; // FIXME: This is not thread-safe and more than a little gross
Filename to use for the script
/** Filename to use for the script */
private final ByteList filename; public IRReaderStream(IRManager manager, InputStream stream, ByteList filename) { ByteBuffer buf = readIntoBuffer(stream); this.manager = manager; this.buf = buf; this.filename = filename; } public IRReaderStream(IRManager manager, File file, ByteList filename) { this.manager = manager; ByteBuffer buf = null; try (FileInputStream fis = new FileInputStream(file)){ buf = readIntoBuffer(fis); } catch (IOException ex) { Logger.getLogger(IRReaderStream.class.getName()).log(Level.SEVERE, null, ex); } this.buf = buf; this.filename = filename; } private ByteBuffer readIntoBuffer(InputStream stream) { ByteBuffer buf = null; try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] bytes = new byte[8192]; int r; while ((r = stream.read(bytes)) > 0) baos.write(bytes, 0, r); if (RubyInstanceConfig.IR_READING_DEBUG) System.out.println("READ IN " + baos.size() + " BYTES OF DATA FROM"); buf = ByteBuffer.wrap(baos.toByteArray()); } catch (IOException ex) { Logger.getLogger(IRReaderStream.class.getName()).log(Level.SEVERE, null, ex); } return buf; } @Override public ByteList getFilename() { return filename; } @Override public ByteList decodeByteList() { return new ByteList(decodeByteArray(), decodeEncoding()); } @Override public byte[] decodeByteArray() { int size = decodeInt(); byte[] bytes = new byte[size]; buf.get(bytes); return bytes; } @Override public Encoding decodeEncoding() { byte[] encodingName = decodeByteArray(); return EncodingDB.getEncodings().get(encodingName).getEncoding(); } @Override public Label decodeLabel() { return (Label) decodeOperand(); } @Override public Label[] decodeLabelArray() { int size = decodeInt(); Label[] labels = new Label[size]; for (int i = 0; i < size; i++) { labels[i] = decodeLabel(); } return labels; } @Override public RubyEvent decodeRubyEvent() { return RubyEvent.fromOrdinal(decodeInt()); } @Override public RubySymbol decodeSymbol() { int strLength = decodeInt(); if (strLength == NULL_STRING) return null; byte[] bytes = new byte[strLength]; // FIXME: This seems really innefficient buf.get(bytes); Encoding encoding = decodeEncoding(); return currentScope.getManager().getRuntime().newSymbol(new ByteList(bytes, encoding, false)); } @Override public String decodeString() { int strLength = decodeInt(); if (strLength == NULL_STRING) return null; byte[] bytes = new byte[strLength]; // FIXME: This seems really innefficient buf.get(bytes); String newString = new String(bytes).intern(); if (RubyInstanceConfig.IR_READING_DEBUG) System.out.println("STR<" + newString + ">"); return newString; } @Override public void addScope(IRScope scope) { scopes.add(scope); currentScope = scope; } @Override public IRScope getCurrentScope() { return currentScope; } @Override public String[] decodeStringArray() { int arrayLength = decodeInt(); String[] array = new String[arrayLength]; for (int i = 0; i < arrayLength; i++) { array[i] = decodeString(); } return array; } @Override public int[] decodeIntArray() { int size = decodeInt(); int[] ints = new int[size]; for (int i = 0; i < size; i++) { ints[i] = decodeInt(); } return ints; } private Map<String, Operand> vars = null; @Override public Map<String, Operand> getVars() { return vars; } @Override public List<Instr> decodeInstructionsAt(IRScope scope, int offset) { currentScope = scope; vars = new HashMap<>(); positionBuffer(buf, offset); int numberOfInstructions = decodeInt(); if (RubyInstanceConfig.IR_READING_DEBUG) System.out.println("Number of Instructions: " + numberOfInstructions); List<Instr> instrs = new ArrayList<>(numberOfInstructions); for (int i = 0; i < numberOfInstructions; i++) { Instr decodedInstr = decodeInstr(); if (RubyInstanceConfig.IR_READING_DEBUG) System.out.println(">INSTR = " + decodedInstr); // FIXME: It would be nice to not run this and just record flag state at encode time decodedInstr.computeScopeFlags(scope); instrs.add(decodedInstr); } return instrs; } public Instr decodeInstr() { Operation operation = decodeOperation(); switch (operation) { case ALIAS: return AliasInstr.decode(this); case ARG_SCOPE_DEPTH: return ArgScopeDepthInstr.decode(this); case ARRAY_DEREF: return ArrayDerefInstr.decode(this); case ATTR_ASSIGN: return AttrAssignInstr.decode(this); case AS_STRING: return AsStringInstr.decode(this); case B_FALSE: return BFalseInstr.decode(this); case B_NIL: return BNilInstr.decode(this); case B_SWITCH: return BSwitchInstr.decode(this); case B_TRUE: return BTrueInstr.decode(this); case B_UNDEF: return BUndefInstr.decode(this); case BINDING_LOAD: return LoadLocalVarInstr.decode(this); case BINDING_STORE: return StoreLocalVarInstr.decode(this); case BLOCK_GIVEN: return BlockGivenInstr.decode(this); case BNE: return BNEInstr.decode(this); case BREAK: return BreakInstr.decode(this); case BUILD_BACKREF: return BuildBackrefInstr.decode(this); case BUILD_COMPOUND_ARRAY: return BuildCompoundArrayInstr.decode(this); case BUILD_COMPOUND_STRING: return BuildCompoundStringInstr.decode(this); case BUILD_DREGEXP: return BuildDynRegExpInstr.decode(this); case BUILD_RANGE: return BuildRangeInstr.decode(this); case BUILD_SPLAT: return BuildSplatInstr.decode(this); case CALL_1F: case CALL_1D: case CALL_1O: case CALL_2O: case CALL_1OB: case CALL_0O: case CALL: return CallInstr.decode(this); case CHECK_ARGS_ARRAY_ARITY: return CheckArgsArrayArityInstr.decode(this); case CHECK_ARITY: return CheckArityInstr.decode(this); case CHECK_FOR_LJE: return CheckForLJEInstr.decode(this); case CLASS_SUPER: return ClassSuperInstr.decode(this); case CLASS_VAR_MODULE: return GetClassVarContainerModuleInstr.decode(this); case COPY: return CopyInstr.decode(this); case DEF_CLASS: return DefineClassInstr.decode(this); case DEF_CLASS_METH: return DefineClassMethodInstr.decode(this); case DEF_INST_METH: return DefineInstanceMethodInstr.decode(this); case DEF_META_CLASS: return DefineMetaClassInstr.decode(this); case DEF_MODULE: return DefineModuleInstr.decode(this); case EQQ: return EQQInstr.decode(this); case EXC_REGION_END: return new ExceptionRegionEndMarkerInstr(); case EXC_REGION_START: return ExceptionRegionStartMarkerInstr.decode(this); case GET_CVAR: return GetClassVariableInstr.decode(this); case GET_ENCODING: return GetEncodingInstr.decode(this); case GET_ERROR_INFO: return GetErrorInfoInstr.decode(this); case GET_FIELD: return GetFieldInstr.decode(this); case GET_GLOBAL_VAR: return GetGlobalVariableInstr.decode(this); case GVAR_ALIAS: return GVarAliasInstr.decode(this); case INHERITANCE_SEARCH_CONST: return InheritanceSearchConstInstr.decode(this); case INSTANCE_SUPER: return InstanceSuperInstr.decode(this); case JUMP: return JumpInstr.decode(this); case LABEL: return LabelInstr.decode(this); case LAMBDA: return BuildLambdaInstr.decode(this); case LEXICAL_SEARCH_CONST: return LexicalSearchConstInstr.decode(this); case LOAD_FRAME_CLOSURE: return LoadFrameClosureInstr.decode(this); case LOAD_IMPLICIT_CLOSURE: return LoadImplicitClosureInstr.decode(this); case LINE_NUM: return LineNumberInstr.decode(this); case MASGN_OPT: return OptArgMultipleAsgnInstr.decode(this); case MASGN_REQD: return ReqdArgMultipleAsgnInstr.decode(this); case MASGN_REST: return RestArgMultipleAsgnInstr.decode(this); case MATCH: return MatchInstr.decode(this); case NONLOCAL_RETURN: return NonlocalReturnInstr.decode(this); case NOP: return NopInstr.NOP; case NORESULT_CALL: case NORESULT_CALL_1O: return NoResultCallInstr.decode(this); case POP_BINDING: return PopBindingInstr.decode(this); case POP_METHOD_FRAME: return PopMethodFrameInstr.decode(this); case PROCESS_MODULE_BODY: return ProcessModuleBodyInstr.decode(this); case PUSH_METHOD_BINDING: return PushMethodBindingInstr.decode(this); case PUSH_METHOD_FRAME: return PushMethodFrameInstr.decode(this); case PUT_CONST: return PutConstInstr.decode(this); case PUT_CVAR: return PutClassVariableInstr.decode(this); case PUT_FIELD: return PutFieldInstr.decode(this); case PUT_GLOBAL_VAR: return PutGlobalVarInstr.decode(this); case RAISE_ARGUMENT_ERROR: return RaiseArgumentErrorInstr.decode(this); case RAISE_REQUIRED_KEYWORD_ARGUMENT_ERROR: return RaiseRequiredKeywordArgumentError.decode(this); case RECORD_END_BLOCK: return RecordEndBlockInstr.decode(this); case REIFY_CLOSURE: return ReifyClosureInstr.decode(this); case RECV_RUBY_EXC: return ReceiveRubyExceptionInstr.decode(this); case RECV_JRUBY_EXC: return ReceiveJRubyExceptionInstr.decode(this); case RECV_KW_ARG: return ReceiveKeywordArgInstr.decode(this); case RECV_KW_REST_ARG: return ReceiveKeywordRestArgInstr.decode(this); case RECV_OPT_ARG: return ReceiveOptArgInstr.decode(this); case RECV_POST_REQD_ARG: return ReceivePostReqdArgInstr.decode(this); case RECV_PRE_REQD_ARG: return ReceivePreReqdArgInstr.decode(this); case RECV_REST_ARG: return ReceiveRestArgInstr.decode(this); case RECV_SELF: return ReceiveSelfInstr.decode(this); case RESCUE_EQQ: return RescueEQQInstr.decode(this); case RESTORE_ERROR_INFO: return RestoreErrorInfoInstr.decode(this); case RETURN: return ReturnInstr.decode(this); case RETURN_OR_RETHROW_SAVED_EXC: return ReturnOrRethrowSavedExcInstr.decode(this); case RUNTIME_HELPER: return RuntimeHelperCall.decode(this); case SEARCH_CONST: return SearchConstInstr.decode(this); case SEARCH_MODULE_FOR_CONST: return SearchModuleForConstInstr.decode(this); case SET_CAPTURED_VAR: return SetCapturedVarInstr.decode(this); case TRACE: return TraceInstr.decode(this); case THREAD_POLL: return ThreadPollInstr.decode(this); case THROW: return ThrowExceptionInstr.decode(this); case TO_ARY: return ToAryInstr.decode(this); case TOGGLE_BACKTRACE: return ToggleBacktraceInstr.decode(this); case UNDEF_METHOD: return UndefMethodInstr.decode(this); case UNRESOLVED_SUPER: return UnresolvedSuperInstr.decode(this); case YIELD: return YieldInstr.decode(this); case ZSUPER: return ZSuperInstr.decode(this); } throw new IllegalArgumentException("Unhandled operation: " + operation); } @Override public IRScopeType decodeIRScopeType() { return IRScopeType.fromOrdinal(decodeInt()); } @Override public TemporaryVariableType decodeTemporaryVariableType() { return TemporaryVariableType.fromOrdinal(decodeInt()); } @Override public StaticScope.Type decodeStaticScopeType() { return StaticScope.Type.fromOrdinal(decodeInt()); } @Override public Operation decodeOperation() { Operation operation = Operation.fromOrdinal(decodeInt()); if (RubyInstanceConfig.IR_READING_DEBUG) System.out.println("INSTR<" + operation); return operation; } @Override public Operand decodeOperand() { OperandType operandType = decodeOperandType(); if (RubyInstanceConfig.IR_READING_DEBUG) System.out.println("OP<" + operandType); Operand decodedOperand = decode(operandType); if (RubyInstanceConfig.IR_READING_DEBUG) System.out.println(">OP = " + decodedOperand); return decodedOperand; } @Override public Variable decodeVariable() { return (Variable) decodeOperand(); } @Override public Operand[] decodeOperandArray() { int size = decodeInt(); Operand[] list = new Operand[size]; for (int i = 0; i < size; i++) { list[i] = decodeOperand(); } return list; } @Override public List<Operand> decodeOperandList() { int size = decodeInt(); if (RubyInstanceConfig.IR_READING_DEBUG) System.out.println("OPERAND LIST of size: " + size); List<Operand> list = new ArrayList<>(size); for (int i = 0; i < size; i++) { if (RubyInstanceConfig.IR_READING_DEBUG) System.out.println("OPERAND #" + i); list.add(decodeOperand()); } return list; } @Override public OperandType decodeOperandType() { return OperandType.fromCoded(decodeByte()); } @Override public boolean decodeBoolean() { byte value = buf.get(); if (value == TRUE) return true; if (value == FALSE) return false; throw new IllegalArgumentException("Value (" + ((int) value) + ", " + (char) value + ") is not a boolean."); } @Override public byte decodeByte() { return buf.get(); } @Override public char decodeChar() { return buf.getChar(); } @Override public int decodeInt() { byte b = buf.get(); return b == FULL ? buf.getInt() : (int) b; } @Override public int decodeIntRaw() { return buf.getInt(); } @Override public long decodeLong() { byte b = buf.get(); return b == FULL ? buf.getLong() : (int) b; } @Override public double decodeDouble() { return buf.getDouble(); } @Override public float decodeFloat() { return buf.getFloat(); } @Override public IRScope decodeScope() { return scopes.get(decodeInt()); } @Override public Signature decodeSignature() { return Signature.decode(decodeLong()); } @Override public void seek(int headersOffset) { positionBuffer(buf, headersOffset); } public Operand decode(OperandType type) { if (RubyInstanceConfig.IR_READING_DEBUG) System.out.println("Decoding operand " + type); switch (type) { case ARRAY: return Array.decode(this); case BIGNUM: return Bignum.decode(this); case BOOLEAN: return org.jruby.ir.operands.Boolean.decode(this); case COMPLEX: return Complex.decode(this); case CURRENT_SCOPE: return CurrentScope.decode(this); case DYNAMIC_SYMBOL: return DynamicSymbol.decode(this); case FILENAME: return Filename.decode(this); case FIXNUM: return Fixnum.decode(this); case FLOAT: return org.jruby.ir.operands.Float.decode(this); case FROZEN_STRING: return FrozenString.decode(this); case GLOBAL_VARIABLE: return GlobalVariable.decode(this); case HASH: return Hash.decode(this); case IR_EXCEPTION: return IRException.decode(this); case LABEL: return Label.decode(this); case LOCAL_VARIABLE: return LocalVariable.decode(this); case NIL: return manager.getNil(); case NTH_REF: return NthRef.decode(this); case NULL_BLOCK: return NullBlock.decode(this); case OBJECT_CLASS: return new ObjectClass(); case RATIONAL: return Rational.decode(this); case REGEXP: return Regexp.decode(this); case SCOPE_MODULE: return ScopeModule.decode(this); case SELF: return Self.SELF; case SPLAT: return Splat.decode(this); case STANDARD_ERROR: return new StandardError(); case STRING_LITERAL: return StringLiteral.decode(this); case SVALUE: return SValue.decode(this); case SYMBOL: return Symbol.decode(this); case SYMBOL_PROC: return SymbolProc.decode(this); case TEMPORARY_VARIABLE: return TemporaryLocalVariable.decode(this); case UNBOXED_BOOLEAN: return new UnboxedBoolean(decodeBoolean()); case UNBOXED_FIXNUM: return new UnboxedFixnum(decodeLong()); case UNBOXED_FLOAT: return new UnboxedFloat(decodeDouble()); case UNDEFINED_VALUE: return UndefinedValue.UNDEFINED; case UNEXECUTABLE_NIL: return UnexecutableNil.U_NIL; case WRAPPED_IR_CLOSURE: return WrappedIRClosure.decode(this); } return null; } }