-
Notifications
You must be signed in to change notification settings - Fork 0
Cache dex syntethic context in dex builder #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,6 +33,7 @@ | |
import com.android.tools.r8.references.ClassReference; | ||
import com.android.tools.r8.utils.StringDiagnostic; | ||
import com.google.auto.value.AutoValue; | ||
import com.google.common.annotations.VisibleForTesting; | ||
import com.google.common.cache.Cache; | ||
import com.google.common.cache.CacheBuilder; | ||
import com.google.common.cache.Weigher; | ||
|
@@ -73,7 +74,7 @@ public class CompatDexBuilder { | |
|
||
private static final long ONE_MEG = 1024 * 1024; | ||
|
||
private static class ContextConsumer implements SyntheticInfoConsumer { | ||
public static class ContextConsumer implements SyntheticInfoConsumer { | ||
|
||
// After compilation this will be non-null iff the compiled class is a D8 synthesized class. | ||
ClassReference sythesizedPrimaryClass = null; | ||
|
@@ -113,7 +114,7 @@ public void finished() { | |
|
||
private static class DexConsumer implements DexIndexedConsumer { | ||
|
||
final ContextConsumer contextConsumer = new ContextConsumer(); | ||
ContextConsumer contextConsumer = new ContextConsumer(); | ||
byte[] bytes; | ||
|
||
@Override | ||
|
@@ -131,6 +132,10 @@ void setBytes(byte[] byteCode) { | |
this.bytes = byteCode; | ||
} | ||
|
||
void setContextConsumer(ContextConsumer contextConsumer) { | ||
this.contextConsumer = contextConsumer; | ||
} | ||
|
||
ContextConsumer getContextConsumer() { | ||
return contextConsumer; | ||
} | ||
|
@@ -148,17 +153,17 @@ public static void main(String[] args) | |
PrintStream realStdErr = System.err; | ||
|
||
// Set up dexer cache | ||
Cache<DexingKeyR8, byte[]> dexCache = | ||
Cache<DexingKeyR8, DexingEntryR8> dexCache = | ||
CacheBuilder.newBuilder() | ||
// Use at most 200 MB for cache and leave at least 25 MB of heap space alone. For | ||
// reference: | ||
// .class & class.dex files are around 1-5 KB, so this fits ~30K-35K class-dex pairs. | ||
.maximumWeight(min(Runtime.getRuntime().maxMemory() - 25 * ONE_MEG, 200 * ONE_MEG)) | ||
Comment on lines
160
to
161
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The cache weight calculation includes return key.classfileContent().length + value.dexContent().length; |
||
.weigher( | ||
new Weigher<DexingKeyR8, byte[]>() { | ||
new Weigher<DexingKeyR8, DexingEntryR8>() { | ||
@Override | ||
public int weigh(DexingKeyR8 key, byte[] value) { | ||
return key.classfileContent().length + value.length; | ||
public int weigh(DexingKeyR8 key, DexingEntryR8 value) { | ||
return key.classfileContent().length + value.dexContent().length; | ||
} | ||
}) | ||
.build(); | ||
|
@@ -186,8 +191,9 @@ public int weigh(DexingKeyR8 key, byte[] value) { | |
} | ||
} | ||
|
||
private int processRequest( | ||
@Nullable Cache<DexingKeyR8, byte[]> dexCache, | ||
@VisibleForTesting | ||
int processRequest( | ||
@Nullable Cache<DexingKeyR8, DexingEntryR8> dexCache, | ||
DiagnosticsHandler diagnosticsHandler, | ||
List<String> args, | ||
PrintWriter pw) { | ||
|
@@ -205,7 +211,7 @@ private int processRequest( | |
|
||
@SuppressWarnings("JdkObsolete") | ||
private void dexEntries( | ||
@Nullable Cache<DexingKeyR8, byte[]> dexCache, | ||
@Nullable Cache<DexingKeyR8, DexingEntryR8> dexCache, | ||
List<String> args, | ||
DiagnosticsHandler dexDiagnosticsHandler) | ||
throws IOException, InterruptedException, ExecutionException, OptionsParsingException { | ||
|
@@ -332,7 +338,7 @@ private void dexEntries( | |
} | ||
|
||
private DexConsumer dexEntry( | ||
@Nullable Cache<DexingKeyR8, byte[]> dexCache, | ||
@Nullable Cache<DexingKeyR8, DexingEntryR8> dexCache, | ||
ZipFile zipFile, | ||
ZipEntry classEntry, | ||
CompilationMode mode, | ||
|
@@ -349,18 +355,20 @@ private DexConsumer dexEntry( | |
.setMinApiLevel(minSdkVersion) | ||
.setDisableDesugaring(true) | ||
.setIntermediate(true); | ||
byte[] cachedDexBytes = null; | ||
|
||
DexingEntryR8 cachedDexEntry = null; | ||
byte[] classFileBytes = null; | ||
try (InputStream stream = zipFile.getInputStream(classEntry)) { | ||
classFileBytes = ByteStreams.toByteArray(stream); | ||
if (dexCache != null) { | ||
// If the cache exists, check for cache validity. | ||
cachedDexBytes = | ||
cachedDexEntry = | ||
dexCache.getIfPresent(DexingKeyR8.create(mode, minSdkVersion, classFileBytes)); | ||
} | ||
if (cachedDexBytes != null) { | ||
if (cachedDexEntry != null) { | ||
// Cache hit: quit early and return the data | ||
consumer.setBytes(cachedDexBytes); | ||
consumer.setBytes(cachedDexEntry.dexContent()); | ||
consumer.setContextConsumer(cachedDexEntry.contextConsumer()); | ||
return consumer; | ||
Comment on lines
+359
to
372
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Shared On a cache hit the same consumer.setContextConsumer(cachedDexEntry.contextConsumer()); Even though we currently never mutate it after caching, future refactors (or a D8 upgrade) could call - consumer.setContextConsumer(cachedDexEntry.contextConsumer());
+ // copy-by-value to guarantee immutability
+ ContextConsumer ctxCopy = new ContextConsumer();
+ ctxCopy.sythesizedPrimaryClass =
+ cachedDexEntry.contextConsumer().sythesizedPrimaryClass;
+ ctxCopy.contextOfSynthesizedClass =
+ cachedDexEntry.contextConsumer().contextOfSynthesizedClass;
+ consumer.setContextConsumer(ctxCopy); (or cache the |
||
} | ||
builder.addClassProgramData( | ||
|
@@ -371,7 +379,9 @@ private DexConsumer dexEntry( | |
D8.run(builder.build(), executor); | ||
// After dexing finishes, store the dexed output into the cache. | ||
if (dexCache != null) { | ||
dexCache.put(DexingKeyR8.create(mode, minSdkVersion, classFileBytes), consumer.getBytes()); | ||
dexCache.put( | ||
DexingKeyR8.create(mode, minSdkVersion, classFileBytes), | ||
DexingEntryR8.create(consumer.getBytes(), consumer.getContextConsumer())); | ||
} | ||
return consumer; | ||
} | ||
|
@@ -396,6 +406,25 @@ public static DexingKeyR8 create( | |
public abstract byte[] classfileContent(); | ||
} | ||
|
||
/** | ||
* Represents a cache entry in the dex cache. | ||
*/ | ||
@AutoValue | ||
public abstract static class DexingEntryR8 { | ||
public static DexingEntryR8 create( | ||
byte[] dexContent, ContextConsumer contextConsumer) { | ||
return new AutoValue_CompatDexBuilder_DexingEntryR8( | ||
dexContent, contextConsumer); | ||
} | ||
|
||
@SuppressWarnings("mutable") | ||
public abstract byte[] dexContent(); | ||
|
||
@SuppressWarnings("mutable") | ||
public abstract ContextConsumer contextConsumer(); | ||
} | ||
|
||
|
||
/** | ||
* Custom implementation of DiagnosticsHandler that writes the info/warning diagnostics messages | ||
* to original System#err stream instead of the WorkerResponse output. This keeps the Bazel | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Return value of
processRequest
is ignoredprocessRequest
returns a non-zero exit code on failure.Asserting the result will surface unexpected errors during the test run and prevent false positives.
Repeat for the second invocation.