1
Fork 0

Auto merge of #48346 - emilio:pgo, r=alexcrichton

Add basic PGO support.

This PR adds two mutually exclusive options for profile usage and generation using LLVM's instruction profile generation (the same as clang uses), `-C pgo-use` and `-C pgo-gen`.

See each commit for details.
This commit is contained in:
bors 2018-03-26 13:00:18 +00:00
commit 13a86f4d85
18 changed files with 212 additions and 10 deletions

View file

@ -27,6 +27,7 @@ fn main() {
"InstrProfilingFile.c", "InstrProfilingFile.c",
"InstrProfilingMerge.c", "InstrProfilingMerge.c",
"InstrProfilingMergeFile.c", "InstrProfilingMergeFile.c",
"InstrProfilingNameVar.c",
"InstrProfilingPlatformDarwin.c", "InstrProfilingPlatformDarwin.c",
"InstrProfilingPlatformLinux.c", "InstrProfilingPlatformLinux.c",
"InstrProfilingPlatformOther.c", "InstrProfilingPlatformOther.c",
@ -42,6 +43,8 @@ fn main() {
cfg.define("strdup", Some("_strdup")); cfg.define("strdup", Some("_strdup"));
cfg.define("open", Some("_open")); cfg.define("open", Some("_open"));
cfg.define("fdopen", Some("_fdopen")); cfg.define("fdopen", Some("_fdopen"));
cfg.define("getpid", Some("_getpid"));
cfg.define("fileno", Some("_fileno"));
} else { } else {
// Turn off various features of gcc and such, mostly copying // Turn off various features of gcc and such, mostly copying
// compiler-rt's build system already // compiler-rt's build system already
@ -50,6 +53,7 @@ fn main() {
cfg.flag("-fomit-frame-pointer"); cfg.flag("-fomit-frame-pointer");
cfg.flag("-ffreestanding"); cfg.flag("-ffreestanding");
cfg.define("VISIBILITY_HIDDEN", None); cfg.define("VISIBILITY_HIDDEN", None);
cfg.define("COMPILER_RT_HAS_UNAME", Some("1"));
} }
for src in profile_sources { for src in profile_sources {

View file

@ -1249,6 +1249,14 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
"extra arguments to prepend to the linker invocation (space separated)"), "extra arguments to prepend to the linker invocation (space separated)"),
profile: bool = (false, parse_bool, [TRACKED], profile: bool = (false, parse_bool, [TRACKED],
"insert profiling code"), "insert profiling code"),
pgo_gen: Option<String> = (None, parse_opt_string, [TRACKED],
"Generate PGO profile data, to a given file, or to the default \
location if it's empty."),
pgo_use: String = (String::new(), parse_string, [TRACKED],
"Use PGO profile data from the given profile file."),
disable_instrumentation_preinliner: bool =
(false, parse_bool, [TRACKED], "Disable the instrumentation pre-inliner, \
useful for profiling / PGO."),
relro_level: Option<RelroLevel> = (None, parse_relro_level, [TRACKED], relro_level: Option<RelroLevel> = (None, parse_relro_level, [TRACKED],
"choose which RELRO level to use"), "choose which RELRO level to use"),
nll: bool = (false, parse_bool, [UNTRACKED], nll: bool = (false, parse_bool, [UNTRACKED],
@ -1773,6 +1781,13 @@ pub fn build_session_options_and_crate_config(
); );
} }
if debugging_opts.pgo_gen.is_some() && !debugging_opts.pgo_use.is_empty() {
early_error(
error_format,
"options `-Z pgo-gen` and `-Z pgo-use` are exclusive",
);
}
let mut output_types = BTreeMap::new(); let mut output_types = BTreeMap::new();
if !debugging_opts.parse_only { if !debugging_opts.parse_only {
for list in matches.opt_strs("emit") { for list in matches.opt_strs("emit") {
@ -2886,6 +2901,14 @@ mod tests {
opts.debugging_opts.tls_model = Some(String::from("tls model")); opts.debugging_opts.tls_model = Some(String::from("tls model"));
assert!(reference.dep_tracking_hash() != opts.dep_tracking_hash()); assert!(reference.dep_tracking_hash() != opts.dep_tracking_hash());
opts = reference.clone();
opts.debugging_opts.pgo_gen = Some(String::from("abc"));
assert_ne!(reference.dep_tracking_hash(), opts.dep_tracking_hash());
opts = reference.clone();
opts.debugging_opts.pgo_use = String::from("abc");
assert_ne!(reference.dep_tracking_hash(), opts.dep_tracking_hash());
opts = reference.clone(); opts = reference.clone();
opts.cg.metadata = vec![String::from("A"), String::from("B")]; opts.cg.metadata = vec![String::from("A"), String::from("B")];
assert!(reference.dep_tracking_hash() != opts.dep_tracking_hash()); assert!(reference.dep_tracking_hash() != opts.dep_tracking_hash());

View file

@ -121,6 +121,7 @@ impl InlineAsmDiagnostic {
pub enum Diagnostic { pub enum Diagnostic {
Optimization(OptimizationDiagnostic), Optimization(OptimizationDiagnostic),
InlineAsm(InlineAsmDiagnostic), InlineAsm(InlineAsmDiagnostic),
PGO(DiagnosticInfoRef),
/// LLVM has other types that we do not wrap here. /// LLVM has other types that we do not wrap here.
UnknownDiagnostic(DiagnosticInfoRef), UnknownDiagnostic(DiagnosticInfoRef),
@ -160,6 +161,10 @@ impl Diagnostic {
Optimization(OptimizationDiagnostic::unpack(OptimizationFailure, di)) Optimization(OptimizationDiagnostic::unpack(OptimizationFailure, di))
} }
Dk::PGOProfile => {
PGO(di)
}
_ => UnknownDiagnostic(di), _ => UnknownDiagnostic(di),
} }
} }

View file

@ -322,6 +322,7 @@ pub enum DiagnosticKind {
OptimizationRemarkAnalysisAliasing, OptimizationRemarkAnalysisAliasing,
OptimizationRemarkOther, OptimizationRemarkOther,
OptimizationFailure, OptimizationFailure,
PGOProfile,
} }
/// LLVMRustArchiveKind /// LLVMRustArchiveKind
@ -1646,7 +1647,9 @@ extern "C" {
OptLevel: CodeGenOptLevel, OptLevel: CodeGenOptLevel,
MergeFunctions: bool, MergeFunctions: bool,
SLPVectorize: bool, SLPVectorize: bool,
LoopVectorize: bool); LoopVectorize: bool,
PGOGenPath: *const c_char,
PGOUsePath: *const c_char);
pub fn LLVMRustAddLibraryInfo(PM: PassManagerRef, pub fn LLVMRustAddLibraryInfo(PM: PassManagerRef,
M: ModuleRef, M: ModuleRef,
DisableSimplifyLibCalls: bool); DisableSimplifyLibCalls: bool);
@ -1741,6 +1744,7 @@ extern "C" {
pub fn LLVMRustModuleCost(M: ModuleRef) -> u64; pub fn LLVMRustModuleCost(M: ModuleRef) -> u64;
pub fn LLVMRustThinLTOAvailable() -> bool; pub fn LLVMRustThinLTOAvailable() -> bool;
pub fn LLVMRustPGOAvailable() -> bool;
pub fn LLVMRustWriteThinBitcodeToFile(PMR: PassManagerRef, pub fn LLVMRustWriteThinBitcodeToFile(PMR: PassManagerRef,
M: ModuleRef, M: ModuleRef,
BC: *const c_char) -> bool; BC: *const c_char) -> bool;

View file

@ -784,7 +784,9 @@ impl<'a> CrateLoader<'a> {
} }
fn inject_profiler_runtime(&mut self) { fn inject_profiler_runtime(&mut self) {
if self.sess.opts.debugging_opts.profile { if self.sess.opts.debugging_opts.profile ||
self.sess.opts.debugging_opts.pgo_gen.is_some()
{
info!("loading profiler"); info!("loading profiler");
let symbol = Symbol::intern("profiler_builtins"); let symbol = Symbol::intern("profiler_builtins");

View file

@ -92,6 +92,11 @@ pub fn set_probestack(cx: &CodegenCx, llfn: ValueRef) {
_ => {} _ => {}
} }
// probestack doesn't play nice either with pgo-gen.
if cx.sess().opts.debugging_opts.pgo_gen.is_some() {
return;
}
// Flag our internal `__rust_probestack` function as the stack probe symbol. // Flag our internal `__rust_probestack` function as the stack probe symbol.
// This is defined in the `compiler-builtins` crate for each architecture. // This is defined in the `compiler-builtins` crate for each architecture.
llvm::AddFunctionAttrStringValue( llvm::AddFunctionAttrStringValue(

View file

@ -1085,6 +1085,10 @@ fn link_args(cmd: &mut Linker,
cmd.build_static_executable(); cmd.build_static_executable();
} }
if sess.opts.debugging_opts.pgo_gen.is_some() {
cmd.pgo_gen();
}
// FIXME (#2397): At some point we want to rpath our guesses as to // FIXME (#2397): At some point we want to rpath our guesses as to
// where extern libraries might live, based on the // where extern libraries might live, based on the
// addl_lib_search_paths // addl_lib_search_paths

View file

@ -117,6 +117,7 @@ pub trait Linker {
fn partial_relro(&mut self); fn partial_relro(&mut self);
fn no_relro(&mut self); fn no_relro(&mut self);
fn optimize(&mut self); fn optimize(&mut self);
fn pgo_gen(&mut self);
fn debuginfo(&mut self); fn debuginfo(&mut self);
fn no_default_libraries(&mut self); fn no_default_libraries(&mut self);
fn build_dylib(&mut self, out_filename: &Path); fn build_dylib(&mut self, out_filename: &Path);
@ -280,6 +281,24 @@ impl<'a> Linker for GccLinker<'a> {
} }
} }
fn pgo_gen(&mut self) {
if !self.sess.target.target.options.linker_is_gnu { return }
// If we're doing PGO generation stuff and on a GNU-like linker, use the
// "-u" flag to properly pull in the profiler runtime bits.
//
// This is because LLVM otherwise won't add the needed initialization
// for us on Linux (though the extra flag should be harmless if it
// does).
//
// See https://reviews.llvm.org/D14033 and https://reviews.llvm.org/D14030.
//
// Though it may be worth to try to revert those changes upstream, since
// the overhead of the initialization should be minor.
self.cmd.arg("-u");
self.cmd.arg("__llvm_profile_runtime");
}
fn debuginfo(&mut self) { fn debuginfo(&mut self) {
match self.sess.opts.debuginfo { match self.sess.opts.debuginfo {
DebugInfoLevel::NoDebugInfo => { DebugInfoLevel::NoDebugInfo => {
@ -520,6 +539,10 @@ impl<'a> Linker for MsvcLinker<'a> {
// Needs more investigation of `/OPT` arguments // Needs more investigation of `/OPT` arguments
} }
fn pgo_gen(&mut self) {
// Nothing needed here.
}
fn debuginfo(&mut self) { fn debuginfo(&mut self) {
// This will cause the Microsoft linker to generate a PDB file // This will cause the Microsoft linker to generate a PDB file
// from the CodeView line tables in the object files. // from the CodeView line tables in the object files.
@ -723,6 +746,10 @@ impl<'a> Linker for EmLinker<'a> {
self.cmd.args(&["--memory-init-file", "0"]); self.cmd.args(&["--memory-init-file", "0"]);
} }
fn pgo_gen(&mut self) {
// noop, but maybe we need something like the gnu linker?
}
fn debuginfo(&mut self) { fn debuginfo(&mut self) {
// Preserve names or generate source maps depending on debug info // Preserve names or generate source maps depending on debug info
self.cmd.arg(match self.sess.opts.debuginfo { self.cmd.arg(match self.sess.opts.debuginfo {
@ -888,6 +915,9 @@ impl Linker for WasmLd {
fn optimize(&mut self) { fn optimize(&mut self) {
} }
fn pgo_gen(&mut self) {
}
fn debuginfo(&mut self) { fn debuginfo(&mut self) {
} }

View file

@ -223,6 +223,20 @@ fn exported_symbols_provider_local<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
} }
} }
if tcx.sess.opts.debugging_opts.pgo_gen.is_some() {
// These are weak symbols that point to the profile version and the
// profile name, which need to be treated as exported so LTO doesn't nix
// them.
const PROFILER_WEAK_SYMBOLS: [&'static str; 2] = [
"__llvm_profile_raw_version",
"__llvm_profile_filename",
];
for sym in &PROFILER_WEAK_SYMBOLS {
let exported_symbol = ExportedSymbol::NoDefId(SymbolName::new(sym));
symbols.push((exported_symbol, SymbolExportLevel::C));
}
}
if tcx.sess.crate_types.borrow().contains(&config::CrateTypeDylib) { if tcx.sess.crate_types.borrow().contains(&config::CrateTypeDylib) {
let symbol_name = metadata_symbol_name(tcx); let symbol_name = metadata_symbol_name(tcx);
let exported_symbol = ExportedSymbol::NoDefId(SymbolName::new(&symbol_name)); let exported_symbol = ExportedSymbol::NoDefId(SymbolName::new(&symbol_name));

View file

@ -240,6 +240,9 @@ pub struct ModuleConfig {
/// Some(level) to optimize binary size, or None to not affect program size. /// Some(level) to optimize binary size, or None to not affect program size.
opt_size: Option<llvm::CodeGenOptSize>, opt_size: Option<llvm::CodeGenOptSize>,
pgo_gen: Option<String>,
pgo_use: String,
// Flags indicating which outputs to produce. // Flags indicating which outputs to produce.
emit_no_opt_bc: bool, emit_no_opt_bc: bool,
emit_bc: bool, emit_bc: bool,
@ -274,6 +277,9 @@ impl ModuleConfig {
opt_level: None, opt_level: None,
opt_size: None, opt_size: None,
pgo_gen: None,
pgo_use: String::new(),
emit_no_opt_bc: false, emit_no_opt_bc: false,
emit_bc: false, emit_bc: false,
emit_bc_compressed: false, emit_bc_compressed: false,
@ -492,8 +498,13 @@ unsafe extern "C" fn diagnostic_handler(info: DiagnosticInfoRef, user: *mut c_vo
opt.message)); opt.message));
} }
} }
llvm::diagnostic::PGO(diagnostic_ref) => {
_ => (), let msg = llvm::build_string(|s| {
llvm::LLVMRustWriteDiagnosticInfoToString(diagnostic_ref, s)
}).expect("non-UTF8 PGO diagnostic");
diag_handler.warn(&msg);
}
llvm::diagnostic::UnknownDiagnostic(..) => {},
} }
} }
@ -932,6 +943,9 @@ pub fn start_async_translation(tcx: TyCtxt,
modules_config.passes.push("insert-gcov-profiling".to_owned()) modules_config.passes.push("insert-gcov-profiling".to_owned())
} }
modules_config.pgo_gen = sess.opts.debugging_opts.pgo_gen.clone();
modules_config.pgo_use = sess.opts.debugging_opts.pgo_use.clone();
modules_config.opt_level = Some(get_llvm_opt_level(sess.opts.optimize)); modules_config.opt_level = Some(get_llvm_opt_level(sess.opts.optimize));
modules_config.opt_size = Some(get_llvm_opt_size(sess.opts.optimize)); modules_config.opt_size = Some(get_llvm_opt_size(sess.opts.optimize));
@ -2046,6 +2060,8 @@ pub unsafe fn with_llvm_pmb(llmod: ModuleRef,
config: &ModuleConfig, config: &ModuleConfig,
opt_level: llvm::CodeGenOptLevel, opt_level: llvm::CodeGenOptLevel,
f: &mut FnMut(llvm::PassManagerBuilderRef)) { f: &mut FnMut(llvm::PassManagerBuilderRef)) {
use std::ptr;
// Create the PassManagerBuilder for LLVM. We configure it with // Create the PassManagerBuilder for LLVM. We configure it with
// reasonable defaults and prepare it to actually populate the pass // reasonable defaults and prepare it to actually populate the pass
// manager. // manager.
@ -2053,11 +2069,27 @@ pub unsafe fn with_llvm_pmb(llmod: ModuleRef,
let opt_size = config.opt_size.unwrap_or(llvm::CodeGenOptSizeNone); let opt_size = config.opt_size.unwrap_or(llvm::CodeGenOptSizeNone);
let inline_threshold = config.inline_threshold; let inline_threshold = config.inline_threshold;
llvm::LLVMRustConfigurePassManagerBuilder(builder, let pgo_gen_path = config.pgo_gen.as_ref().map(|s| {
opt_level, let s = if s.is_empty() { "default_%m.profraw" } else { s };
config.merge_functions, CString::new(s.as_bytes()).unwrap()
config.vectorize_slp, });
config.vectorize_loop);
let pgo_use_path = if config.pgo_use.is_empty() {
None
} else {
Some(CString::new(config.pgo_use.as_bytes()).unwrap())
};
llvm::LLVMRustConfigurePassManagerBuilder(
builder,
opt_level,
config.merge_functions,
config.vectorize_slp,
config.vectorize_loop,
pgo_gen_path.as_ref().map_or(ptr::null(), |s| s.as_ptr()),
pgo_use_path.as_ref().map_or(ptr::null(), |s| s.as_ptr()),
);
llvm::LLVMPassManagerBuilderSetSizeLevel(builder, opt_size as u32); llvm::LLVMPassManagerBuilderSetSizeLevel(builder, opt_size as u32);
if opt_size != llvm::CodeGenOptSizeNone { if opt_size != llvm::CodeGenOptSizeNone {

View file

@ -708,6 +708,13 @@ pub fn trans_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
} }
} }
if (tcx.sess.opts.debugging_opts.pgo_gen.is_some() ||
!tcx.sess.opts.debugging_opts.pgo_use.is_empty()) &&
unsafe { !llvm::LLVMRustPGOAvailable() }
{
tcx.sess.fatal("this compiler's LLVM does not support PGO");
}
let crate_hash = tcx.crate_hash(LOCAL_CRATE); let crate_hash = tcx.crate_hash(LOCAL_CRATE);
let link_meta = link::build_link_meta(crate_hash); let link_meta = link::build_link_meta(crate_hash);

View file

@ -61,6 +61,9 @@ unsafe fn configure_llvm(sess: &Session) {
add("rustc"); // fake program name add("rustc"); // fake program name
if sess.time_llvm_passes() { add("-time-passes"); } if sess.time_llvm_passes() { add("-time-passes"); }
if sess.print_llvm_passes() { add("-debug-pass=Structure"); } if sess.print_llvm_passes() { add("-debug-pass=Structure"); }
if sess.opts.debugging_opts.disable_instrumentation_preinliner {
add("-disable-preinline");
}
for arg in &sess.opts.cg.llvm_args { for arg in &sess.opts.cg.llvm_args {
add(&(*arg)); add(&(*arg));

View file

@ -44,6 +44,10 @@
#include "llvm-c/Transforms/PassManagerBuilder.h" #include "llvm-c/Transforms/PassManagerBuilder.h"
#if LLVM_VERSION_GE(4, 0)
#define PGO_AVAILABLE
#endif
using namespace llvm; using namespace llvm;
using namespace llvm::legacy; using namespace llvm::legacy;
@ -428,12 +432,27 @@ extern "C" void LLVMRustAddAnalysisPasses(LLVMTargetMachineRef TM,
extern "C" void LLVMRustConfigurePassManagerBuilder( extern "C" void LLVMRustConfigurePassManagerBuilder(
LLVMPassManagerBuilderRef PMBR, LLVMRustCodeGenOptLevel OptLevel, LLVMPassManagerBuilderRef PMBR, LLVMRustCodeGenOptLevel OptLevel,
bool MergeFunctions, bool SLPVectorize, bool LoopVectorize) { bool MergeFunctions, bool SLPVectorize, bool LoopVectorize,
const char* PGOGenPath, const char* PGOUsePath) {
// Ignore mergefunc for now as enabling it causes crashes. // Ignore mergefunc for now as enabling it causes crashes.
// unwrap(PMBR)->MergeFunctions = MergeFunctions; // unwrap(PMBR)->MergeFunctions = MergeFunctions;
unwrap(PMBR)->SLPVectorize = SLPVectorize; unwrap(PMBR)->SLPVectorize = SLPVectorize;
unwrap(PMBR)->OptLevel = fromRust(OptLevel); unwrap(PMBR)->OptLevel = fromRust(OptLevel);
unwrap(PMBR)->LoopVectorize = LoopVectorize; unwrap(PMBR)->LoopVectorize = LoopVectorize;
#ifdef PGO_AVAILABLE
if (PGOGenPath) {
assert(!PGOUsePath);
unwrap(PMBR)->EnablePGOInstrGen = true;
unwrap(PMBR)->PGOInstrGen = PGOGenPath;
}
if (PGOUsePath) {
assert(!PGOGenPath);
unwrap(PMBR)->PGOInstrUse = PGOUsePath;
}
#else
assert(!PGOGenPath && !PGOUsePath && "Should've caught earlier");
#endif
} }
// Unfortunately, the LLVM C API doesn't provide a way to set the `LibraryInfo` // Unfortunately, the LLVM C API doesn't provide a way to set the `LibraryInfo`
@ -766,6 +785,15 @@ LLVMRustThinLTOAvailable() {
#endif #endif
} }
extern "C" bool
LLVMRustPGOAvailable() {
#ifdef PGO_AVAILABLE
return true;
#else
return false;
#endif
}
#if LLVM_VERSION_GE(4, 0) #if LLVM_VERSION_GE(4, 0)
// Here you'll find an implementation of ThinLTO as used by the Rust compiler // Here you'll find an implementation of ThinLTO as used by the Rust compiler

View file

@ -1021,6 +1021,7 @@ enum class LLVMRustDiagnosticKind {
OptimizationRemarkAnalysisAliasing, OptimizationRemarkAnalysisAliasing,
OptimizationRemarkOther, OptimizationRemarkOther,
OptimizationFailure, OptimizationFailure,
PGOProfile,
}; };
static LLVMRustDiagnosticKind toRust(DiagnosticKind Kind) { static LLVMRustDiagnosticKind toRust(DiagnosticKind Kind) {
@ -1043,6 +1044,8 @@ static LLVMRustDiagnosticKind toRust(DiagnosticKind Kind) {
return LLVMRustDiagnosticKind::OptimizationRemarkAnalysisFPCommute; return LLVMRustDiagnosticKind::OptimizationRemarkAnalysisFPCommute;
case DK_OptimizationRemarkAnalysisAliasing: case DK_OptimizationRemarkAnalysisAliasing:
return LLVMRustDiagnosticKind::OptimizationRemarkAnalysisAliasing; return LLVMRustDiagnosticKind::OptimizationRemarkAnalysisAliasing;
case DK_PGOProfile:
return LLVMRustDiagnosticKind::PGOProfile;
default: default:
return (Kind >= DK_FirstRemark && Kind <= DK_LastRemark) return (Kind >= DK_FirstRemark && Kind <= DK_LastRemark)
? LLVMRustDiagnosticKind::OptimizationRemarkOther ? LLVMRustDiagnosticKind::OptimizationRemarkOther

View file

@ -0,0 +1,8 @@
-include ../tools.mk
all:
ifeq ($(PROFILER_SUPPORT),1)
$(RUSTC) -Copt-level=3 -Clto=fat -Z pgo-gen="$(TMPDIR)/test.profraw" test.rs
$(call RUN,test) || exit 1
[ -e "$(TMPDIR)/test.profraw" ] || (echo "No .profraw file"; exit 1)
endif

View file

@ -0,0 +1,11 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
fn main() {}

View file

@ -0,0 +1,8 @@
-include ../tools.mk
all:
ifeq ($(PROFILER_SUPPORT),1)
$(RUSTC) -g -Z pgo-gen="$(TMPDIR)/test.profraw" test.rs
$(call RUN,test) || exit 1
[ -e "$(TMPDIR)/test.profraw" ] || (echo "No .profraw file"; exit 1)
endif

View file

@ -0,0 +1,11 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
fn main() {}