blob: b193e732746ae5c7c8b244c58a597182a09f640d [file] [log] [blame]
// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.dex;
import com.android.tools.r8.ByteBufferProvider;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.ObjectToOffsetMapping;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.utils.EncodedValueUtils;
import com.android.tools.r8.utils.LebUtils;
import com.google.common.annotations.VisibleForTesting;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
/**
* Provides an abstraction around a {@link ByteBuffer} with write operations for
* additional DEX specific formats, like Leb128.
*/
public class DexOutputBuffer {
private static final int DEFAULT_BUFFER_SIZE = 256 * 1024;
private final ByteBufferProvider byteBufferProvider;
private CompatByteBuffer byteBuffer;
@VisibleForTesting
DexOutputBuffer() {
this(new ByteBufferProvider() {});
}
public DexOutputBuffer(ByteBufferProvider byteBufferProvider) {
this.byteBufferProvider = byteBufferProvider;
byteBuffer = allocateByteBuffer(DEFAULT_BUFFER_SIZE);
}
private void ensureSpaceFor(int bytes) {
if (byteBuffer.remaining() < bytes) {
int newSize = byteBuffer.capacity() + Math.max(byteBuffer.capacity(), bytes * 2);
CompatByteBuffer newBuffer = allocateByteBuffer(newSize);
System.arraycopy(byteBuffer.array(), 0, newBuffer.array(), 0, position());
newBuffer.position(byteBuffer.position());
freeByteBuffer(byteBuffer);
byteBuffer = newBuffer;
}
}
private CompatByteBuffer allocateByteBuffer(int size) {
CompatByteBuffer buffer = new CompatByteBuffer(byteBufferProvider.acquireByteBuffer(size));
if (!buffer.hasArray()) {
throw new CompilationError(
"Provided byte-buffer is required to have an array backing, but does not.");
}
if (buffer.capacity() < size) {
throw new CompilationError(
"Insufficient capacity of provided byte-buffer."
+ " Requested capacity "
+ size
+ ", actual capacity: "
+ buffer.capacity());
}
if (buffer.position() != 0) {
throw new CompilationError(
"Provided byte-buffer is required to start at position zero, but starts at "
+ buffer.position()
+ ".");
}
buffer.order(ByteOrder.LITTLE_ENDIAN);
return buffer;
}
private void freeByteBuffer(CompatByteBuffer buffer) {
assert buffer != null;
byteBufferProvider.releaseByteBuffer(buffer.asByteBuffer());
}
public void putUleb128(int value) {
LebUtils.putUleb128(this, value);
}
public void putSleb128(int value) {
LebUtils.putSleb128(this, value);
}
public int putSignedEncodedValue(long value, int expectedSize) {
return EncodedValueUtils.putSigned(this, value, expectedSize);
}
public int putUnsignedEncodedValue(long value, int expectedSize) {
return EncodedValueUtils.putUnsigned(this, value, expectedSize);
}
public void putInstructions(
DexCode code,
ProgramMethod context,
ObjectToOffsetMapping mapping,
CodeToKeep desugaredLibraryCodeToKeep) {
int size = 0;
Instruction[] instructions = code.instructions;
for (Instruction instruction : instructions) {
size += instruction.getSize();
}
ensureSpaceFor(size * Short.BYTES);
assert byteBuffer.position() % 2 == 0;
ShortBuffer shortBuffer = byteBuffer.asShortBuffer();
for (int i = 0; i < instructions.length; i++) {
Instruction insn = instructions[i];
DexMethod method = insn.getMethod();
DexField field = insn.getField();
if (field != null) {
assert method == null;
desugaredLibraryCodeToKeep.recordField(field);
} else if (method != null) {
desugaredLibraryCodeToKeep.recordMethod(method);
} else if (insn.isConstClass()) {
desugaredLibraryCodeToKeep.recordClass(insn.asConstClass().getType());
} else if (insn.isInstanceOf()) {
desugaredLibraryCodeToKeep.recordClass(insn.asInstanceOf().getType());
} else if (insn.isCheckCast()) {
desugaredLibraryCodeToKeep.recordClass(insn.asCheckCast().getType());
}
insn.write(
shortBuffer, context, mapping.getGraphLens(), mapping, mapping.getLensCodeRewriter());
}
byteBuffer.position(byteBuffer.position() + shortBuffer.position() * Short.BYTES);
}
public void putByte(byte aByte) {
ensureSpaceFor(Byte.BYTES);
byteBuffer.put(aByte);
}
public void putBytes(byte[] bytes) {
ensureSpaceFor(bytes.length);
byteBuffer.put(bytes);
}
public void putShort(short aShort) {
ensureSpaceFor(Short.BYTES);
byteBuffer.putShort(aShort);
}
public void putInt(int anInteger) {
ensureSpaceFor(Integer.BYTES);
byteBuffer.putInt(anInteger);
}
/**
* Moves the position in the bytebuffer forward until it is aligned.
*
* @param bytes alignment requirement in bytes
* @return the new position after alignment
*/
public int align(int bytes) {
assert bytes > 0;
int mask = bytes - 1;
int newPosition = (position() + mask) & ~mask;
ensureSpaceFor(newPosition - position());
byteBuffer.position(newPosition);
return newPosition;
}
public int position() {
return byteBuffer.position();
}
public void forward(int bytes) {
ensureSpaceFor(bytes);
byteBuffer.position(byteBuffer.position() + bytes);
}
public void rewind(int bytes) {
forward(-bytes);
}
public void moveTo(int position) {
ensureSpaceFor(position - byteBuffer.position());
byteBuffer.position(position);
}
public boolean isAligned(int bytes) {
return position() % bytes == 0;
}
public byte[] asArray() {
return byteBuffer.array();
}
public CompatByteBuffer stealByteBuffer() {
CompatByteBuffer buffer = byteBuffer;
byteBuffer = null;
return buffer;
}
}