Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 95 additions & 8 deletions src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ pub(crate) struct BuildSettings {
opt_level: OptLevel,
debug_level: DebugLevel,
use_wasm_opt: bool,
openmp: bool,
}

/// A single user-supplied token destined for the link stage. Flags (`-Wl`,
Expand Down Expand Up @@ -266,6 +267,7 @@ pub(crate) fn link_only(args: Vec<String>, mut user_settings: UserSettings) -> R
opt_level: OptLevel::O0,
debug_level: DebugLevel::G0,
use_wasm_opt: user_settings.run_wasm_opt.unwrap_or(true),
openmp: false,
};

let state = State {
Expand Down Expand Up @@ -553,20 +555,30 @@ fn build_link_args(
push("-lm");
push("-lpthread");
push("-lutil");

if state.cxx || state.user_settings.include_cpp_symbols {
push("-lc++");
push("-lc++abi");
if state.user_settings.wasm_exceptions.is_enabled() {
push("-lunwind");
}
}
}

if matches!(module_kind, ModuleKind::DynamicMain) {
push("--no-whole-archive");
}

// -fopenmp is a compiler-driver flag and cannot be forwarded to wasm-ld.
// The caller supplies libomp's search path just like any other -L path.
if state.build_settings.openmp {
push("-lomp");
}

// The WASIX libomp archive references the C++ runtime. Shared modules do
// not otherwise receive it, and executables should receive only one copy.
if state.build_settings.openmp
|| (module_kind.is_executable() && (state.cxx || state.user_settings.include_cpp_symbols))
{
push("-lc++");
push("-lc++abi");
if state.user_settings.wasm_exceptions.is_enabled() {
push("-lunwind");
}
}

// Link as much as needed out of libclang_rt.builtins regardless of module kind.
push("-lclang_rt.builtins-wasm32");

Expand Down Expand Up @@ -880,6 +892,7 @@ mod tests {
opt_level: OptLevel::O0,
debug_level: DebugLevel::G0,
use_wasm_opt: true,
openmp: false,
},
args: PreparedArgs {
compiler_args: Vec::new(),
Expand Down Expand Up @@ -907,6 +920,7 @@ mod tests {
opt_level: OptLevel::O0,
debug_level: DebugLevel::G0,
use_wasm_opt: true,
openmp: false,
},
args: PreparedArgs {
compiler_args: Vec::new(),
Expand Down Expand Up @@ -934,6 +948,7 @@ mod tests {
opt_level: OptLevel::O0,
debug_level: DebugLevel::G0,
use_wasm_opt: true,
openmp: false,
},
args: PreparedArgs {
compiler_args: Vec::new(),
Expand Down Expand Up @@ -962,6 +977,7 @@ mod tests {
opt_level: OptLevel::O0,
debug_level: DebugLevel::G0,
use_wasm_opt: true,
openmp: false,
},
args: PreparedArgs {
compiler_args: Vec::new(),
Expand Down Expand Up @@ -989,6 +1005,7 @@ mod tests {
opt_level: OptLevel::O0,
debug_level: DebugLevel::G0,
use_wasm_opt: true,
openmp: false,
},
args: PreparedArgs {
compiler_args: Vec::new(),
Expand Down Expand Up @@ -1017,6 +1034,7 @@ mod tests {
opt_level: OptLevel::O0,
debug_level: DebugLevel::G0,
use_wasm_opt: true,
openmp: false,
},
args: PreparedArgs {
compiler_args: Vec::new(),
Expand Down Expand Up @@ -1045,6 +1063,7 @@ mod tests {
opt_level: OptLevel::O0,
debug_level: DebugLevel::G0,
use_wasm_opt: true,
openmp: false,
},
args: PreparedArgs {
compiler_args: Vec::new(),
Expand Down Expand Up @@ -1076,6 +1095,7 @@ mod tests {
opt_level: OptLevel::O0,
debug_level: DebugLevel::G0,
use_wasm_opt: true,
openmp: false,
},
args: PreparedArgs {
compiler_args: Vec::new(),
Expand Down Expand Up @@ -1137,6 +1157,7 @@ mod tests {
opt_level: OptLevel::O0,
debug_level: DebugLevel::G0,
use_wasm_opt: true,
openmp: false,
},
args: PreparedArgs {
compiler_args: Vec::new(),
Expand Down Expand Up @@ -1235,6 +1256,7 @@ mod tests {
opt_level: OptLevel::O0,
debug_level: DebugLevel::G0,
use_wasm_opt: false,
openmp: false,
},
args: PreparedArgs {
compiler_args: Vec::new(),
Expand All @@ -1260,6 +1282,23 @@ mod tests {
.collect()
}

fn compiler_args_state(args: &[&str], cxx: bool) -> State {
let mut user_settings = UserSettings::default();
let (args, build_settings) = prepare_compiler_args(
args.iter().map(|arg| (*arg).to_owned()),
&mut user_settings,
cxx,
)
.unwrap();
State {
user_settings,
build_settings,
args,
cxx,
temp_dir: PathBuf::from("/tmp"),
}
}

#[test]
fn test_dynamic_main_user_lib_emitted_after_whole_archive_block() {
// Regression: for DynamicMain, wasixcc force-includes the sysroot libs via
Expand Down Expand Up @@ -1296,6 +1335,54 @@ mod tests {
);
}

#[test]
fn test_openmp_links_runtime_for_shared_module() {
let state = compiler_args_state(&["-fopenmp", "-shared", "ext.o", "-o", "out.wasm"], false);
assert!(state.build_settings.openmp);
let args = rendered_link_args(&state);

for library in ["-lomp", "-lc++", "-lc++abi", "-lunwind"] {
assert_eq!(
args.iter().filter(|arg| arg.as_str() == library).count(),
1,
"OpenMP should link {library} once, got {args:?}"
);
}
}

#[test]
fn test_openmp_cxx_executable_does_not_duplicate_cpp_runtime() {
let mut state = link_args_state(
ModuleKind::StaticMain,
vec![LinkToken::Input(PathBuf::from("main.o"))],
);
state.build_settings.openmp = true;
state.cxx = true;
let args = rendered_link_args(&state);

for library in ["-lomp", "-lc++", "-lc++abi", "-lunwind"] {
assert_eq!(
args.iter().filter(|arg| arg.as_str() == library).count(),
1,
"OpenMP C++ executables should link {library} once, got {args:?}"
);
}
}

#[test]
fn test_no_openmp_leaves_runtime_out() {
let state = link_args_state(
ModuleKind::SharedLibrary,
vec![LinkToken::Input(PathBuf::from("ext.o"))],
);
let args = rendered_link_args(&state);

assert!(
!args.iter().any(|arg| arg == "-lomp"),
"libomp must not be linked without OpenMP, got {args:?}"
);
}

#[test]
fn test_user_whole_archive_bracket_contiguous_in_link_args() {
// A user --whole-archive <archive> --no-whole-archive must reach wasm-ld
Expand Down
47 changes: 47 additions & 0 deletions src/compiler/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ fn update_build_settings_from_compiler_flag(
user_settings.wasm_exceptions = WasmExceptionStyle::Exnref
}
Flag::Simple("-fno-exceptions") => user_settings.wasm_exceptions = WasmExceptionStyle::Off,
// The link stage invokes wasm-ld directly, so remember the compiler
// driver's OpenMP runtime request and translate it in build_link_args.
Flag::Simple("-fopenmp" | "-fopenmp=libomp") => build_settings.openmp = true,
Flag::Simple("-fno-openmp") => build_settings.openmp = false,
Flag::Simple("-fPIC") => user_settings.pic = true,
Flag::Simple("-fno-PIC") => user_settings.pic = false,
Flag::Simple("--wasm-opt") => build_settings.use_wasm_opt = true,
Expand Down Expand Up @@ -455,6 +459,7 @@ pub(super) fn prepare_compiler_args(
opt_level: OptLevel::O0,
debug_level: DebugLevel::G0,
use_wasm_opt: true,
openmp: false,
};

let args = DEFAULT_WASM_CLANG_FLAGS
Expand Down Expand Up @@ -557,6 +562,7 @@ mod tests {
opt_level: OptLevel::O0,
debug_level: DebugLevel::G0,
use_wasm_opt: true,
openmp: false,
};
let mut us = UserSettings::default();
update_build_settings_from_compiler_flag(Flag::Simple("-O3"), &mut bs, &mut us);
Expand All @@ -572,6 +578,11 @@ mod tests {
assert_eq!(us.wasm_exceptions, WasmExceptionStyle::Exnref);
update_build_settings_from_compiler_flag(Flag::Simple("-fno-exceptions"), &mut bs, &mut us);
assert_eq!(us.wasm_exceptions, WasmExceptionStyle::Off);
assert!(!bs.openmp);
update_build_settings_from_compiler_flag(Flag::Simple("-fopenmp"), &mut bs, &mut us);
assert!(bs.openmp);
update_build_settings_from_compiler_flag(Flag::Simple("-fno-openmp"), &mut bs, &mut us);
assert!(!bs.openmp);
us = UserSettings::default();
update_build_settings_from_compiler_flag(
Flag::Simple("-fwasm-exceptions"),
Expand Down Expand Up @@ -643,6 +654,42 @@ mod tests {
assert!(pa.linker_inputs().is_empty());
}

#[test]
fn test_openmp_flags_flow_through_compiler_arg_processing() {
for enable_flag in ["-fopenmp", "-fopenmp=libomp"] {
let mut us = UserSettings::default();
let args = vec![enable_flag.to_string(), "input.o".to_string()];
let (prepared, settings) = prepare_compiler_args(args, &mut us, false).unwrap();

assert!(
settings.openmp,
"{enable_flag} should request the OpenMP runtime"
);
assert!(
prepared.compiler_args.iter().any(|arg| arg == enable_flag),
"{enable_flag} must still reach the compiler frontend"
);
assert_eq!(prepared.linker_inputs(), vec![PathBuf::from("input.o")]);
}

let mut us = UserSettings::default();
let (_, settings) = prepare_compiler_args(
["-fopenmp", "-fno-openmp", "input.o"].map(str::to_string),
&mut us,
false,
)
.unwrap();
assert!(!settings.openmp, "the last OpenMP flag should win");

let (_, settings) = prepare_compiler_args(
["-fno-openmp", "-fopenmp", "input.o"].map(str::to_string),
&mut us,
false,
)
.unwrap();
assert!(settings.openmp, "the last OpenMP flag should win");
}

#[test]
fn test_prepare_compiler_args_lib_flag() {
let mut us = UserSettings::default();
Expand Down
Loading