Skip to content

Commit

Permalink
Add function deduplication in debug mode, but considering metadata eq…
Browse files Browse the repository at this point in the history
…uality (debug info) (#5977)

Closes #5890

---------

Co-authored-by: IGI-111 <igi-111@protonmail.com>
Co-authored-by: Joshua Batty <joshpbatty@gmail.com>
  • Loading branch information
3 people authored May 10, 2024
1 parent 9e51a28 commit eb43057
Show file tree
Hide file tree
Showing 7 changed files with 359 additions and 21 deletions.
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

0 comments on commit eb43057

Please sign in to comment.