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

Remove dependency on libgcc-dw2-1.dll from win32 executables. #29177

Merged
merged 10 commits into from
Nov 1, 2015
1 change: 1 addition & 0 deletions mk/cfg/i686-pc-windows-gnu.mk
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ CFG_LDPATH_i686-pc-windows-gnu :=
CFG_RUN_i686-pc-windows-gnu=$(2)
CFG_RUN_TARG_i686-pc-windows-gnu=$(call CFG_RUN_i686-pc-windows-gnu,,$(2))
CFG_GNU_TRIPLE_i686-pc-windows-gnu := i686-w64-mingw32
CFG_LIBC_STARTUP_OBJECTS_i686-pc-windows-gnu := crt2.o dllcrt2.o
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a comment here as to why these two objects are being included? It's fine to be a "pointer comment" to somewhere else as well (same with x86_64 below)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no strong objection... it just... doesn't seem scalable to comment on a variable at every point of use. Eventually, when we get rid of "gcc as a linker driver" of all platforms (we want that, right?), this line will be repeated in every one of those .mk files...
The purpose is explained in targets.mk, where one can arrive via a simple grep search.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah that's fine, just want to make sure that there's enough data here for someone to figure out why all this exists in the future.

1 change: 1 addition & 0 deletions mk/cfg/x86_64-pc-windows-gnu.mk
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ CFG_LDPATH_x86_64-pc-windows-gnu :=
CFG_RUN_x86_64-pc-windows-gnu=$(2)
CFG_RUN_TARG_x86_64-pc-windows-gnu=$(call CFG_RUN_x86_64-pc-windows-gnu,,$(2))
CFG_GNU_TRIPLE_x86_64-pc-windows-gnu := x86_64-w64-mingw32
CFG_LIBC_STARTUP_OBJECTS_x86_64-pc-windows-gnu := crt2.o dllcrt2.o
72 changes: 72 additions & 0 deletions mk/target.mk
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,73 @@ $$(TBIN$(1)_T_$(2)_H_$(3))/$(4)$$(X_$(2)): \

endef

# Macro for building runtime startup objects
# Of those we have two kinds:
# - Rust runtime-specific: these are Rust's equivalents of GCC's crti.o/crtn.o,
# - LibC-specific: these we don't build ourselves, but copy them from the system lib directory.
#
# $(1) - stage
# $(2) - target triple
# $(3) - host triple
define TARGET_RT_STARTUP

# Expand build rules for rsbegin.o and rsend.o
$$(foreach obj,rsbegin rsend, \
$$(eval $$(call TARGET_RUSTRT_STARTUP_OBJ,$(1),$(2),$(3),$$(obj))) )

# Expand build rules for libc startup objects
$$(foreach obj,$$(CFG_LIBC_STARTUP_OBJECTS_$(2)), \
$$(eval $$(call TARGET_LIBC_STARTUP_OBJ,$(1),$(2),$(3),$$(obj))) )

endef

# Macro for building runtime startup/shutdown object files;
# these are Rust's equivalent of crti.o, crtn.o
#
# $(1) - stage
# $(2) - target triple
# $(3) - host triple
# $(4) - object basename
define TARGET_RUSTRT_STARTUP_OBJ

$$(TLIB$(1)_T_$(2)_H_$(3))/$(4).o: \
$(S)src/rtstartup/$(4).rs \
$$(TLIB$(1)_T_$(2)_H_$(3))/stamp.core \
$$(HSREQ$(1)_T_$(2)_H_$(3)) \
| $$(TBIN$(1)_T_$(2)_H_$(3))/
@$$(call E, rustc: $$@)
$$(STAGE$(1)_T_$(2)_H_$(3)) --emit=obj -o $$@ $$<

# Add dependencies on Rust startup objects to all crates that depend on core.
# This ensures that they are built after core (since they depend on it),
# but before everything else (since they are needed for linking dylib crates).
$$(foreach crate, $$(TARGET_CRATES), \
$$(if $$(findstring core,$$(DEPS_$$(crate))), \
$$(TLIB$(1)_T_$(2)_H_$(3))/stamp.$$(crate))) : $$(TLIB$(1)_T_$(2)_H_$(3))/$(4).o

endef

# Macro for copying libc startup objects into the target's lib directory.
#
# $(1) - stage
# $(2) - target triple
# $(3) - host triple
# $(4) - object name
define TARGET_LIBC_STARTUP_OBJ

# Ask gcc where the startup object is located
$$(TLIB$(1)_T_$(2)_H_$(3))/$(4) : $$(shell $$(CC_$(2)) -print-file-name=$(4))
@$$(call E, cp: $$@)
@cp $$^ $$@

# Make sure this is done before libcore has finished building
# (libcore itself does not depend on these objects, but other crates do,
# so might as well do it here)
$$(TLIB$(1)_T_$(2)_H_$(3))/stamp.core : $$(TLIB$(1)_T_$(2)_H_$(3))/$(4)

endef


# Every recipe in RUST_TARGET_STAGE_N outputs to $$(TLIB$(1)_T_$(2)_H_$(3),
# a directory that can be cleaned out during the middle of a run of
# the get-snapshot.py script. Therefore, every recipe needs to have
Expand Down Expand Up @@ -174,3 +241,8 @@ $(foreach host,$(CFG_HOST), \
$(foreach stage,$(STAGES), \
$(foreach tool,$(TOOLS), \
$(eval $(call TARGET_TOOL,$(stage),$(target),$(host),$(tool)))))))

$(foreach host,$(CFG_HOST), \
$(foreach target,$(CFG_TARGET), \
$(foreach stage,$(STAGES), \
$(eval $(call TARGET_RT_STARTUP,$(stage),$(target),$(host))))))
2 changes: 2 additions & 0 deletions src/doc/trpl/custom-allocators.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ pub extern fn __rust_usable_size(size: usize, _align: usize) -> usize {
# #[lang = "panic_fmt"] fn panic_fmt() {}
# #[lang = "eh_personality"] fn eh_personality() {}
# #[lang = "eh_unwind_resume"] extern fn eh_unwind_resume() {}
# #[no_mangle] pub extern fn rust_eh_register_frames () {}
# #[no_mangle] pub extern fn rust_eh_unregister_frames () {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, these are so volatile that it seems like a pretty good indication to me that we either need to start ignoring these tests or figure out a better way to deal with this, it's unfortunate to have these keep getting duplicated... Not critical though!

```

After we compile this crate, it can be used as follows:
Expand Down
2 changes: 2 additions & 0 deletions src/doc/trpl/lang-items.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ fn main(argc: isize, argv: *const *const u8) -> isize {
#[lang = "eh_personality"] extern fn eh_personality() {}
#[lang = "panic_fmt"] fn panic_fmt() -> ! { loop {} }
# #[lang = "eh_unwind_resume"] extern fn rust_eh_unwind_resume() {}
# #[no_mangle] pub extern fn rust_eh_register_frames () {}
# #[no_mangle] pub extern fn rust_eh_unregister_frames () {}
```

Note the use of `abort`: the `exchange_malloc` lang item is assumed to
Expand Down
8 changes: 7 additions & 1 deletion src/doc/trpl/no-stdlib.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ fn start(_argc: isize, _argv: *const *const u8) -> isize {
#[lang = "eh_personality"] extern fn eh_personality() {}
#[lang = "panic_fmt"] fn panic_fmt() -> ! { loop {} }
# #[lang = "eh_unwind_resume"] extern fn rust_eh_unwind_resume() {}
# #[no_mangle] pub extern fn rust_eh_register_frames () {}
# #[no_mangle] pub extern fn rust_eh_unregister_frames () {}
# // fn main() {} tricked you, rustdoc!
```

Expand All @@ -60,6 +62,8 @@ pub extern fn main(argc: i32, argv: *const *const u8) -> i32 {
#[lang = "eh_personality"] extern fn eh_personality() {}
#[lang = "panic_fmt"] fn panic_fmt() -> ! { loop {} }
# #[lang = "eh_unwind_resume"] extern fn rust_eh_unwind_resume() {}
# #[no_mangle] pub extern fn rust_eh_register_frames () {}
# #[no_mangle] pub extern fn rust_eh_unregister_frames () {}
# // fn main() {} tricked you, rustdoc!
```

Expand Down Expand Up @@ -145,8 +149,10 @@ extern fn panic_fmt(args: &core::fmt::Arguments,
}

#[lang = "eh_personality"] extern fn eh_personality() {}
# #[lang = "eh_unwind_resume"] extern fn rust_eh_unwind_resume() {}
# #[start] fn start(argc: isize, argv: *const *const u8) -> isize { 0 }
# #[lang = "eh_unwind_resume"] extern fn rust_eh_unwind_resume() {}
# #[no_mangle] pub extern fn rust_eh_register_frames () {}
# #[no_mangle] pub extern fn rust_eh_unregister_frames () {}
# fn main() {}
```

Expand Down
13 changes: 4 additions & 9 deletions src/librustc_back/target/i686_pc_windows_gnu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,12 @@
use target::Target;

pub fn target() -> Target {
let mut options = super::windows_base::opts();
options.cpu = "pentium4".to_string();
let mut base = super::windows_base::opts();
base.cpu = "pentium4".to_string();

// Mark all dynamic libraries and executables as compatible with the larger 4GiB address
// space available to x86 Windows binaries on x86_64.
options.pre_link_args.push("-Wl,--large-address-aware".to_string());

// Make sure that we link to the dynamic libgcc, otherwise cross-module
// DWARF stack unwinding will not work.
// This behavior may be overridden by -Clink-args="-static-libgcc"
options.pre_link_args.push("-shared-libgcc".to_string());
base.pre_link_args.push("-Wl,--large-address-aware".to_string());

Target {
llvm_target: "i686-pc-windows-gnu".to_string(),
Expand All @@ -31,6 +26,6 @@ pub fn target() -> Target {
target_os: "windows".to_string(),
target_env: "gnu".to_string(),
target_vendor: "pc".to_string(),
options: options,
options: base,
}
}
21 changes: 16 additions & 5 deletions src/librustc_back/target/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,25 @@ pub struct TargetOptions {
pub linker: String,
/// Archive utility to use when managing archives. Defaults to "ar".
pub ar: String,

/// Linker arguments that are unconditionally passed *before* any
/// user-defined libraries.
pub pre_link_args: Vec<String>,
/// Objects to link before all others, always found within the
/// sysroot folder.
pub pre_link_objects_exe: Vec<String>, // ... when linking an executable
pub pre_link_objects_dll: Vec<String>, // ... when linking a dylib
/// Linker arguments that are unconditionally passed after any
/// user-defined but before post_link_objects. Standard platform
/// libraries that should be always be linked to, usually go here.
pub late_link_args: Vec<String>,
/// Objects to link after all others, always found within the
/// sysroot folder.
pub post_link_objects: Vec<String>,
/// Linker arguments that are unconditionally passed *after* any
/// user-defined libraries.
pub post_link_args: Vec<String>,
/// Objects to link before and after all others, always found within the
/// sysroot folder.
pub pre_link_objects: Vec<String>,
pub post_link_objects: Vec<String>,

/// Default CPU to pass to LLVM. Corresponds to `llc -mcpu=$cpu`. Defaults
/// to "default".
pub cpu: String,
Expand Down Expand Up @@ -219,8 +228,10 @@ impl Default for TargetOptions {
no_compiler_rt: false,
no_default_libraries: true,
position_independent_executables: false,
pre_link_objects: Vec::new(),
pre_link_objects_exe: Vec::new(),
pre_link_objects_dll: Vec::new(),
post_link_objects: Vec::new(),
late_link_args: Vec::new(),
archive_format: String::new(),
custom_unwind_resume: false,
lib_allocation_crate: "alloc_system".to_string(),
Expand Down
30 changes: 26 additions & 4 deletions src/librustc_back/target/windows_base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,7 @@ pub fn opts() -> TargetOptions {
exe_suffix: ".exe".to_string(),
staticlib_prefix: "".to_string(),
staticlib_suffix: ".lib".to_string(),
// Unfortunately right now passing -nodefaultlibs to gcc on windows
// doesn't work so hot (in terms of native dependencies). This flag
// should hopefully be removed one day though!
no_default_libraries: false,
no_default_libraries: true,
is_like_windows: true,
archive_format: "gnu".to_string(),
pre_link_args: vec!(
Expand Down Expand Up @@ -63,7 +60,32 @@ pub fn opts() -> TargetOptions {

// Always enable DEP (NX bit) when it is available
"-Wl,--nxcompat".to_string(),

// Do not use the standard system startup files or libraries when linking
"-nostdlib".to_string(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this necessary? I thought that -nodefaultlibs would do what this comment indicates, and the man pages at least indicate that -nostdlib means that only the directories on the command line will be searched (e.g. doesn't mention anything about startup files or objects).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My man page's section for -nostdlib begins with this exact sentence . I could have used -nostartfiles since we are also passing -nodefaultlibs, but just to make sure...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wait I was looking at the man page for ld not gcc, nevermind!

),
pre_link_objects_exe: vec!(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you be sure to add a comment for these fields? They'll probably want to point somewhere else, but just an indication about where to find info about what they're doing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are explained in the struct definition. Is that not enough?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I was looking more of a "why are these passed on this platform" moreso than "what does this field do"

"crt2.o".to_string(), // mingw C runtime initialization for executables
"rsbegin.o".to_string(), // Rust compiler runtime initialization, see rsbegin.rs
),
pre_link_objects_dll: vec!(
"dllcrt2.o".to_string(), // mingw C runtime initialization for dlls
"rsbegin.o".to_string(),
),
late_link_args: vec!(
"-lmingwex".to_string(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering, what do we rely on mingwex for anyway?

"-lmingw32".to_string(),
"-lgcc".to_string(), // alas, mingw* libraries above depend on libgcc
"-lmsvcrt".to_string(),
"-ladvapi32".to_string(),
"-lshell32".to_string(),
"-luser32".to_string(),
"-lkernel32".to_string(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an interesting list! Were these added because no_default_libraries is now true? In the spirit of "no default libraries" none of these should actually be specified (e.g. they have to be explicitly linked in). This has the risk of being a breaking change, however, but it's one that I think I'd be fine stomaching.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The trouble is, they have to come after all other libs, and be in this specific sequence. Just consider them a part of the platform runtime. I think compiler-rt should also go here, btw.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of our other platforms don't implicitly pass -l arguments because they pass -nodefaultlibs to gcc, and when that started I just didn't take care of MinGW at the time as I didn't really fully understand it. So in theory libraries like libstd and liblibc and such are the ones that pass these -l flags, and they can control the ordering via the normal methods (e.g. in the source or via the crate DAG).

The compiler-rt library is passed by the compiler at the very end of the linker, and it's got a flag in the custom target spec as well to say "don't pass this as it's not available on this platform"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant that we should just stick compiler-rt into late_link_args and eliminate its special status.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah that's a good point, but probably good to consider separately!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm it may be the case that some parts of those previous libraries depend on things like advapi32, but I was able to succesfully compile an executable with just:

-lmingw32 -lmingwex -lkernel32 -lmsvcrt -lgcc

If we include mingwex or mingw32 I think we must include gcc because they're probably generated by gcc (and need the intrinsics). I think we can successfully link without advapi32, shell32, and user32, though?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, hm. I stand corrected!

Well, on second look, there is a small chance of backcompat fall-out:
nm -A -u libmingwex.a | grep __imp__ mostly shows symbols from kernel32, but not all of them are:
lib32_libmingwex_a-getlogin.o: U __imp__GetUserNameA@8 is from advapi32.dll,
lib32_libmingwex_a-wassert.o: U __imp__MessageBoxW@16 is from user32.dll

The last one actually breaks linking when building libflate, because I guess miniz uses assert internally.
There might be other crates who are in the same boat.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In terms of intrinsically required libraries by the compiler, however, I don't these may want to not be in this list. These libraries are injected into all compilations ever for this target, and it can end up creating hidden dependencies and such (like in this case), when the "most correct" thing to do would probably be to only include those the compiler itself requires. I think we need mingw32/mingwex to build executables as crt2.o references some symbols from those, and then kernel32/msvcrt/gcc are dependencies of those libraries, so those are all required for dlls/exes. If libflate is the one, though, that depends on advapi32 or user32, then libflate itself should declare that I think (it should also in theory declare that it depends on mingwex).

That being said, though, we probably want to just link these into the standard library (e.g. add -luser32 which I think may be missing today) and we should already be covered by -ladvapi32 in the standard library?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

libflate does not depend on user32 directly, it depends on mingwex, which depends on user32.
Assert is actually a bit of a weird case, because normally assert's helper function lives libc (I think). Linking to mingwex by default papers over the fact that that on mingw it isn't provided by libc (though msvcrt does export assert, not sure why mingw folks were not happy with it).
I worry that this might be rather common to have crates that depend on a C library, that uses assert (and thus depends on mingwex), which don't specify mingwex as a #[link] dependency, because on all other platforms linking to libc is enough.
How about we drop advapi32 and shell32 but keep user32? (see my last commit)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, sounds good to me!

),
post_link_objects: vec!(
"rsend.o".to_string()
),
custom_unwind_resume: true,
exe_allocation_crate: super::maybe_jemalloc(),

.. Default::default()
Expand Down
3 changes: 0 additions & 3 deletions src/librustc_back/target/x86_64_pc_windows_gnu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@ use target::Target;
pub fn target() -> Target {
let mut base = super::windows_base::opts();
base.cpu = "x86-64".to_string();
// On Win64 unwinding is handled by the OS, so we can link libgcc statically.
base.pre_link_args.push("-static-libgcc".to_string());
base.pre_link_args.push("-m64".to_string());
base.custom_unwind_resume = true;

Target {
llvm_target: "x86_64-pc-windows-gnu".to_string(),
Expand Down
4 changes: 2 additions & 2 deletions src/librustc_back/target/x86_64_unknown_linux_musl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ pub fn target() -> Target {
//
// Each target directory for musl has these object files included in it so
// they'll be included from there.
base.pre_link_objects.push("crt1.o".to_string());
base.pre_link_objects.push("crti.o".to_string());
base.pre_link_objects_exe.push("crt1.o".to_string());
base.pre_link_objects_exe.push("crti.o".to_string());
base.post_link_objects.push("crtn.o".to_string());

// MUSL support doesn't currently include dynamic linking, so there's no
Expand Down
9 changes: 8 additions & 1 deletion src/librustc_trans/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -850,7 +850,13 @@ fn link_natively(sess: &Session, dylib: bool,

let root = sess.target_filesearch(PathKind::Native).get_lib_path();
cmd.args(&sess.target.target.options.pre_link_args);
for obj in &sess.target.target.options.pre_link_objects {

let pre_link_objects = if dylib {
&sess.target.target.options.pre_link_objects_dll
} else {
&sess.target.target.options.pre_link_objects_exe
};
for obj in pre_link_objects {
cmd.arg(root.join(obj));
}

Expand All @@ -866,6 +872,7 @@ fn link_natively(sess: &Session, dylib: bool,
linker.link_staticlib("compiler-rt");
}
}
cmd.args(&sess.target.target.options.late_link_args);
for obj in &sess.target.target.options.post_link_objects {
cmd.arg(root.join(obj));
}
Expand Down
13 changes: 13 additions & 0 deletions src/librustc_trans/trans/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,19 @@ pub fn call_lifetime_end(cx: Block, ptr: ValueRef) {
Call(cx, lifetime_end, &[C_u64(ccx, size), ptr], None, DebugLoc::None);
}

// Generates code for resumption of unwind at the end of a landing pad.
pub fn trans_unwind_resume(bcx: Block, lpval: ValueRef) {
if !bcx.sess().target.target.options.custom_unwind_resume {
Resume(bcx, lpval);
} else {
let exc_ptr = ExtractValue(bcx, lpval, 0);
let llunwresume = bcx.fcx.eh_unwind_resume();
Call(bcx, llunwresume, &[exc_ptr], None, DebugLoc::None);
Unreachable(bcx);
}
}


pub fn call_memcpy(cx: Block, dst: ValueRef, src: ValueRef, n_bytes: ValueRef, align: u32) {
let _icx = push_ctxt("call_memcpy");
let ccx = cx.ccx();
Expand Down
4 changes: 1 addition & 3 deletions src/librustc_trans/trans/cleanup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,7 @@ impl<'blk, 'tcx> CleanupHelperMethods<'blk, 'tcx> for FunctionContext<'blk, 'tcx
"create_landing_pad() should have set this");
let lp = build::Load(prev_bcx, personality);
base::call_lifetime_end(prev_bcx, personality);
build::Resume(prev_bcx, lp);
base::trans_unwind_resume(prev_bcx, lp);
prev_llbb = prev_bcx.llbb;
break;
}
Expand Down Expand Up @@ -845,8 +845,6 @@ impl<'blk, 'tcx> CleanupHelperMethods<'blk, 'tcx> for FunctionContext<'blk, 'tcx

debug!("get_or_create_landing_pad");

self.inject_unwind_resume_hook();

// Check if a landing pad block exists; if not, create one.
{
let mut scopes = self.scopes.borrow_mut();
Expand Down
Loading