Skip to content

Commit 4f6f4eb

Browse files
authored
Rollup merge of #40377 - camlorn:optimization_fuel, r=eddyb
Implement optimization fuel and re-enable struct field reordering See [this discussion](https://internals.rust-lang.org/t/rolling-out-or-unrolling-struct-field-reorderings/4485) for background. This pull request adds two new compilation options: `-Z print-fuel=crate` prints the optimization fuel used by a crate and `-Z fuel=crate=n` sets the optimization fuel for a crate. It also turns field reordering back on. There is no way to test this feature without something consuming fuel. We can roll this back if we want, but then the optimization fuel bits will be dead code. The one notable absence from this PR is a test case. I'm not sure how to do one that's worth having. The only thing I can think of to test is `-Z fuel=foo=0`. The problem with other tests is that either (1) they're so big that future optimizations will apply, thus breaking them or (2) we don't know which order the optimizations will be applied in, so we can't guess the message that will be printed. If someone has a useful proposal for a good test, I certainly want to add one.
2 parents c58c928 + e18c59f commit 4f6f4eb

File tree

16 files changed

+219
-45
lines changed

16 files changed

+219
-45
lines changed

src/librustc/session/config.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,8 @@ macro_rules! options {
643643
Some("one of: `address`, `leak`, `memory` or `thread`");
644644
pub const parse_linker_flavor: Option<&'static str> =
645645
Some(::rustc_back::LinkerFlavor::one_of());
646+
pub const parse_optimization_fuel: Option<&'static str> =
647+
Some("crate=integer");
646648
}
647649

648650
#[allow(dead_code)]
@@ -787,6 +789,21 @@ macro_rules! options {
787789
}
788790
true
789791
}
792+
793+
fn parse_optimization_fuel(slot: &mut Option<(String, u64)>, v: Option<&str>) -> bool {
794+
match v {
795+
None => false,
796+
Some(s) => {
797+
let parts = s.split('=').collect::<Vec<_>>();
798+
if parts.len() != 2 { return false; }
799+
let crate_name = parts[0].to_string();
800+
let fuel = parts[1].parse::<u64>();
801+
if fuel.is_err() { return false; }
802+
*slot = Some((crate_name, fuel.unwrap()));
803+
true
804+
}
805+
}
806+
}
790807
}
791808
) }
792809

@@ -991,6 +1008,10 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
9911008
"Use a sanitizer"),
9921009
linker_flavor: Option<LinkerFlavor> = (None, parse_linker_flavor, [UNTRACKED],
9931010
"Linker flavor"),
1011+
fuel: Option<(String, u64)> = (None, parse_optimization_fuel, [TRACKED],
1012+
"Set the optimization fuel quota for a crate."),
1013+
print_fuel: Option<String> = (None, parse_opt_string, [TRACKED],
1014+
"Make Rustc print the total optimization fuel used by a crate."),
9941015
}
9951016

9961017
pub fn default_lib_output() -> CrateType {
@@ -1784,11 +1805,13 @@ mod dep_tracking {
17841805

17851806
impl_dep_tracking_hash_via_hash!(bool);
17861807
impl_dep_tracking_hash_via_hash!(usize);
1808+
impl_dep_tracking_hash_via_hash!(u64);
17871809
impl_dep_tracking_hash_via_hash!(String);
17881810
impl_dep_tracking_hash_via_hash!(lint::Level);
17891811
impl_dep_tracking_hash_via_hash!(Option<bool>);
17901812
impl_dep_tracking_hash_via_hash!(Option<usize>);
17911813
impl_dep_tracking_hash_via_hash!(Option<String>);
1814+
impl_dep_tracking_hash_via_hash!(Option<(String, u64)>);
17921815
impl_dep_tracking_hash_via_hash!(Option<PanicStrategy>);
17931816
impl_dep_tracking_hash_via_hash!(Option<lint::Level>);
17941817
impl_dep_tracking_hash_via_hash!(Option<PathBuf>);
@@ -1810,6 +1833,7 @@ mod dep_tracking {
18101833
impl_dep_tracking_hash_for_sortable_vec_of!((String, lint::Level));
18111834
impl_dep_tracking_hash_for_sortable_vec_of!((String, Option<String>,
18121835
Option<cstore::NativeLibraryKind>));
1836+
impl_dep_tracking_hash_for_sortable_vec_of!((String, u64));
18131837
impl DepTrackingHash for SearchPaths {
18141838
fn hash(&self, hasher: &mut DefaultHasher, _: ErrorOutputType) {
18151839
let mut elems: Vec<_> = self

src/librustc/session/mod.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,20 @@ pub struct Session {
123123
pub code_stats: RefCell<CodeStats>,
124124

125125
next_node_id: Cell<ast::NodeId>,
126+
127+
/// If -zfuel=crate=n is specified, Some(crate).
128+
optimization_fuel_crate: Option<String>,
129+
/// If -zfuel=crate=n is specified, initially set to n. Otherwise 0.
130+
optimization_fuel_limit: Cell<u64>,
131+
/// We're rejecting all further optimizations.
132+
out_of_fuel: Cell<bool>,
133+
134+
// The next two are public because the driver needs to read them.
135+
136+
/// If -zprint-fuel=crate, Some(crate).
137+
pub print_fuel_crate: Option<String>,
138+
/// Always set to zero and incremented so that we can print fuel expended by a crate.
139+
pub print_fuel: Cell<u64>,
126140
}
127141

128142
pub struct PerfStats {
@@ -507,6 +521,32 @@ impl Session {
507521
println!("Total time spent decoding DefPath tables: {}",
508522
duration_to_secs_str(self.perf_stats.decode_def_path_tables_time.get()));
509523
}
524+
525+
/// We want to know if we're allowed to do an optimization for crate foo from -z fuel=foo=n.
526+
/// This expends fuel if applicable, and records fuel if applicable.
527+
pub fn consider_optimizing<T: Fn() -> String>(&self, crate_name: &str, msg: T) -> bool {
528+
let mut ret = true;
529+
match self.optimization_fuel_crate {
530+
Some(ref c) if c == crate_name => {
531+
let fuel = self.optimization_fuel_limit.get();
532+
ret = fuel != 0;
533+
if fuel == 0 && !self.out_of_fuel.get() {
534+
println!("optimization-fuel-exhausted: {}", msg());
535+
self.out_of_fuel.set(true);
536+
} else if fuel > 0 {
537+
self.optimization_fuel_limit.set(fuel-1);
538+
}
539+
}
540+
_ => {}
541+
}
542+
match self.print_fuel_crate {
543+
Some(ref c) if c == crate_name=> {
544+
self.print_fuel.set(self.print_fuel.get()+1);
545+
},
546+
_ => {}
547+
}
548+
ret
549+
}
510550
}
511551

512552
pub fn build_session(sopts: config::Options,
@@ -602,6 +642,12 @@ pub fn build_session_(sopts: config::Options,
602642
}
603643
);
604644

645+
let optimization_fuel_crate = sopts.debugging_opts.fuel.as_ref().map(|i| i.0.clone());
646+
let optimization_fuel_limit = Cell::new(sopts.debugging_opts.fuel.as_ref()
647+
.map(|i| i.1).unwrap_or(0));
648+
let print_fuel_crate = sopts.debugging_opts.print_fuel.clone();
649+
let print_fuel = Cell::new(0);
650+
605651
let sess = Session {
606652
dep_graph: dep_graph.clone(),
607653
target: target_cfg,
@@ -643,6 +689,11 @@ pub fn build_session_(sopts: config::Options,
643689
decode_def_path_tables_time: Cell::new(Duration::from_secs(0)),
644690
},
645691
code_stats: RefCell::new(CodeStats::new()),
692+
optimization_fuel_crate: optimization_fuel_crate,
693+
optimization_fuel_limit: optimization_fuel_limit,
694+
print_fuel_crate: print_fuel_crate,
695+
print_fuel: print_fuel,
696+
out_of_fuel: Cell::new(false),
646697
};
647698

648699
init_llvm(&sess);

src/librustc/ty/context.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,11 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
732732
ast_ty_to_ty_cache: RefCell::new(NodeMap()),
733733
}, f)
734734
}
735+
736+
pub fn consider_optimizing<T: Fn() -> String>(&self, msg: T) -> bool {
737+
let cname = self.crate_name(LOCAL_CRATE).as_str();
738+
self.sess.consider_optimizing(&cname, msg)
739+
}
735740
}
736741

737742
impl<'gcx: 'tcx, 'tcx> GlobalCtxt<'gcx> {

src/librustc/ty/layout.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,6 @@ enum StructKind {
580580
}
581581

582582
impl<'a, 'gcx, 'tcx> Struct {
583-
// FIXME(camlorn): reprs need a better representation to deal with multiple reprs on one type.
584583
fn new(dl: &TargetDataLayout, fields: &Vec<&'a Layout>,
585584
repr: &ReprOptions, kind: StructKind,
586585
scapegoat: Ty<'gcx>) -> Result<Struct, LayoutError<'gcx>> {
@@ -598,12 +597,8 @@ impl<'a, 'gcx, 'tcx> Struct {
598597
// Neither do 1-member and 2-member structs.
599598
// In addition, code in trans assume that 2-element structs can become pairs.
600599
// It's easier to just short-circuit here.
601-
let mut can_optimize = (fields.len() > 2 || StructKind::EnumVariant == kind)
602-
&& ! (repr.c || repr.packed);
603-
604-
// Disable field reordering until we can decide what to do.
605-
// The odd pattern here avoids a warning about the value never being read.
606-
if can_optimize { can_optimize = false; }
600+
let can_optimize = (fields.len() > 2 || StructKind::EnumVariant == kind)
601+
&& !(repr.c || repr.packed || repr.linear || repr.simd);
607602

608603
let (optimize, sort_ascending) = match kind {
609604
StructKind::AlwaysSizedUnivariant => (can_optimize, false),

src/librustc/ty/mod.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1411,13 +1411,16 @@ pub struct ReprOptions {
14111411
pub packed: bool,
14121412
pub simd: bool,
14131413
pub int: Option<attr::IntType>,
1414+
// Internal only for now. If true, don't reorder fields.
1415+
pub linear: bool,
14141416
}
14151417

14161418
impl_stable_hash_for!(struct ReprOptions {
14171419
c,
14181420
packed,
14191421
simd,
1420-
int
1422+
int,
1423+
linear
14211424
});
14221425

14231426
impl ReprOptions {
@@ -1440,6 +1443,9 @@ impl ReprOptions {
14401443
ret.simd = true;
14411444
}
14421445

1446+
// This is here instead of layout because the choice must make it into metadata.
1447+
ret.linear = !tcx.consider_optimizing(|| format!("Reorder fields of {:?}",
1448+
tcx.item_path_str(did)));
14431449
ret
14441450
}
14451451

src/librustc_driver/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,16 @@ impl<'a> CompilerCalls<'a> for RustcDefaultCalls {
517517
control.make_glob_map = resolve::MakeGlobMap::Yes;
518518
}
519519

520+
if sess.print_fuel_crate.is_some() {
521+
let old_callback = control.compilation_done.callback;
522+
control.compilation_done.callback = box move |state| {
523+
old_callback(state);
524+
let sess = state.session;
525+
println!("Fuel used by {}: {}",
526+
sess.print_fuel_crate.as_ref().unwrap(),
527+
sess.print_fuel.get());
528+
}
529+
}
520530
control
521531
}
522532
}

src/librustc_trans/intrinsic.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use llvm;
1616
use llvm::{ValueRef};
1717
use abi::{Abi, FnType};
1818
use adt;
19-
use mir::lvalue::LvalueRef;
19+
use mir::lvalue::{LvalueRef, Alignment};
2020
use base::*;
2121
use common::*;
2222
use declare;
@@ -36,8 +36,6 @@ use syntax_pos::Span;
3636
use std::cmp::Ordering;
3737
use std::iter;
3838

39-
use mir::lvalue::Alignment;
40-
4139
fn get_simple_intrinsic(ccx: &CrateContext, name: &str) -> Option<ValueRef> {
4240
let llvm_name = match name {
4341
"sqrtf32" => "llvm.sqrt.f32",
@@ -622,7 +620,10 @@ pub fn trans_intrinsic_call<'a, 'tcx>(bcx: &Builder<'a, 'tcx>,
622620

623621
for i in 0..elems.len() {
624622
let val = bcx.extract_value(val, i);
625-
bcx.store(val, bcx.struct_gep(llresult, i), None);
623+
let lval = LvalueRef::new_sized_ty(llresult, ret_ty,
624+
Alignment::AbiAligned);
625+
let (dest, align) = lval.trans_field_ptr(bcx, i);
626+
bcx.store(val, dest, align.to_align());
626627
}
627628
C_nil(ccx)
628629
}

src/librustc_trans/mir/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ fn arg_local_refs<'a, 'tcx>(bcx: &Builder<'a, 'tcx>,
386386

387387
let lvalue = LvalueRef::alloca(bcx, arg_ty, &format!("arg{}", arg_index));
388388
for (i, &tupled_arg_ty) in tupled_arg_tys.iter().enumerate() {
389-
let dst = bcx.struct_gep(lvalue.llval, i);
389+
let (dst, _) = lvalue.trans_field_ptr(bcx, i);
390390
let arg = &mircx.fn_ty.args[idx];
391391
idx += 1;
392392
if common::type_is_fat_ptr(bcx.ccx, tupled_arg_ty) {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![crate_name="foo"]
12+
13+
use std::mem::size_of;
14+
15+
// compile-flags: -Z fuel=foo=0
16+
17+
struct S1(u8, u16, u8);
18+
struct S2(u8, u16, u8);
19+
20+
fn main() {
21+
assert_eq!(size_of::<S1>(), 6);
22+
assert_eq!(size_of::<S2>(), 6);
23+
}
24+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![crate_name="foo"]
12+
13+
use std::mem::size_of;
14+
15+
// compile-flags: -Z fuel=foo=1
16+
17+
struct S1(u8, u16, u8);
18+
struct S2(u8, u16, u8);
19+
20+
fn main() {
21+
let optimized = (size_of::<S1>() == 4) as usize
22+
+(size_of::<S2>() == 4) as usize;
23+
assert_eq!(optimized, 1);
24+
}
25+
26+

src/test/run-pass/type-sizes.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,17 @@ enum e3 {
3131
a([u16; 0], u8), b
3232
}
3333

34+
struct ReorderedStruct {
35+
a: u8,
36+
b: u16,
37+
c: u8
38+
}
39+
40+
enum ReorderedEnum {
41+
A(u8, u16, u8),
42+
B(u8, u16, u8),
43+
}
44+
3445
pub fn main() {
3546
assert_eq!(size_of::<u8>(), 1 as usize);
3647
assert_eq!(size_of::<u32>(), 4 as usize);
@@ -54,4 +65,6 @@ pub fn main() {
5465
assert_eq!(size_of::<e1>(), 8 as usize);
5566
assert_eq!(size_of::<e2>(), 8 as usize);
5667
assert_eq!(size_of::<e3>(), 4 as usize);
68+
assert_eq!(size_of::<ReorderedStruct>(), 4);
69+
assert_eq!(size_of::<ReorderedEnum>(), 6);
5770
}

src/test/ui/print-fuel/print-fuel.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![crate_name="foo"]
12+
#![allow(dead_code)]
13+
14+
// compile-flags: -Z print-fuel=foo
15+
16+
struct S1(u8, u16, u8);
17+
struct S2(u8, u16, u8);
18+
struct S3(u8, u16, u8);
19+
20+
fn main() {
21+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fuel used by foo: 3

0 commit comments

Comments
 (0)