Skip to content
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

Add function deduplication in debug mode, but considering metadata equality (debug info) #5977

Merged
merged 7 commits into from
May 10, 2024
10 changes: 7 additions & 3 deletions sway-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ use sway_ast::AttributeDecl;
use sway_error::handler::{ErrorEmitted, Handler};
use sway_ir::{
create_o1_pass_group, register_known_passes, Context, Kind, Module, PassGroup, PassManager,
ARGDEMOTION_NAME, CONSTDEMOTION_NAME, DCE_NAME, FUNC_DCE_NAME, INLINE_MODULE_NAME,
MEM2REG_NAME, MEMCPYOPT_NAME, MISCDEMOTION_NAME, MODULEPRINTER_NAME, RETDEMOTION_NAME,
SIMPLIFYCFG_NAME, SROA_NAME,
ARGDEMOTION_NAME, CONSTDEMOTION_NAME, DCE_NAME, FNDEDUP_DEBUG_PROFILE_NAME, FUNC_DCE_NAME,
INLINE_MODULE_NAME, MEM2REG_NAME, MEMCPYOPT_NAME, MISCDEMOTION_NAME, MODULEPRINTER_NAME,
RETDEMOTION_NAME, SIMPLIFYCFG_NAME, SROA_NAME,
};
use sway_types::constants::DOC_COMMENT_ATTRIBUTE_NAME;
use sway_types::SourceEngine;
Expand Down Expand Up @@ -883,6 +883,10 @@ pub(crate) fn compile_ast_to_ir_to_asm(
// Inlining is necessary until #4899 is resolved.
pass_group.append_pass(INLINE_MODULE_NAME);

// We run a function deduplication pass that only removes duplicate
// functions when everything, including the metadata are identical.
pass_group.append_pass(FNDEDUP_DEBUG_PROFILE_NAME);

// Do DCE so other optimizations run faster.
pass_group.append_pass(FUNC_DCE_NAME);
pass_group.append_pass(DCE_NAME);
Expand Down
116 changes: 107 additions & 9 deletions sway-ir/src/optimize/fn_dedup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,28 @@ use rustc_hash::{FxHashMap, FxHashSet, FxHasher};

use crate::{
build_call_graph, callee_first_order, AnalysisResults, Block, Context, Function, InstOp,
Instruction, IrError, Module, Pass, PassMutability, ScopedPass, Value,
Instruction, IrError, MetadataIndex, Metadatum, Module, Pass, PassMutability, ScopedPass,
Value,
};

pub const FNDEDUP_NAME: &str = "fndedup";
pub const FNDEDUP_DEBUG_PROFILE_NAME: &str = "fndedup-debug-profile";
pub const FNDEDUP_RELEASE_PROFILE_NAME: &str = "fndedup-release-profile";

pub fn create_fn_dedup_pass() -> Pass {
pub fn create_fn_dedup_release_profile_pass() -> Pass {
Pass {
name: FNDEDUP_NAME,
descr: "Deduplicate functions.",
name: FNDEDUP_RELEASE_PROFILE_NAME,
descr: "Deduplicate functions, ignore metadata",
deps: vec![],
runner: ScopedPass::ModulePass(PassMutability::Transform(dedup_fns)),
runner: ScopedPass::ModulePass(PassMutability::Transform(dedup_fns_release_profile)),
}
}

pub fn create_fn_dedup_debug_profile_pass() -> Pass {
Pass {
name: FNDEDUP_DEBUG_PROFILE_NAME,
descr: "Deduplicate functions, consider metadata also",
deps: vec![],
runner: ScopedPass::ModulePass(PassMutability::Transform(dedup_fns_debug_profile)),
}
}

Expand All @@ -35,13 +46,20 @@ struct EqClass {
function_hash_map: FxHashMap<Function, u64>,
}

fn hash_fn(context: &Context, function: Function, eq_class: &mut EqClass) -> u64 {
fn hash_fn(
context: &Context,
function: Function,
eq_class: &mut EqClass,
ignore_metadata: bool,
) -> u64 {
let state = &mut FxHasher::default();

// A unique, but only in this function, ID for values.
let localised_value_id: &mut FxHashMap<Value, u64> = &mut FxHashMap::default();
// A unique, but only in this function, ID for blocks.
let localised_block_id: &mut FxHashMap<Block, u64> = &mut FxHashMap::default();
// A unique, but only in this function, ID for MetadataIndex.
let metadata_hashes: &mut FxHashMap<MetadataIndex, u64> = &mut FxHashMap::default();
// TODO: We could do a similar localised ID'ing of local variable names
// and ASM block arguments too, thereby slightly relaxing the equality check.

Expand All @@ -54,14 +72,70 @@ fn hash_fn(context: &Context, function: Function, eq_class: &mut EqClass) -> u64
context: &Context,
v: Value,
localised_value_id: &mut FxHashMap<Value, u64>,
metadata_hashes: &mut FxHashMap<MetadataIndex, u64>,
hasher: &mut FxHasher,
ignore_metadata: bool,
) {
match &context.values.get(v.0).unwrap().value {
crate::ValueDatum::Argument(_) | crate::ValueDatum::Instruction(_) => {
get_localised_id(v, localised_value_id).hash(hasher)
}
crate::ValueDatum::Configurable(c) | crate::ValueDatum::Constant(c) => c.hash(hasher),
}
if let Some(m) = &context.values.get(v.0).unwrap().metadata {
if !ignore_metadata {
hash_metadata(context, *m, metadata_hashes, hasher)
}
}
}

fn hash_metadata(
context: &Context,
m: MetadataIndex,
metadata_hashes: &mut FxHashMap<MetadataIndex, u64>,
hasher: &mut FxHasher,
) {
if let Some(hash) = metadata_hashes.get(&m) {
return hash.hash(hasher);
}

let md_contents = context
.metadata
.get(m.0)
.expect("Orphan / missing metadata");
let descr = std::mem::discriminant(md_contents);
let state = &mut FxHasher::default();
// We temporarily set the discriminant as the hash.
descr.hash(state);
metadata_hashes.insert(m, state.finish());

fn internal(
context: &Context,
m: &Metadatum,
metadata_hashes: &mut FxHashMap<MetadataIndex, u64>,
hasher: &mut FxHasher,
) {
match m {
Metadatum::Integer(i) => i.hash(hasher),
Metadatum::Index(mdi) => hash_metadata(context, *mdi, metadata_hashes, hasher),
Metadatum::String(s) => s.hash(hasher),
Metadatum::SourceId(sid) => sid.hash(hasher),
Metadatum::Struct(name, fields) => {
name.hash(hasher);
fields
.iter()
.for_each(|field| internal(context, field, metadata_hashes, hasher));
}
Metadatum::List(l) => l
.iter()
.for_each(|i| hash_metadata(context, *i, metadata_hashes, hasher)),
}
}
internal(context, md_contents, metadata_hashes, hasher);

let m_hash = state.finish();
metadata_hashes.insert(m, m_hash);
m_hash.hash(hasher);
}

// Start with the function return type.
Expand Down Expand Up @@ -89,7 +163,14 @@ fn hash_fn(context: &Context, function: Function, eq_class: &mut EqClass) -> u64
std::mem::discriminant(&inst.op).hash(state);
// Hash value inputs to instructions in one-go.
for v in inst.op.get_operands() {
hash_value(context, v, localised_value_id, state);
hash_value(
context,
v,
localised_value_id,
metadata_hashes,
state,
ignore_metadata,
);
}
// Hash non-value inputs.
match &inst.op {
Expand Down Expand Up @@ -190,6 +271,7 @@ pub fn dedup_fns(
context: &mut Context,
_: &AnalysisResults,
module: Module,
ignore_metadata: bool,
) -> Result<bool, IrError> {
let mut modified = false;
let eq_class = &mut EqClass {
Expand All @@ -199,7 +281,7 @@ pub fn dedup_fns(
let cg = build_call_graph(context, &context.modules.get(module.0).unwrap().functions);
let callee_first = callee_first_order(&cg);
for function in callee_first {
let hash = hash_fn(context, function, eq_class);
let hash = hash_fn(context, function, eq_class, ignore_metadata);
eq_class
.hash_set_map
.entry(hash)
Expand Down Expand Up @@ -252,3 +334,19 @@ pub fn dedup_fns(

Ok(modified)
}

fn dedup_fns_debug_profile(
context: &mut Context,
analysis_results: &AnalysisResults,
module: Module,
) -> Result<bool, IrError> {
dedup_fns(context, analysis_results, module, false)
}

fn dedup_fns_release_profile(
context: &mut Context,
analysis_results: &AnalysisResults,
module: Module,
) -> Result<bool, IrError> {
dedup_fns(context, analysis_results, module, true)
}
18 changes: 10 additions & 8 deletions sway-ir/src/pass_manager.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use crate::{
create_arg_demotion_pass, create_const_combine_pass, create_const_demotion_pass,
create_dce_pass, create_dom_fronts_pass, create_dominators_pass, create_escaped_symbols_pass,
create_fn_dedup_pass, create_func_dce_pass, create_inline_in_main_pass,
create_inline_in_module_pass, create_mem2reg_pass, create_memcpyopt_pass,
create_misc_demotion_pass, create_module_printer_pass, create_module_verifier_pass,
create_postorder_pass, create_ret_demotion_pass, create_simplify_cfg_pass, create_sroa_pass,
Context, Function, IrError, Module, CONSTCOMBINE_NAME, DCE_NAME, FNDEDUP_NAME, FUNC_DCE_NAME,
INLINE_MODULE_NAME, MEM2REG_NAME, SIMPLIFYCFG_NAME,
create_fn_dedup_debug_profile_pass, create_fn_dedup_release_profile_pass, create_func_dce_pass,
create_inline_in_main_pass, create_inline_in_module_pass, create_mem2reg_pass,
create_memcpyopt_pass, create_misc_demotion_pass, create_module_printer_pass,
create_module_verifier_pass, create_postorder_pass, create_ret_demotion_pass,
create_simplify_cfg_pass, create_sroa_pass, Context, Function, IrError, Module,
CONSTCOMBINE_NAME, DCE_NAME, FNDEDUP_RELEASE_PROFILE_NAME, FUNC_DCE_NAME, INLINE_MODULE_NAME,
MEM2REG_NAME, SIMPLIFYCFG_NAME,
};
use downcast_rs::{impl_downcast, Downcast};
use rustc_hash::FxHashMap;
Expand Down Expand Up @@ -306,7 +307,8 @@ pub fn register_known_passes(pm: &mut PassManager) {
pm.register(create_module_printer_pass());
pm.register(create_module_verifier_pass());
// Optimization passes.
pm.register(create_fn_dedup_pass());
pm.register(create_fn_dedup_release_profile_pass());
pm.register(create_fn_dedup_debug_profile_pass());
pm.register(create_mem2reg_pass());
pm.register(create_sroa_pass());
pm.register(create_inline_in_module_pass());
Expand All @@ -328,7 +330,7 @@ pub fn create_o1_pass_group() -> PassGroup {
// Configure to run our passes.
o1.append_pass(MEM2REG_NAME);
o1.append_pass(INLINE_MODULE_NAME);
o1.append_pass(FNDEDUP_NAME);
o1.append_pass(FNDEDUP_RELEASE_PROFILE_NAME);
o1.append_pass(CONSTCOMBINE_NAME);
o1.append_pass(SIMPLIFYCFG_NAME);
o1.append_pass(CONSTCOMBINE_NAME);
Expand Down
67 changes: 67 additions & 0 deletions sway-ir/tests/fn_dedup/debug/debug-dce.ir
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// regex: FOONAME=fn (foo_1|foo_3)

script {
entry fn main() -> bool, !1 {
entry():
v0 = const u64 1, !2
v1 = const u64 1, !3
v2 = call foo_1(v0, v1), !4
cbr v2, block0(), block1(v2), !5

block0():
v3 = const u64 1, !6
v4 = const u64 2, !7
v5 = call foo_3(v3, v4), !8
br block1(v5), !5

block1(v6: bool):
ret bool v6
}

// check: $(FOONAME)
fn foo_1(t1 !9: u64, t2 !10: u64) -> bool, !13 {
entry(t1: u64, t2: u64):
v0 = call eq_2(t1, t2), !14
ret bool v0
}

pub fn eq_2(self !16: u64, other !17: u64) -> bool, !18 {
entry(self: u64, other: u64):
v0 = cmp eq self other
ret bool v0
}

// not: $(FOONAME)
fn foo_3(t1 !9: u64, t2 !10: u64) -> bool, !19 {
entry(t1: u64, t2: u64):
v0 = call eq_4(t1, t2), !14
ret bool v0
}

pub fn eq_4(self !16: u64, other !17: u64) -> bool, !18 {
entry(self: u64, other: u64):
v0 = cmp eq self other
ret bool v0
}
}

!0 = "sway_test/src/main.sw"
!1 = span !0 9 55
!2 = span !0 35 36
!3 = span !0 38 39
!4 = span !0 31 40
!5 = span !0 31 53
!6 = span !0 48 49
!7 = span !0 51 52
!8 = span !0 44 53
!9 = span !0 84 86
!10 = span !0 91 93
!11 = span !0 74 134
!12 = inline "never"
!13 = (!11 !12)
!14 = span !0 123 132
!15 = "sway/sway-lib-core/src/ops.sw"
!16 = span !15 12645 12649
!17 = span !15 12651 12656
!18 = span !15 12639 12705
!19 = (!11 !12)
68 changes: 68 additions & 0 deletions sway-ir/tests/fn_dedup/debug/debug-nodce.ir
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// regex: FOONAME=fn (foo_1|foo_3)

script {
entry fn main() -> bool, !1 {
entry():
v0 = const u64 1, !2
v1 = const u64 1, !3
v2 = call foo_1(v0, v1), !4
cbr v2, block0(), block1(v2), !5

block0():
v3 = const u64 1, !6
v4 = const u64 2, !7
v5 = call foo_3(v3, v4), !8
br block1(v5), !5

block1(v6: bool):
ret bool v6
}

// check: $(FOONAME)
fn foo_1(t1 !9: u64, t2 !10: u64) -> bool, !13 {
entry(t1: u64, t2: u64):
v0 = call eq_2(t1, t2), !14
ret bool v0
}

pub fn eq_2(self !16: u64, other !17: u64) -> bool, !18 {
entry(self: u64, other: u64):
v0 = cmp eq self other
ret bool v0
}

// check: $(FOONAME)
fn foo_3(t1 !9: u64, t2 !10: u64) -> bool, !19 {
entry(t1: u64, t2: u64):
v0 = call eq_4(t1, t2), !20
ret bool v0
}

pub fn eq_4(self !16: u64, other !17: u64) -> bool, !18 {
entry(self: u64, other: u64):
v0 = cmp eq self other
ret bool v0
}
}

!0 = "sway_test/src/main.sw"
!1 = span !0 9 55
!2 = span !0 35 36
!3 = span !0 38 39
!4 = span !0 31 40
!5 = span !0 31 53
!6 = span !0 48 49
!7 = span !0 51 52
!8 = span !0 44 53
!9 = span !0 84 86
!10 = span !0 91 93
!11 = span !0 74 134
!12 = inline "never"
!13 = (!11 !12)
!14 = span !0 123 132
!15 = "sway/sway-lib-core/src/ops.sw"
!16 = span !15 12645 12649
!17 = span !15 12651 12656
!18 = span !15 12639 12705
!19 = (!11 !12)
!20 = span !0 133 142
Loading
Loading