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

org.neo4j.codegen.api.SizeEstimation.scala Maven / Gradle / Ivy

/*
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [http://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.codegen.api

import java.lang.reflect.Modifier.isInterface

import org.neo4j.codegen.TypeReference
import org.neo4j.codegen.api.IntermediateRepresentation.block
import org.neo4j.codegen.api.IntermediateRepresentation.declare
import org.neo4j.codegen.api.IntermediateRepresentation.declareAndAssign
import org.neo4j.cypher.internal.util.Foldable.SkipChildren
import org.neo4j.cypher.internal.util.Foldable.TraverseChildren

import scala.collection.mutable

object SizeEstimation {
  private val JUMP_INSTRUCTION = 3
  private val FIELD_INSTRUCTION = 3
  private val INVOKE = 3
  private val INVOKE_INTERFACE = 5
  private val LDC_INSTRUCTION = 2
  private val WIDE_LDC_INSTRUCTION = 3
  private val TYPE_INSTRUCTION = 3

  /**
   * Estimates the number of bytes the byte code that is required for the generating
   * the byte code of this method.
   * @param m The method to estimate
   * @return the estimation of the number of bytes this will use
   */
  def estimateByteCodeSize(m: MethodDeclaration): Int = {
    val fullBody =
      block(
        (m.parameters.map(p => declare(p.typ, p.name)) ++
          m.localVariables.distinct.map(v => declareAndAssign(v.typ, v.name, v.value))) :+
          m.body:_*)

    estimateByteCodeSize(fullBody, 0)
  }

  def estimateByteCodeSize(ir: IntermediateRepresentation, initialNumberOfVariables: Int): Int = {
    //0 is always `this`
    var localVarCount = initialNumberOfVariables + 1
    //keeps track of the index of each local variable
    val locals = mutable.Map.empty[String, Int]
    def declare(typeReference: TypeReference, name: String): Unit = {
      locals.put(name, localVarCount)
      if (typeReference.simpleName() == "long" || typeReference.simpleName() == "double") {
        localVarCount += 2
      } else {
        localVarCount += 1
      }
    }
    def localVarInstruction(name: String) = {
      val index = locals.getOrElse(name, initialNumberOfVariables)
      if (index < 4) 1 else if (index >= 256) 4 else 2
    }
    def sizeOfIntPush(i: Integer) = {
      if (i < 6 && i >= -1) 1
      else if (i <= Byte.MaxValue && i >= Byte.MinValue) 2 //BIPUSH
      else if (i <= Short.MaxValue && i >= Short.MinValue) 3 //SIPUSH
      else LDC_INSTRUCTION//constant pool
    }
    def costOfNot(not: Not)= not.test match {
      case _: Not => 0
      case _: Constant => 0
      case _: Gt | _: Gte | _: Lt | _: Lte | _: Eq | _: NotEq | _: IsNull => 0
      case _ => 2 /*true and false*/  + 2 * JUMP_INSTRUCTION
    }

    val visitedOneTimes = mutable.Set.empty[IntermediateRepresentation]
    ir.folder.treeFold(0) {
      case op: IntermediateRepresentation =>

        var visitChildren = true

        val bytesForInstruction = op match {
          //Freebies
          case oneTime: OneTime =>
            if (!visitedOneTimes.add(oneTime)) {
              visitChildren = false
            }
            0
          case _: Block | Noop => 0

          //Single byte instructions
          case _: ArraySet | _: ArrayLength | _: ArrayLoad | _: Add | _: Subtract | _: Multiply | _: Returns | _: Self | _: Throw => 1

          //multi byte instructions
          case i: InvokeSideEffect => if (isInterface(i.method.owner.modifiers())) INVOKE_INTERFACE else INVOKE
          case i: Invoke => if (isInterface(i.method.owner.modifiers())) INVOKE_INTERFACE else INVOKE
          case _: InvokeStatic | _: InvokeStaticSideEffect => INVOKE
          case Load(name, _) => localVarInstruction(name)
          case _: LoadField => 1 + FIELD_INSTRUCTION // load this + 3 bytes for the field
          case _: SetField => 1 + FIELD_INSTRUCTION // load this + 3 bytes for setting the field
          case _: GetStatic => FIELD_INSTRUCTION
          case DeclareLocalVariable(typ, name) =>
            declare(typ, name)
            0
          case AssignToLocalVariable(name, _) =>
            localVarInstruction(name)
          case Constant(constant) => constant match {
            case i: Int => sizeOfIntPush(i)
            case l: Long => if (l == 0L || l == 1L) 1 else WIDE_LDC_INSTRUCTION //constant pool (unless 0 or 1)
            case _: Boolean => 1
            case _: Double => WIDE_LDC_INSTRUCTION
            case null => 1
            case _ => LDC_INSTRUCTION
          }
          case ArrayLiteral(typ, values) =>
            val numberOfElements = values.length
            val sizeOfNewArray = if (typ.isPrimitive) 2 else 3
            sizeOfIntPush(numberOfElements) + sizeOfNewArray + (0 until numberOfElements).map(i => 1 + sizeOfIntPush(i) + 1).sum
          case NewArray(typ, size) => sizeOfIntPush(size) + (if (typ.isPrimitive) 2 else 3)

          //Conditions and loops
          case _: Ternary => 2 * JUMP_INSTRUCTION //two jump instructions
          case Condition(ands: BooleanAnd, _, onFalse) =>
            //Here we subtract the cost of the jump instruction + TRUE + FALSE since we can combine
            //it with the jump instruction of the conditional

            //for each or contained in the ands we also save in on two JUMP INSTRUCTIONS and TRUE, FALSE
            val numberOfOrs = ands.folder.treeCount {
              case _: BooleanOr => true
            }
            -5 + onFalse.map(_ => JUMP_INSTRUCTION).getOrElse(0) - numberOfOrs * 8
          case Condition(_: BooleanOr, _, onFalse) =>
            //Here we subtract the cost of the jump instruction + TRUE + FALSE since we can combine
            //it with the jump instruction of the conditional
            -5 + onFalse.map(_ => JUMP_INSTRUCTION).getOrElse(0)
          case Condition(Not(_: BooleanAnd), _, onFalse) => onFalse.map(_ => JUMP_INSTRUCTION).getOrElse(0) - 8 - 5
          case Condition(Not(_: BooleanOr), _, onFalse) => onFalse.map(_ => JUMP_INSTRUCTION).getOrElse(0) - 8 - 5
          case Condition(not: Not, _, onFalse) => 3 - costOfNot(not) + onFalse.map(_ => JUMP_INSTRUCTION).getOrElse(0)
          case Condition(_: IsNull, _, onFalse) => 3 - 8 + onFalse.map(_ => JUMP_INSTRUCTION).getOrElse(0)
          case c: Condition => 3 + c.onFalse.map(_ => JUMP_INSTRUCTION).getOrElse(0) //single jump instruction takes 3 bytes

          case Loop(ands: BooleanAnd, _, _) =>
            //Here we subtract the cost of the jump instruction + TRUE + FALSE since we can combine
            //it with the jump instruction of the loop

            //for each or contained in the ands we also save in on two JUMP INSTRUCTIONS and TRUE, FALSE
            val numberOfOrs = ands.folder.treeCount {
              case _: BooleanOr => true
            }
            -5 + JUMP_INSTRUCTION - numberOfOrs * 8
          case Loop(_: BooleanOr, _, _) =>
            //Here we subtract the cost of the jump instruction + TRUE + FALSE since we can combine
            //it with the jump instruction of the loop
            -5 + JUMP_INSTRUCTION
          case Loop(Not(_: BooleanAnd), _, _) => JUMP_INSTRUCTION - 5 - 8 //subtract the cost of OR and the cost of NOT
          case Loop(Not(_: BooleanOr), _, _) => JUMP_INSTRUCTION - 5 - 8 //subtract the cost of OR and the cost of NOT
          case Loop(not: Not, _, _) => 2 * JUMP_INSTRUCTION - costOfNot(not)
          case Loop(_: IsNull, _, _) => 2 * JUMP_INSTRUCTION - 8
          case _: Loop => 2 * JUMP_INSTRUCTION //two jump instructions

          //Boolean operations
          case BooleanAnd(as) =>
            //For a stand-alone `and` we generate a single jump instruction (3 bytes) and a TRUE and FALSE for the different cases
            //furthermore for each argument we generate a jump instruction
            (1 + 1 + JUMP_INSTRUCTION) + as.length * 3
          //For a stand-alone `or` we generate a single jump instruction (3 bytes) and a TRUE and FALSE for the different cases
          //furthermore for each argument we generate a jump instruction
          case BooleanOr(as) => (1 + 1 + JUMP_INSTRUCTION) + as.length * 3
          case c: Gt => if (c.lhs.typeReference == TypeReference.INT) 8 else 9
          case c: Gte => if (c.lhs.typeReference == TypeReference.INT) 8 else 9
          case c: Lt => if (c.lhs.typeReference == TypeReference.INT) 8 else 9
          case c: Lte => if (c.lhs.typeReference == TypeReference.INT) 8 else 9
          case c: Eq => if (c.lhs.typeReference == TypeReference.INT || !c.lhs.typeReference.isPrimitive) 8 else 9
          case c: NotEq => if (c.lhs.typeReference == TypeReference.INT || !c.lhs.typeReference.isPrimitive) 8 else 9
          case _: IsNull =>  2 /*true and false*/  + 2 * JUMP_INSTRUCTION
          case n: Not => costOfNot(n)

          //Misc operations
          case _: NewInstance | _: NewInstanceInnerClass => 3 /*NEW*/ + 1 /*DUP*/ + INVOKE /*INVOKESPECIAL*/
          case _: Break => JUMP_INSTRUCTION //an extra jump instruction
          case _: Box => INVOKE //boils down to INVOKESTATIC, eg `Long.valueOf(x)`
          case _: Unbox => INVOKE //boils down to INVOKEVIRTUAL, eg `x.longValue()`
          case Cast(to, _) => if (to.isPrimitive) 1 else TYPE_INSTRUCTION
          case _: InstanceOf => TYPE_INSTRUCTION
          case t: TryCatch =>
            declare(t.typeReference, t.name)
            JUMP_INSTRUCTION + localVarInstruction(t.name)

          case unknown: IntermediateRepresentation => throw new IllegalStateException(s"Don't know how many bytes $unknown will use")
        }

        if (visitChildren) acc => TraverseChildren(acc + bytesForInstruction)
        else acc => SkipChildren(acc + bytesForInstruction)
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy