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

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

There is a newer version: 5.25.1
Show newest version
/*
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [https://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 org.neo4j.codegen
import org.neo4j.codegen.ClassHandle
import org.neo4j.codegen.CodeGenerationNotSupportedException
import org.neo4j.codegen.CodeGenerator
import org.neo4j.codegen.CodeGenerator.generateCode
import org.neo4j.codegen.CodeGeneratorOption
import org.neo4j.codegen.DisassemblyVisitor
import org.neo4j.codegen.Expression
import org.neo4j.codegen.Expression.constant
import org.neo4j.codegen.Expression.getStatic
import org.neo4j.codegen.Expression.invoke
import org.neo4j.codegen.Expression.invokeSuper
import org.neo4j.codegen.Expression.newInitializedArray
import org.neo4j.codegen.FieldReference
import org.neo4j.codegen.FieldReference.field
import org.neo4j.codegen.FieldReference.staticField
import org.neo4j.codegen.Parameter.param
import org.neo4j.codegen.TypeReference
import org.neo4j.codegen.TypeReference.OBJECT
import org.neo4j.codegen.api.CodeGeneration.ByteCodeGeneration
import org.neo4j.codegen.api.CodeGeneration.CodeGenerationMode
import org.neo4j.codegen.api.CodeGeneration.CodeGenerationMode.modeFromDebugOptions
import org.neo4j.codegen.api.CodeGeneration.DEBUG_PRINT_BYTECODE
import org.neo4j.codegen.api.CodeGeneration.DEBUG_PRINT_SOURCE
import org.neo4j.codegen.api.CodeGeneration.SourceCodeGeneration
import org.neo4j.codegen.api.SizeEstimation.estimateByteCodeSize
import org.neo4j.codegen.bytecode.ByteCode.BYTECODE
import org.neo4j.codegen.bytecode.ByteCode.PRINT_BYTECODE
import org.neo4j.codegen.source.SourceCode.PRINT_SOURCE
import org.neo4j.codegen.source.SourceCode.SOURCECODE
import org.neo4j.codegen.source.SourceCode.sourceLocation
import org.neo4j.codegen.source.SourceVisitor
import org.neo4j.cypher.internal.options.CypherDebugOption
import org.neo4j.cypher.internal.options.CypherDebugOptions
import org.neo4j.exceptions.CantCompileQueryException

import java.nio.file.Path
import java.nio.file.Paths

import scala.annotation.nowarn
import scala.collection.mutable.ArrayBuffer
import scala.language.existentials

/**
 * Produces runnable code from an IntermediateRepresentation
 */
object CodeGeneration {

  // the jvm doesn't allow methods bigger than 65535 bytes,
  final val MAX_METHOD_LIMIT: Int = 65535

  // Use these options for Debugging. They will print generated code to stdout
  private val DEBUG_PRINT_SOURCE = false
  private val DEBUG_PRINT_BYTECODE = false

  final val GENERATE_JAVA_SOURCE_DEBUG_OPTION = CypherDebugOption.generateJavaSource.name
  final val GENERATED_SOURCE_LOCATION_PROPERTY = "org.neo4j.cypher.DEBUG.generated_source_location"

  def fromDebugOptions(methodLimit: Int = MAX_METHOD_LIMIT, debugOptions: CypherDebugOptions): CodeGeneration =
    codeGeneration(methodLimit, modeFromDebugOptions(debugOptions))

  def codeGeneration(
    methodLimit: Int = MAX_METHOD_LIMIT,
    mode: CodeGenerationMode =
      ByteCodeGeneration(new CodeSaver(false, false))
  ): CodeGeneration = new CodeGeneration(methodLimit, mode)

  sealed trait CodeGenerationMode {
    def saver: CodeSaver
  }

  case class ByteCodeGeneration(saver: CodeSaver) extends CodeGenerationMode

  case class SourceCodeGeneration(saver: CodeSaver) extends CodeGenerationMode

  object CodeGenerationMode {

    def modeFromDebugOptions(debugOptions: CypherDebugOptions): CodeGenerationMode = {
      if (debugOptions.generateJavaSourceEnabled) {
        val saveSourceToFileLocation = Option(System.getProperty(GENERATED_SOURCE_LOCATION_PROPERTY)).map(Paths.get(_))
        val saver =
          new CodeSaver(debugOptions.showJavaSourceEnabled, debugOptions.showBytecodeEnabled, saveSourceToFileLocation)
        SourceCodeGeneration(saver)
      } else {
        val saver = new CodeSaver(false, debugOptions.showBytecodeEnabled)
        ByteCodeGeneration(saver)
      }
    }
  }

  class CodeSaver(saveSource: Boolean, saveByteCode: Boolean, saveSourceToFileLocation: Option[Path] = None) {
    private val _source: ArrayBuffer[(String, String)] = new ArrayBuffer()
    private val _bytecode: ArrayBuffer[(String, String)] = new ArrayBuffer()

    private def sourceVisitor: SourceVisitor =
      (reference: TypeReference, sourceCode: CharSequence) => _source += (reference.name() -> sourceCode.toString)

    private def byteCodeVisitor: DisassemblyVisitor =
      (className: String, disassembly: CharSequence) => _bytecode += (className -> disassembly.toString)

    def options: List[CodeGeneratorOption] = {
      var l: List[CodeGeneratorOption] = Nil
      if (saveSource) l ::= sourceVisitor
      if (saveByteCode) l ::= byteCodeVisitor
      saveSourceToFileLocation.foreach(path => l ::= sourceLocation(path))
      l
    }

    def sourceCode: Seq[(String, String)] = _source.toSeq

    def bytecode: Seq[(String, String)] = _bytecode.toSeq
  }
}

class CodeGeneration(methodLimit: Int, val codeGenerationMode: CodeGenerationMode) {

  def createGenerator(): CodeGenerator = {
    var (strategy, options) = (codeGenerationMode, DEBUG_PRINT_SOURCE) match {
      case (SourceCodeGeneration(saver), _)   => (SOURCECODE, saver.options)
      case (ByteCodeGeneration(saver), true)  => (SOURCECODE, saver.options)
      case (ByteCodeGeneration(saver), false) => (BYTECODE, saver.options)
    }
    if (DEBUG_PRINT_SOURCE) options ::= PRINT_SOURCE
    if (DEBUG_PRINT_BYTECODE) options ::= PRINT_BYTECODE

    generateCode(classOf[IntermediateRepresentation].getClassLoader, strategy, options: _*)
  }

  def compileClass[T](c: ClassDeclaration[T], generator: CodeGenerator): ClassHandle = {
    compileClassDeclaration(c, generator)
  }

  def loadAndSetConstants[T](handle: ClassHandle, declaration: ClassDeclaration[T]): Class[T] = {
    val clazz = handle.loadClass()
    setConstants(clazz, declaration.fields)
    clazz.asInstanceOf[Class[T]]
  }

  def compileAnonymousClass[T](c: ClassDeclaration[T], generator: CodeGenerator): Class[T] = {
    val handle = compileClassDeclaration(c, generator)
    val clazz = handle.loadAnonymousClass()
    setConstants(clazz, c.fields)
    clazz.asInstanceOf[Class[T]]
  }

  private def setConstants(clazz: Class[_], fields: collection.Seq[Field]): Unit = {
    if (fields.isEmpty) {
      return
    }
    val declaredFields = clazz.getDeclaredFields

    @nowarn("msg=return statement")
    def findField(fields: Array[java.lang.reflect.Field], name: String): java.lang.reflect.Field = {
      for (field <- fields) {
        if (field.getName == name) return field
      }
      throw new NoSuchFieldException(name)
    }

    fields.distinct.foreach {
      case StaticField(_, name, Some(value)) =>
        findField(declaredFields, name).set(null, value)
      case _ =>
    }
  }

  private def beginBlock[BlockType <: AutoCloseable, T](block: BlockType)(exhaustBlock: BlockType => T): T = {
    /*
     * In the java API we are using try-with-resources for this. This is slightly problematic since we
     * are then always calling close which potentially will hide errors thrown in code generation.
     */
    val result = exhaustBlock(block)
    block.close()
    result
  }

  private def generateConstructor(
    clazz: codegen.ClassGenerator,
    fields: collection.Seq[Field],
    params: collection.Seq[Parameter],
    initializationCode: codegen.CodeBlock => codegen.Expression,
    parent: Option[TypeReference]
  ): Unit = {
    beginBlock(clazz.generateConstructor(params.map(_.asCodeGen).toSeq: _*)) { block =>
      block.expression(invokeSuper(parent.getOrElse(OBJECT)))
      fields.distinct.foreach {
        case field @ InstanceField(typ, name) =>
          val reference = clazz.field(typ, name)
          field.initializer.map(ir => compileExpression(ir(), block)).foreach { value =>
            block.put(block.self(), reference, value)
          }
        case StaticField(typ, name, _) =>
          clazz.publicStaticField(typ, name)
      }
      initializationCode(block)
    }
  }

  private def compileExpression(ir: IntermediateRepresentation, block: codegen.CodeBlock): codegen.Expression =
    ir match {
      // Foo.method(p1, p2,...)
      case InvokeStatic(method, params) =>
        invoke(method.asReference, params.map(p => compileExpression(p, block)): _*)

      // Foo.method(p1, p2,...)
      case InvokeStaticSideEffect(method, params) =>
        val invocation = invoke(method.asReference, params.map(p => compileExpression(p, block)): _*)
        if (method.returnType.isVoid) {
          block.expression(invocation)
        } else {
          block.expression(codegen.Expression.pop(invocation))
        }
        codegen.Expression.EMPTY

      // target.method(p1,p2,...)
      case Invoke(target, method, params) =>
        invoke(compileExpression(target, block), method.asReference, params.map(p => compileExpression(p, block)): _*)

      // target.method(p1,p2,...)
      case InvokeLocal(method, params) =>
        invoke(
          block.self(),
          codegen.MethodReference.methodReference(block.owner(), method.returnType, method.name, method.params: _*),
          params.map(p => compileExpression(p, block)): _*
        )

      // target.method(p1,p2,...)
      case InvokeSideEffect(target, method, params) =>
        val invocation =
          invoke(compileExpression(target, block), method.asReference, params.map(p => compileExpression(p, block)): _*)
        if (method.returnType.isVoid) {
          block.expression(invocation)
        } else {
          block.expression(codegen.Expression.pop(invocation))
        }
        codegen.Expression.EMPTY

      // this.method(p1,p2,...)
      case InvokeLocalSideEffect(method, params) =>
        val invocation = invoke(
          block.self(),
          codegen.MethodReference.methodReference(block.owner(), method.returnType, method.name, method.params: _*),
          params.map(p => compileExpression(p, block)): _*
        )
        if (method.returnType.isVoid) {
          block.expression(invocation)
        } else {
          block.expression(codegen.Expression.pop(invocation))
        }
        codegen.Expression.EMPTY

      // loads local variable by name
      case Load(variable, _) => block.load(variable)

      // loads field
      case LoadField(f) =>
        codegen.Expression.get(block.self(), field(block.owner(), f.typ, f.name))

      // sets a field
      case SetField(f, v) =>
        block.put(block.self(), field(block.owner(), f.typ, f.name), compileExpression(v, block))
        codegen.Expression.EMPTY

      // loads a given constant
      case Constant(value) => constant(value)

      // new ArrayValue[]{p1, p2,...}
      case ArrayLiteral(typ, values) => newInitializedArray(typ, values.map(v => compileExpression(v, block)): _*)

      // array[offset] = value
      case ArraySet(array, offset, value) =>
        block.expression(codegen.Expression.arraySet(
          compileExpression(array, block),
          compileExpression(offset, block),
          compileExpression(value, block)
        ))
        codegen.Expression.EMPTY

      // array.length
      case ArrayLength(array) =>
        codegen.Expression.arrayLength(compileExpression(array, block))

      // array[offset]
      case ArrayLoad(array, offset) =>
        codegen.Expression.arrayLoad(compileExpression(array, block), compileExpression(offset, block))

      // Foo.BAR
      case GetStatic(owner, typ, name) =>
        getStatic(staticField(owner.getOrElse(block.classGenerator().handle()), typ, name))

      // condition ? onTrue : onFalse
      case Ternary(condition, onTrue, onFalse) =>
        codegen.Expression.ternary(
          compileExpression(condition, block),
          compileExpression(onTrue, block),
          compileExpression(onFalse, block)
        )

      // lhs + rhs
      case Add(lhs, rhs) =>
        codegen.Expression.add(compileExpression(lhs, block), compileExpression(rhs, block))

      // lhs - rhs
      case Subtract(lhs, rhs) =>
        codegen.Expression.subtract(compileExpression(lhs, block), compileExpression(rhs, block))

      // lhs * rhs
      case Multiply(lhs, rhs) =>
        codegen.Expression.multiply(compileExpression(lhs, block), compileExpression(rhs, block))

      // lhs < rhs
      case Lt(lhs, rhs) =>
        codegen.Expression.lt(compileExpression(lhs, block), compileExpression(rhs, block))

      // lhs <= rhs
      case Lte(lhs, rhs) =>
        codegen.Expression.lte(compileExpression(lhs, block), compileExpression(rhs, block))

      // lhs > rhs
      case Gt(lhs, rhs) =>
        codegen.Expression.gt(compileExpression(lhs, block), compileExpression(rhs, block))

      // lhs > rhs
      case Gte(lhs, rhs) =>
        codegen.Expression.gte(compileExpression(lhs, block), compileExpression(rhs, block))

      // lhs == rhs
      case Eq(lhs, rhs) =>
        codegen.Expression.equal(compileExpression(lhs, block), compileExpression(rhs, block))

      // lhs != rhs
      case NotEq(lhs, rhs) =>
        codegen.Expression.notEqual(compileExpression(lhs, block), compileExpression(rhs, block))

      // test == null
      case IsNull(test) => codegen.Expression.isNull(compileExpression(test, block))

      // run multiple ops in a block, the value of the block is the last expression
      case Block(ops) =>
        if (ops.isEmpty) codegen.Expression.EMPTY else ops.map(compileExpression(_, block)).last

      // if (test) {onTrue}
      case Condition(test, onTrue, None) =>
        beginBlock(block.ifStatement(compileExpression(test, block)))(compileExpression(onTrue, _))

      case Condition(test, onTrue, Some(onFalse)) =>
        block.ifElseStatement(
          compileExpression(test, block),
          (t: codegen.CodeBlock) => compileExpression(onTrue, t),
          (f: codegen.CodeBlock) => compileExpression(onFalse, f)
        )
        codegen.Expression.EMPTY

      // typ name;
      case DeclareLocalVariable(typ, name) =>
        block.declare(typ, name)

      // name = value;
      case AssignToLocalVariable(name, value) =>
        block.assign(block.local(name), compileExpression(value, block))
        codegen.Expression.EMPTY

      // try {ops} catch(exception name)(onError)
      case TryCatch(ops, onError, exception, name) =>
        beginBlock(block.tryCatch(
          (errorBlock: codegen.CodeBlock) => compileExpression(onError, errorBlock),
          param(exception, name)
        ))(
          compileExpression(ops, _)
        )
        codegen.Expression.EMPTY

      // throw error
      case Throw(error) =>
        block.throwException(compileExpression(error, block))
        codegen.Expression.EMPTY

      // lhs && rhs
      case BooleanAnd(Seq(lhs, rhs)) =>
        codegen.Expression.and(compileExpression(lhs, block), compileExpression(rhs, block))
      case BooleanAnd(as) =>
        codegen.Expression.ands(as.map(a => compileExpression(a, block)).toArray)

      // lhs && rhs
      case BooleanOr(Seq(lhs, rhs)) =>
        codegen.Expression.or(compileExpression(lhs, block), compileExpression(rhs, block))
      case BooleanOr(as) =>
        codegen.Expression.ors(as.map(a => compileExpression(a, block)).toArray)

      // new Foo(args[0], args[1], ...)
      case NewInstance(constructor, args) =>
        codegen.Expression.invoke(
          codegen.Expression.newInstance(constructor.owner),
          constructor.asReference,
          args.map(compileExpression(_, block)): _*
        )

      // new Foo[5]
      case NewArray(baseType, size) =>
        codegen.Expression.newArray(baseType, size)

      // new Foo[size]
      case NewArrayDynamicSize(baseType, size) =>
        codegen.Expression.newArray(baseType, compileExpression(size, block))

      case Returns(value: IntermediateRepresentation) =>
        block.returns(compileExpression(value, block))
        codegen.Expression.EMPTY

      // while(test) { body }
      case Loop(test, body, labelName) =>
        beginBlock(block.whileLoop(compileExpression(test, block), labelName))(compileExpression(body, _))

      // break label
      case Break(labelName) =>
        block.breaks(labelName)
        codegen.Expression.EMPTY

      // (to) expressions
      case Cast(to, expression) => codegen.Expression.cast(to, compileExpression(expression, block))

      //  expressions instance of t
      case InstanceOf(typ, expression) => codegen.Expression.instanceOf(typ, compileExpression(expression, block))

      case Not(test) => codegen.Expression.not(compileExpression(test, block))

      case e @ OneTime(inner) =>
        if (!e.isUsed) {
          e.use()
          compileExpression(inner, block)
        } else {
          codegen.Expression.EMPTY
        }
      case Noop =>
        codegen.Expression.EMPTY

      case Box(expression) =>
        codegen.Expression.box(compileExpression(expression, block))

      case Unbox(expression) =>
        codegen.Expression.unbox(compileExpression(expression, block))

      case Self(_) => block.self()

      case NewInstanceInnerClass(ExtendClass(className, overrides, params, methods, fields), args) =>
        val parentClass: ClassHandle = block.classGenerator().handle()
        val generator = parentClass.generator
        val classHandle = beginBlock(generator.generateClass(overrides, parentClass.packageName(), className)) {
          (clazz: codegen.ClassGenerator) =>
            beginBlock(clazz.generateConstructor(params.map(_.asCodeGen): _*)) { constructor =>
              constructor.expression(Expression.invokeSuper(overrides, params.map(p => constructor.load(p.name)): _*))
              fields.distinct.foreach {
                case field @ InstanceField(typ, name) =>
                  val reference = clazz.field(typ, name)
                  field.initializer.map(ir => compileExpression(ir(), constructor)).foreach { value =>
                    constructor.put(constructor.self(), reference, value)
                  }
                case StaticField(typ, name, _) =>
                  val field = clazz.publicStaticField(typ, name)
                  constructor.putStatic(
                    field,
                    Expression.getStatic(FieldReference.staticField(parentClass, field.`type`(), field.name()))
                  )
              }
            }
            // methods
            methods.foreach { m =>
              compileMethodDeclaration(clazz, m)
            }
            clazz.handle()
        }

        val constructor =
          if (args.isEmpty) {
            codegen.MethodReference.constructorReference(classHandle)
          } else {
            codegen.MethodReference.constructorReference(classHandle, params.map(_.typ): _*)
          }
        codegen.Expression.invoke(
          codegen.Expression.newInstance(classHandle),
          constructor,
          args.map(compileExpression(_, block)): _*
        )

      case unknownIr =>
        throw new CodeGenerationNotSupportedException(null, s"Unknown ir `$unknownIr`") {}
    }

  private def compileClassDeclaration(c: ClassDeclaration[_], generator: CodeGenerator): codegen.ClassHandle = {
    val handle = beginBlock(generator.generateClass(
      c.extendsClass.getOrElse(codegen.TypeReference.OBJECT),
      c.packageName,
      c.className,
      c.implementsInterfaces.toSeq: _*
    )) { (clazz: codegen.ClassGenerator) =>
      generateConstructor(
        clazz,
        c.fields,
        c.constructorParameters,
        block => compileExpression(c.initializationCode, block),
        c.extendsClass
      )
      c.methods.foreach { m =>
        compileMethodDeclaration(clazz, m)
      }
      clazz.handle()
    }
    handle
  }

  private def compileMethodDeclaration(clazz: codegen.ClassGenerator, m: MethodDeclaration): Unit = {

    val estimatedSize = estimateByteCodeSize(m)
    if (estimatedSize > methodLimit) {
      throw new CantCompileQueryException(
        s"Method '${m.methodName}' is too big, estimated size $estimatedSize is bigger than $methodLimit"
      )
    }

    val method = codegen.MethodDeclaration.method(
      m.returnType,
      m.methodName,
      m.parameters.map(_.asCodeGen): _*
    ).modifiers(m.modifiers)
    m.parameterizedWith.foreach {
      case (name, bound) => method.parameterizedWith(name, bound)
    }
    m.throws.foreach(method.throwsException)

    beginBlock(clazz.generate(method)) { block =>
      m.localVariables.distinct.foreach { v =>
        block.assign(v.typ, v.name, compileExpression(v.value, block))
      }
      if (m.returnType == codegen.TypeReference.VOID) {
        block.expression(compileExpression(m.body, block))
      } else {
        block.returns(compileExpression(m.body, block))
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy