Skip to content

Commit 89e16f4

Browse files
xuhdevmeta-codesync[bot]
authored andcommitted
Fix incomplete rewriting in lambda dedup
Summary: The dedup rewrite step only redirected a subset of instructions: `sget-object` on `INSTANCE` fields for singletons, and `new-instance` + `invoke-direct <init>` for non-singletons. Other type and method references (`invoke-virtual`, `check-cast`, etc.) on non-canonical lambda types were left unrewritten. This generalizes the rewrite loop to redirect all type operands (`has_type()`) and method ref operands (`has_method()`) that reference non-canonical lambda types, for both singleton and non-singleton lambdas. Reviewed By: thezhangwei Differential Revision: D93205174 fbshipit-source-id: 3e6f46cc0b94cd6948c766ed0873ea947a7479a1
1 parent 6a9fc80 commit 89e16f4

File tree

3 files changed

+445
-15
lines changed

3 files changed

+445
-15
lines changed

opt/kotlin-lambda/KotlinLambdaDeduplicationPass.cpp

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ DexClass* find_canonical_class(
135135
struct SingletonDeduplicationResult {
136136
// Map from original INSTANCE field to the canonical's renamed field.
137137
std::unordered_map<DexFieldRef*, DexFieldRef*> field_redirect_map;
138+
// Map from non-canonical lambda type to the canonical's type.
139+
// Used to redirect type references (invoke-virtual, check-cast, etc.).
140+
std::unordered_map<const DexType*, DexType*> type_redirect_map;
138141
size_t lambdas_deduplicated = 0;
139142
size_t duplicate_group_count = 0;
140143
};
@@ -186,6 +189,7 @@ SingletonDeduplicationResult process_singleton_duplicates(
186189
DexField* instance_field = get_singleton_field(lambda_cls, instance_name);
187190
always_assert(instance_field != nullptr);
188191
result.field_redirect_map[instance_field] = canonical_instance;
192+
result.type_redirect_map[lambda_cls->get_type()] = canonical->get_type();
189193
result.lambdas_deduplicated++;
190194
}
191195

@@ -334,18 +338,30 @@ void KotlinLambdaDeduplicationPass::run_pass(DexStoresVector& stores,
334338
m_min_duplicate_group_size);
335339

336340
// Step 4: Process duplicate groups.
337-
const auto singleton_result = process_singleton_duplicates(
341+
auto singleton_result = process_singleton_duplicates(
338342
singleton_tracker, class_to_dex_idx, m_min_duplicate_group_size);
339-
const auto non_singleton_result = process_non_singleton_duplicates(
343+
auto non_singleton_result = process_non_singleton_duplicates(
340344
non_singleton_tracker, class_to_dex_idx, m_min_duplicate_group_size);
341345

342346
// Step 5: Rewrite all usages.
347+
// - For both singleton and non-singleton lambdas: redirect all type
348+
// references (new-instance, check-cast, etc.) and method references
349+
// (invoke-virtual, invoke-interface, etc.) on non-canonical lambda types
350+
// to the canonical. This is necessary because IntraDexClassMergingPass
351+
// runs after this pass and may merge the canonical and non-canonical
352+
// lambdas into different shape types. Any unrewritten reference to a
353+
// non-canonical type would then point to the wrong shape.
343354
// - For singleton lambdas: redirect sget on INSTANCE fields.
344-
// - For non-singleton lambdas: redirect new-instance and invoke-direct
345-
// <init>.
355+
// - For non-singleton lambdas: additionally redirect invoke-direct <init>.
346356
// We count singleton and non-singleton rewrites separately. For non-singleton
347357
// lambdas, each usage consists of new-instance + invoke-direct, so we only
348358
// count new-instance rewrites to get the usage count.
359+
360+
// Build a combined type redirect map covering both singleton and
361+
// non-singleton non-canonical lambda types.
362+
auto type_redirect_map = std::move(singleton_result.type_redirect_map);
363+
type_redirect_map.merge(non_singleton_result.type_redirect_map);
364+
349365
struct RewriteCounts {
350366
size_t singleton = 0;
351367
size_t non_singleton = 0;
@@ -358,8 +374,7 @@ void KotlinLambdaDeduplicationPass::run_pass(DexStoresVector& stores,
358374
const auto rewrite_counts = walk::parallel::methods<RewriteCounts>(
359375
scope,
360376
[&field_redirect_map = std::as_const(singleton_result.field_redirect_map),
361-
&type_redirect_map =
362-
std::as_const(non_singleton_result.type_redirect_map),
377+
&type_redirect_map = std::as_const(type_redirect_map),
363378
&ctor_redirect_map = std::as_const(
364379
non_singleton_result.ctor_redirect_map)](DexMethod* meth) {
365380
RewriteCounts counts;
@@ -368,6 +383,14 @@ void KotlinLambdaDeduplicationPass::run_pass(DexStoresVector& stores,
368383
return counts;
369384
}
370385

386+
// Skip methods belonging to non-canonical lambda classes. Their code
387+
// is dead after dedup (all usage sites have been redirected), and
388+
// rewriting self-referencing method calls (e.g., the bridge invoke
389+
// calling this.invoke()) would create type inconsistencies.
390+
if (type_redirect_map.contains(meth->get_class())) {
391+
return counts;
392+
}
393+
371394
always_assert(code->cfg_built());
372395
auto& cfg = code->cfg();
373396

@@ -383,21 +406,42 @@ void KotlinLambdaDeduplicationPass::run_pass(DexStoresVector& stores,
383406
insn->set_field(it->second);
384407
counts.singleton++;
385408
}
386-
} else if (opcode == OPCODE_NEW_INSTANCE) {
387-
// Redirect new-instance (non-singleton lambdas).
409+
continue;
410+
}
411+
412+
// Redirect type operands: new-instance, check-cast, instance-of,
413+
// const-class, new-array (non-singleton lambdas).
414+
if (insn->has_type()) {
388415
const auto* type = insn->get_type();
389416
if (const auto it = type_redirect_map.find(type);
390417
it != type_redirect_map.end()) {
391418
insn->set_type(it->second);
392-
counts.non_singleton++;
419+
if (opcode == OPCODE_NEW_INSTANCE) {
420+
counts.non_singleton++;
421+
}
393422
}
394-
} else if (opcode == OPCODE_INVOKE_DIRECT) {
395-
// Redirect invoke-direct <init> (non-singleton lambdas).
396-
// Don't count this - new-instance already counted the usage.
423+
}
424+
425+
// Redirect method ref operands.
426+
if (insn->has_method()) {
397427
auto* method_ref = insn->get_method();
398-
if (const auto it = ctor_redirect_map.find(method_ref);
399-
it != ctor_redirect_map.end()) {
400-
insn->set_method(it->second);
428+
// For invoke-direct, first try the constructor redirect map.
429+
if (opcode == OPCODE_INVOKE_DIRECT) {
430+
if (const auto it = ctor_redirect_map.find(method_ref);
431+
it != ctor_redirect_map.end()) {
432+
insn->set_method(it->second);
433+
continue;
434+
}
435+
}
436+
// Redirect by declaring type (covers invoke-virtual,
437+
// invoke-interface, invoke-direct on private methods, etc.).
438+
const auto* declaring_type = method_ref->get_class();
439+
if (const auto it = type_redirect_map.find(declaring_type);
440+
it != type_redirect_map.end()) {
441+
auto* redirected = DexMethod::get_method(
442+
it->second, method_ref->get_name(), method_ref->get_proto());
443+
always_assert(redirected != nullptr);
444+
insn->set_method(redirected);
401445
}
402446
}
403447
}

0 commit comments

Comments
 (0)