private static void generateProcessColumnarMethod(
      ClassDefinition classDefinition,
      List<RowExpression> projections,
      List<MethodDefinition> projectColumnarMethods) {
    Parameter session = arg("session", ConnectorSession.class);
    Parameter page = arg("page", Page.class);
    Parameter types = arg("types", List.class);
    MethodDefinition method =
        classDefinition.declareMethod(
            a(PUBLIC), "processColumnar", type(Page.class), session, page, types);

    Scope scope = method.getScope();
    BytecodeBlock body = method.getBody();
    Variable thisVariable = method.getThis();

    Variable selectedPositions =
        scope.declareVariable(
            "selectedPositions",
            body,
            thisVariable.invoke("filterPage", int[].class, session, page));
    Variable cardinality = scope.declareVariable("cardinality", body, selectedPositions.length());

    body.comment("if no rows selected return null")
        .append(
            new IfStatement()
                .condition(equal(cardinality, constantInt(0)))
                .ifTrue(constantNull(Page.class).ret()));

    if (projections.isEmpty()) {
      // if no projections, return new page with selected rows
      body.append(newInstance(Page.class, cardinality, newArray(type(Block[].class), 0)).ret());
      return;
    }

    Variable pageBuilder =
        scope.declareVariable(
            "pageBuilder", body, newInstance(PageBuilder.class, cardinality, types));
    Variable outputBlocks =
        scope.declareVariable(
            "outputBlocks", body, newArray(type(Block[].class), projections.size()));

    for (int projectionIndex = 0; projectionIndex < projections.size(); projectionIndex++) {
      List<BytecodeExpression> params =
          ImmutableList.<BytecodeExpression>builder()
              .add(session)
              .add(page)
              .add(selectedPositions)
              .add(pageBuilder)
              .add(constantInt(projectionIndex))
              .build();
      body.append(
          outputBlocks.setElement(
              projectionIndex,
              thisVariable.invoke(projectColumnarMethods.get(projectionIndex), params)));
    }

    // create new page from outputBlocks
    body.append(newInstance(Page.class, cardinality, outputBlocks).ret());
  }
  private static void generateGetNonLazyPageMethod(
      ClassDefinition classDefinition, RowExpression filter, List<RowExpression> projections) {
    Parameter page = arg("page", Page.class);
    MethodDefinition method =
        classDefinition.declareMethod(a(PRIVATE), "getNonLazyPage", type(Page.class), page);

    Scope scope = method.getScope();
    BytecodeBlock body = method.getBody();

    List<Integer> allInputChannels =
        getInputChannels(concat(projections, ImmutableList.of(filter)));
    if (allInputChannels.isEmpty()) {
      body.append(page.ret());
      return;
    }

    Variable index = scope.declareVariable(int.class, "index");
    Variable channelCount =
        scope.declareVariable("channelCount", body, page.invoke("getChannelCount", int.class));
    Variable blocks =
        scope.declareVariable("blocks", body, newArray(type(Block[].class), channelCount));
    Variable inputBlock = scope.declareVariable(Block.class, "inputBlock");

    Variable positionCount =
        scope.declareVariable("positionCount", body, page.invoke("getPositionCount", int.class));
    Variable createNewPage = scope.declareVariable("createNewPage", body, constantFalse());

    ForLoop forLoop =
        new ForLoop()
            .initialize(index.set(constantInt(0)))
            .condition(lessThan(index, channelCount))
            .update(index.increment())
            .body(
                new BytecodeBlock()
                    .append(inputBlock.set(page.invoke("getBlock", Block.class, index)))
                    .append(
                        new IfStatement()
                            .condition(inputBlock.instanceOf(LazyBlock.class))
                            .ifTrue(
                                new BytecodeBlock()
                                    .append(
                                        blocks.setElement(
                                            index,
                                            inputBlock
                                                .cast(LazyBlock.class)
                                                .invoke("getBlock", Block.class)))
                                    .append(createNewPage.set(constantTrue())))
                            .ifFalse(blocks.setElement(index, inputBlock))));

    body.append(forLoop);
    body.append(
        new IfStatement()
            .condition(createNewPage)
            .ifTrue(page.set(newInstance(Page.class, positionCount, blocks))));

    body.append(page.ret());
  }
  private static void generateFilterPageMethod(
      ClassDefinition classDefinition, RowExpression filter) {
    Parameter session = arg("session", ConnectorSession.class);
    Parameter page = arg("page", Page.class);

    MethodDefinition method =
        classDefinition.declareMethod(a(PUBLIC), "filterPage", type(int[].class), session, page);
    method.comment("Filter: %s rows in the page", filter.toString());

    Scope scope = method.getScope();
    Variable thisVariable = method.getThis();
    BytecodeBlock body = method.getBody();

    Variable positionCount =
        scope.declareVariable("positionCount", body, page.invoke("getPositionCount", int.class));
    Variable selectedPositions =
        scope.declareVariable(
            "selectedPositions", body, newArray(type(int[].class), positionCount));

    List<Integer> filterChannels = getInputChannels(filter);

    // extract block variables
    ImmutableList.Builder<Variable> blockVariablesBuilder = ImmutableList.<Variable>builder();
    for (int channel : filterChannels) {
      Variable blockVariable =
          scope.declareVariable(
              "block_" + channel, body, page.invoke("getBlock", Block.class, constantInt(channel)));
      blockVariablesBuilder.add(blockVariable);
    }
    List<Variable> blockVariables = blockVariablesBuilder.build();

    Variable selectedCount = scope.declareVariable("selectedCount", body, constantInt(0));
    Variable position = scope.declareVariable(int.class, "position");

    IfStatement ifStatement = new IfStatement();
    ifStatement
        .condition(invokeFilter(thisVariable, session, blockVariables, position))
        .ifTrue()
        .append(selectedPositions.setElement(selectedCount, position))
        .append(selectedCount.increment());

    body.append(
        new ForLoop()
            .initialize(position.set(constantInt(0)))
            .condition(lessThan(position, positionCount))
            .update(position.increment())
            .body(ifStatement));

    body.append(
        invokeStatic(Arrays.class, "copyOf", int[].class, selectedPositions, selectedCount).ret());
  }
  private MethodDefinition generateProjectMethod(
      ClassDefinition classDefinition,
      CallSiteBinder callSiteBinder,
      CachedInstanceBinder cachedInstanceBinder,
      String methodName,
      RowExpression projection) {
    Parameter session = arg("session", ConnectorSession.class);
    List<Parameter> inputs = toBlockParameters(getInputChannels(projection));
    Parameter position = arg("position", int.class);
    Parameter output = arg("output", BlockBuilder.class);
    MethodDefinition method =
        classDefinition.declareMethod(
            a(PUBLIC),
            methodName,
            type(void.class),
            ImmutableList.<Parameter>builder()
                .add(session)
                .addAll(inputs)
                .add(position)
                .add(output)
                .build());

    method.comment("Projection: %s", projection.toString());

    Scope scope = method.getScope();
    BytecodeBlock body = method.getBody();

    Variable wasNullVariable = scope.declareVariable("wasNull", body, constantFalse());
    BytecodeExpressionVisitor visitor =
        new BytecodeExpressionVisitor(
            callSiteBinder,
            cachedInstanceBinder,
            fieldReferenceCompiler(callSiteBinder, position, wasNullVariable),
            metadata.getFunctionRegistry());

    body.getVariable(output)
        .comment("evaluate projection: " + projection.toString())
        .append(projection.accept(visitor, scope))
        .append(generateWrite(callSiteBinder, scope, wasNullVariable, projection.getType()))
        .ret();
    return method;
  }
  private void generateFilterMethod(
      ClassDefinition classDefinition,
      CallSiteBinder callSiteBinder,
      CachedInstanceBinder cachedInstanceBinder,
      RowExpression filter) {
    Parameter session = arg("session", ConnectorSession.class);
    Parameter position = arg("position", int.class);

    List<Parameter> blocks = toBlockParameters(getInputChannels(filter));
    MethodDefinition method =
        classDefinition.declareMethod(
            a(PUBLIC),
            "filter",
            type(boolean.class),
            ImmutableList.<Parameter>builder().add(session).addAll(blocks).add(position).build());

    method.comment("Filter: %s", filter.toString());
    BytecodeBlock body = method.getBody();

    Scope scope = method.getScope();
    Variable wasNullVariable = scope.declareVariable("wasNull", body, constantFalse());

    BytecodeExpressionVisitor visitor =
        new BytecodeExpressionVisitor(
            callSiteBinder,
            cachedInstanceBinder,
            fieldReferenceCompiler(callSiteBinder, position, wasNullVariable),
            metadata.getFunctionRegistry());
    BytecodeNode visitorBody = filter.accept(visitor, scope);

    Variable result = scope.declareVariable(boolean.class, "result");
    body.append(visitorBody)
        .putVariable(result)
        .append(
            new IfStatement()
                .condition(wasNullVariable)
                .ifTrue(constantFalse().ret())
                .ifFalse(result.ret()));
  }
  private static void generateConstructor(
      ClassDefinition classDefinition,
      CachedInstanceBinder cachedInstanceBinder,
      int projectionCount) {
    MethodDefinition constructorDefinition = classDefinition.declareConstructor(a(PUBLIC));
    FieldDefinition inputDictionaries =
        classDefinition.declareField(a(PRIVATE, FINAL), "inputDictionaries", Block[].class);
    FieldDefinition outputDictionaries =
        classDefinition.declareField(a(PRIVATE, FINAL), "outputDictionaries", Block[].class);

    BytecodeBlock body = constructorDefinition.getBody();
    Variable thisVariable = constructorDefinition.getThis();

    body.comment("super();").append(thisVariable).invokeConstructor(Object.class);

    body.append(
        thisVariable.setField(inputDictionaries, newArray(type(Block[].class), projectionCount)));
    body.append(
        thisVariable.setField(outputDictionaries, newArray(type(Block[].class), projectionCount)));
    cachedInstanceBinder.generateInitializations(thisVariable, body);
    body.ret();
  }
  private static MethodDefinition generateProjectDictionaryMethod(
      ClassDefinition classDefinition,
      String methodName,
      RowExpression projection,
      MethodDefinition project,
      MethodDefinition projectColumnar) {
    Parameter session = arg("session", ConnectorSession.class);
    Parameter page = arg("page", Page.class);
    Parameter selectedPositions = arg("selectedPositions", int[].class);
    Parameter pageBuilder = arg("pageBuilder", PageBuilder.class);
    Parameter projectionIndex = arg("projectionIndex", int.class);

    List<Parameter> params =
        ImmutableList.<Parameter>builder()
            .add(session)
            .add(page)
            .add(selectedPositions)
            .add(pageBuilder)
            .add(projectionIndex)
            .build();

    MethodDefinition method =
        classDefinition.declareMethod(a(PRIVATE), methodName, type(Block.class), params);
    BytecodeBlock body = method.getBody();
    Scope scope = method.getScope();
    Variable thisVariable = method.getThis();

    List<Integer> inputChannels = getInputChannels(projection);

    if (inputChannels.size() != 1) {
      body.append(thisVariable.invoke(projectColumnar, params).ret());
      return method;
    }

    Variable inputBlock =
        scope.declareVariable(
            "inputBlock",
            body,
            page.invoke(
                "getBlock", Block.class, constantInt(Iterables.getOnlyElement(inputChannels))));
    IfStatement ifStatement =
        new IfStatement()
            .condition(inputBlock.instanceOf(DictionaryBlock.class))
            .ifFalse(thisVariable.invoke(projectColumnar, params).ret());
    body.append(ifStatement);

    Variable blockBuilder =
        scope.declareVariable(
            "blockBuilder",
            body,
            pageBuilder.invoke("getBlockBuilder", BlockBuilder.class, projectionIndex));
    Variable cardinality = scope.declareVariable("cardinality", body, selectedPositions.length());

    Variable dictionary = scope.declareVariable(Block.class, "dictionary");
    Variable ids = scope.declareVariable(Slice.class, "ids");
    Variable dictionaryCount = scope.declareVariable(int.class, "dictionaryCount");

    Variable outputDictionary = scope.declareVariable(Block.class, "outputDictionary");
    Variable outputIds = scope.declareVariable(int[].class, "outputIds");

    BytecodeExpression inputDictionaries =
        thisVariable.getField("inputDictionaries", Block[].class);
    BytecodeExpression outputDictionaries =
        thisVariable.getField("outputDictionaries", Block[].class);

    Variable position = scope.declareVariable("position", body, constantInt(0));

    body.comment("Extract dictionary and ids")
        .append(
            dictionary.set(
                inputBlock.cast(DictionaryBlock.class).invoke("getDictionary", Block.class)))
        .append(ids.set(inputBlock.cast(DictionaryBlock.class).invoke("getIds", Slice.class)))
        .append(dictionaryCount.set(dictionary.invoke("getPositionCount", int.class)));

    BytecodeBlock projectDictionary =
        new BytecodeBlock()
            .comment("Project dictionary")
            .append(
                new ForLoop()
                    .initialize(position.set(constantInt(0)))
                    .condition(lessThan(position, dictionaryCount))
                    .update(position.increment())
                    .body(
                        invokeProject(
                            thisVariable,
                            session,
                            ImmutableList.of(dictionary),
                            position,
                            pageBuilder,
                            projectionIndex,
                            project)))
            .append(outputDictionary.set(blockBuilder.invoke("build", Block.class)))
            .append(inputDictionaries.setElement(projectionIndex, dictionary))
            .append(outputDictionaries.setElement(projectionIndex, outputDictionary));

    body.comment("Use processed dictionary, if available, else project it")
        .append(
            new IfStatement()
                .condition(equal(inputDictionaries.getElement(projectionIndex), dictionary))
                .ifTrue(outputDictionary.set(outputDictionaries.getElement(projectionIndex)))
                .ifFalse(projectDictionary));

    body.comment("Filter ids")
        .append(outputIds.set(newArray(type(int[].class), cardinality)))
        .append(
            new ForLoop()
                .initialize(position.set(constantInt(0)))
                .condition(lessThan(position, cardinality))
                .update(position.increment())
                .body(
                    outputIds.setElement(
                        position,
                        ids.invoke(
                            "getInt",
                            int.class,
                            multiply(
                                selectedPositions.getElement(position),
                                constantInt(SIZE_OF_INT))))));

    body.append(
        newInstance(
                DictionaryBlock.class,
                cardinality,
                outputDictionary,
                invokeStatic(Slices.class, "wrappedIntArray", Slice.class, outputIds))
            .cast(Block.class)
            .ret());
    return method;
  }
  private static MethodDefinition generateProjectColumnarMethod(
      ClassDefinition classDefinition,
      CallSiteBinder callSiteBinder,
      String methodName,
      RowExpression projection,
      MethodDefinition projectionMethod) {
    Parameter session = arg("session", ConnectorSession.class);
    Parameter page = arg("page", Page.class);
    Parameter selectedPositions = arg("selectedPositions", int[].class);
    Parameter pageBuilder = arg("pageBuilder", PageBuilder.class);
    Parameter projectionIndex = arg("projectionIndex", int.class);

    List<Parameter> params =
        ImmutableList.<Parameter>builder()
            .add(session)
            .add(page)
            .add(selectedPositions)
            .add(pageBuilder)
            .add(projectionIndex)
            .build();
    MethodDefinition method =
        classDefinition.declareMethod(a(PRIVATE), methodName, type(Block.class), params);

    BytecodeBlock body = method.getBody();
    Scope scope = method.getScope();
    Variable thisVariable = method.getThis();

    ImmutableList.Builder<Variable> builder = ImmutableList.<Variable>builder();
    for (int channel : getInputChannels(projection)) {
      Variable blockVariable =
          scope.declareVariable(
              "block_" + channel, body, page.invoke("getBlock", Block.class, constantInt(channel)));
      builder.add(blockVariable);
    }
    List<Variable> inputs = builder.build();

    Variable positionCount =
        scope.declareVariable("positionCount", body, page.invoke("getPositionCount", int.class));
    Variable position = scope.declareVariable("position", body, constantInt(0));
    Variable cardinality = scope.declareVariable("cardinality", body, selectedPositions.length());
    Variable outputBlock = scope.declareVariable(Block.class, "outputBlock");
    Variable blockBuilder =
        scope.declareVariable(
            "blockBuilder",
            body,
            pageBuilder.invoke("getBlockBuilder", BlockBuilder.class, projectionIndex));
    Variable type =
        scope.declareVariable(
            "type", body, pageBuilder.invoke("getType", Type.class, projectionIndex));

    BytecodeBlock projectBlock =
        new BytecodeBlock()
            .append(
                new ForLoop()
                    .initialize(position.set(constantInt(0)))
                    .condition(lessThan(position, cardinality))
                    .update(position.increment())
                    .body(
                        invokeProject(
                            thisVariable,
                            session,
                            inputs,
                            selectedPositions.getElement(position),
                            pageBuilder,
                            projectionIndex,
                            projectionMethod)))
            .append(outputBlock.set(blockBuilder.invoke("build", Block.class)));

    if (isIdentityExpression(projection)) {
      // if nothing is filtered out, copy the entire block, else project it
      body.append(
          new IfStatement()
              .condition(equal(cardinality, positionCount))
              .ifTrue(outputBlock.set(inputs.get(0)))
              .ifFalse(projectBlock));
    } else if (isConstantExpression(projection)) {
      // if projection is a constant, create RLE block of constant expression with cardinality
      // positions
      ConstantExpression constantExpression = (ConstantExpression) projection;
      verify(getInputChannels(projection).isEmpty());
      BytecodeExpression value =
          loadConstant(callSiteBinder, constantExpression.getValue(), Object.class);
      body.append(
          outputBlock.set(
              invokeStatic(
                  RunLengthEncodedBlock.class, "create", Block.class, type, value, cardinality)));
    } else {
      body.append(projectBlock);
    }
    body.append(outputBlock.ret());
    return method;
  }
  private static void generateProcessMethod(
      ClassDefinition classDefinition,
      RowExpression filter,
      List<RowExpression> projections,
      List<MethodDefinition> projectionMethods) {
    Parameter session = arg("session", ConnectorSession.class);
    Parameter page = arg("page", Page.class);
    Parameter start = arg("start", int.class);
    Parameter end = arg("end", int.class);
    Parameter pageBuilder = arg("pageBuilder", PageBuilder.class);
    MethodDefinition method =
        classDefinition.declareMethod(
            a(PUBLIC), "process", type(int.class), session, page, start, end, pageBuilder);

    Scope scope = method.getScope();
    BytecodeBlock body = method.getBody();
    Variable thisVariable = method.getThis();

    // extract blocks
    List<Integer> allInputChannels =
        getInputChannels(concat(projections, ImmutableList.of(filter)));
    ImmutableMap.Builder<Integer, Variable> builder = ImmutableMap.builder();
    for (int channel : allInputChannels) {
      Variable blockVariable =
          scope.declareVariable(
              "block_" + channel, body, page.invoke("getBlock", Block.class, constantInt(channel)));
      builder.put(channel, blockVariable);
    }
    Map<Integer, Variable> channelBlocks = builder.build();
    Map<RowExpression, List<Variable>> expressionInputBlocks =
        getExpressionInputBlocks(projections, filter, channelBlocks);

    // projection body
    Variable position = scope.declareVariable(int.class, "position");

    BytecodeBlock project =
        new BytecodeBlock().append(pageBuilder.invoke("declarePosition", void.class));

    for (int projectionIndex = 0; projectionIndex < projections.size(); projectionIndex++) {
      RowExpression projection = projections.get(projectionIndex);
      project.append(
          invokeProject(
              thisVariable,
              session,
              expressionInputBlocks.get(projection),
              position,
              pageBuilder,
              constantInt(projectionIndex),
              projectionMethods.get(projectionIndex)));
    }
    LabelNode done = new LabelNode("done");

    // for loop loop body
    ForLoop loop =
        new ForLoop()
            .initialize(position.set(start))
            .condition(lessThan(position, end))
            .update(position.set(add(position, constantInt(1))))
            .body(
                new BytecodeBlock()
                    .append(
                        new IfStatement()
                            .condition(pageBuilder.invoke("isFull", boolean.class))
                            .ifTrue(jump(done)))
                    .append(
                        new IfStatement()
                            .condition(
                                invokeFilter(
                                    thisVariable,
                                    session,
                                    expressionInputBlocks.get(filter),
                                    position))
                            .ifTrue(project)));

    body.append(loop).visitLabel(done).append(position.ret());
  }