Use the sym operator for inline assembly (#5459)

* Use the `sym` operator for inline assembly

Avoids extra `#[no_mangle]` functions and undue symbols being exposed
from Wasmtime. This is a newly stabilized feature in Rust 1.66.0. I've
also added a `rust-version` entry to the `wasmtime` crate to try to head
off possible reports in the future about odd error messages or usage of
unstable features if the rustc version is too old.

* Fix a s390x warning

* Add `rust-version` annotation to Wasmtime crate

As the other main entrypoint for embedders.
This commit is contained in:
Alex Crichton
2022-12-16 14:12:24 -06:00
committed by GitHub
parent 25bf8e0e67
commit d9fdbfd50e
12 changed files with 204 additions and 185 deletions

View File

@@ -77,7 +77,7 @@ jobs:
submodules: true submodules: true
- uses: ./.github/actions/install-rust - uses: ./.github/actions/install-rust
with: with:
toolchain: nightly-2022-09-07 toolchain: nightly-2022-12-15
# Build C API documentation # Build C API documentation
- run: curl -L https://sourceforge.net/projects/doxygen/files/rel-1.9.3/doxygen-1.9.3.linux.bin.tar.gz/download | tar xzf - - run: curl -L https://sourceforge.net/projects/doxygen/files/rel-1.9.3/doxygen-1.9.3.linux.bin.tar.gz/download | tar xzf -
@@ -197,7 +197,7 @@ jobs:
# flags to rustc. # flags to rustc.
- uses: ./.github/actions/install-rust - uses: ./.github/actions/install-rust
with: with:
toolchain: nightly-2022-09-07 toolchain: nightly-2022-12-15
- run: cargo install cargo-fuzz --vers "^0.11" - run: cargo install cargo-fuzz --vers "^0.11"
# Install the OCaml packages necessary for fuzz targets that use the # Install the OCaml packages necessary for fuzz targets that use the
# `wasm-spec-interpreter`. # `wasm-spec-interpreter`.

View File

@@ -11,6 +11,7 @@ repository = "https://github.com/bytecodealliance/wasmtime"
readme = "README.md" readme = "README.md"
edition.workspace = true edition.workspace = true
default-run = "wasmtime" default-run = "wasmtime"
rust-version.workspace = true
[lib] [lib]
doctest = false doctest = false
@@ -105,6 +106,7 @@ exclude = [
version = "5.0.0" version = "5.0.0"
authors = ["The Wasmtime Project Developers"] authors = ["The Wasmtime Project Developers"]
edition = "2021" edition = "2021"
rust-version = "1.66.0"
[workspace.dependencies] [workspace.dependencies]
wasmtime = { path = "crates/wasmtime", version = "5.0.0", default-features = false } wasmtime = { path = "crates/wasmtime", version = "5.0.0", default-features = false }

View File

@@ -5,51 +5,43 @@
//! attributes correct (e.g. ELF symbols get a size and are flagged as a //! attributes correct (e.g. ELF symbols get a size and are flagged as a
//! function) and additionally handles visibility across platforms. All symbols //! function) and additionally handles visibility across platforms. All symbols
//! should be visible to Rust but not visible externally outside of a `*.so`. //! should be visible to Rust but not visible externally outside of a `*.so`.
//!
//! It also exports a an `asm_sym!` macro which can be used to reference symbols
//! from within `global_asm!`-defined functions, and handles adding the leading
//! underscore that macOS prepends to symbols for you.
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(target_os = "macos")] { if #[cfg(target_os = "macos")] {
#[macro_export] #[macro_export]
macro_rules! asm_func { macro_rules! asm_func {
($name:expr, $($body:tt)*) => { ($name:expr, $body:expr $(, $($args:tt)*)?) => {
std::arch::global_asm!(concat!( std::arch::global_asm!(
".p2align 4\n", concat!(
".private_extern _", $name, "\n", ".p2align 4\n",
".global _", $name, "\n", ".private_extern _", $name, "\n",
"_", $name, ":\n", ".global _", $name, "\n",
$($body)* "_", $name, ":\n",
)); $body,
),
$($($args)*)?
);
}; };
} }
#[macro_export]
macro_rules! asm_sym {
( $( $name:tt )* ) => ( concat!("_", $( $name )* ) )
}
} else if #[cfg(target_os = "windows")] { } else if #[cfg(target_os = "windows")] {
#[macro_export] #[macro_export]
macro_rules! asm_func { macro_rules! asm_func {
($name:expr, $($body:tt)*) => { ($name:expr, $body:expr $(, $($args:tt)*)?) => {
std::arch::global_asm!(concat!( std::arch::global_asm!(
".def ", $name, "\n", concat!(
".scl 2\n", ".def ", $name, "\n",
".type 32\n", ".scl 2\n",
".endef\n", ".type 32\n",
".global ", $name, "\n", ".endef\n",
".p2align 4\n", ".global ", $name, "\n",
$name, ":\n", ".p2align 4\n",
$($body)* $name, ":\n",
)); $body
),
$($($args)*)?
);
}; };
} }
#[macro_export]
macro_rules! asm_sym {
( $( $name:tt )* ) => ( $( $name )* )
}
} else { } else {
// Note that for now this "else" clause just assumes that everything // Note that for now this "else" clause just assumes that everything
// other than macOS is ELF and has the various directives here for // other than macOS is ELF and has the various directives here for
@@ -70,22 +62,20 @@ cfg_if::cfg_if! {
#[macro_export] #[macro_export]
macro_rules! asm_func { macro_rules! asm_func {
($name:expr, $($body:tt)*) => { ($name:expr, $body:expr $(, $($args:tt)*)?) => {
std::arch::global_asm!(concat!( std::arch::global_asm!(
".p2align 4\n", concat!(
".hidden ", $name, "\n", ".p2align 4\n",
".global ", $name, "\n", ".hidden ", $name, "\n",
$crate::elf_func_type_header!($name), ".global ", $name, "\n",
$name, ":\n", $crate::elf_func_type_header!($name),
concat!($($body)*), $name, ":\n",
".size ", $name, ",.-", $name, $body,
)); ".size ", $name, ",.-", $name,
)
$(, $($args)*)?
);
}; };
} }
#[macro_export]
macro_rules! asm_sym {
( $( $name:tt )* ) => ( $( $name )* )
}
} }
} }

View File

@@ -110,6 +110,8 @@ extern "C" {
entry_arg0: *mut u8, entry_arg0: *mut u8,
); );
fn wasmtime_fiber_switch(top_of_stack: *mut u8); fn wasmtime_fiber_switch(top_of_stack: *mut u8);
#[allow(dead_code)] // only used in inline assembly for some platforms
fn wasmtime_fiber_start();
} }
extern "C" fn fiber_start<F, A, B, C>(arg0: *mut u8, top_of_stack: *mut u8) extern "C" fn fiber_start<F, A, B, C>(arg0: *mut u8, top_of_stack: *mut u8)

View File

@@ -18,6 +18,7 @@
// `DW_CFA_AARCH64_negate_ra_state` DWARF operation (aliased with the // `DW_CFA_AARCH64_negate_ra_state` DWARF operation (aliased with the
// `.cfi_window_save` assembler directive) informs an unwinder about this // `.cfi_window_save` assembler directive) informs an unwinder about this
use super::wasmtime_fiber_start;
use wasmtime_asm_macros::asm_func; use wasmtime_asm_macros::asm_func;
cfg_if::cfg_if! { cfg_if::cfg_if! {
@@ -25,8 +26,8 @@ cfg_if::cfg_if! {
macro_rules! paci1716 { () => ("pacib1716\n"); } macro_rules! paci1716 { () => ("pacib1716\n"); }
macro_rules! pacisp { () => ("pacibsp\n"); } macro_rules! pacisp { () => ("pacibsp\n"); }
macro_rules! autisp { () => ("autibsp\n"); } macro_rules! autisp { () => ("autibsp\n"); }
macro_rules! sym_adrp { ($s:tt) => (concat!("_", $s, "@PAGE")); } macro_rules! sym_adrp { ($s:tt) => (concat!($s, "@PAGE")); }
macro_rules! sym_add { ($s:tt) => (concat!("_", $s, "@PAGEOFF")); } macro_rules! sym_add { ($s:tt) => (concat!($s, "@PAGEOFF")); }
} else { } else {
macro_rules! paci1716 { () => ("pacia1716\n"); } macro_rules! paci1716 { () => ("pacia1716\n"); }
macro_rules! pacisp { () => ("paciasp\n"); } macro_rules! pacisp { () => ("paciasp\n"); }
@@ -39,52 +40,54 @@ cfg_if::cfg_if! {
// fn(top_of_stack(%x0): *mut u8) // fn(top_of_stack(%x0): *mut u8)
asm_func!( asm_func!(
"wasmtime_fiber_switch", "wasmtime_fiber_switch",
" concat!(
.cfi_startproc "
", .cfi_startproc
pacisp!(), ",
" pacisp!(),
.cfi_window_save "
// Save all callee-saved registers on the stack since we're .cfi_window_save
// assuming they're clobbered as a result of the stack switch. // Save all callee-saved registers on the stack since we're
stp x29, x30, [sp, -16]! // assuming they're clobbered as a result of the stack switch.
stp x20, x19, [sp, -16]! stp x29, x30, [sp, -16]!
stp x22, x21, [sp, -16]! stp x20, x19, [sp, -16]!
stp x24, x23, [sp, -16]! stp x22, x21, [sp, -16]!
stp x26, x25, [sp, -16]! stp x24, x23, [sp, -16]!
stp x28, x27, [sp, -16]! stp x26, x25, [sp, -16]!
stp d9, d8, [sp, -16]! stp x28, x27, [sp, -16]!
stp d11, d10, [sp, -16]! stp d9, d8, [sp, -16]!
stp d13, d12, [sp, -16]! stp d11, d10, [sp, -16]!
stp d15, d14, [sp, -16]! stp d13, d12, [sp, -16]!
stp d15, d14, [sp, -16]!
// Load our previously saved stack pointer to resume to, and save // Load our previously saved stack pointer to resume to, and save
// off our current stack pointer on where to come back to // off our current stack pointer on where to come back to
// eventually. // eventually.
ldr x8, [x0, -0x10] ldr x8, [x0, -0x10]
mov x9, sp mov x9, sp
str x9, [x0, -0x10] str x9, [x0, -0x10]
// Switch to the new stack and restore all our callee-saved // Switch to the new stack and restore all our callee-saved
// registers after the switch and return to our new stack. // registers after the switch and return to our new stack.
mov sp, x8 mov sp, x8
ldp d15, d14, [sp], 16 ldp d15, d14, [sp], 16
ldp d13, d12, [sp], 16 ldp d13, d12, [sp], 16
ldp d11, d10, [sp], 16 ldp d11, d10, [sp], 16
ldp d9, d8, [sp], 16 ldp d9, d8, [sp], 16
ldp x28, x27, [sp], 16 ldp x28, x27, [sp], 16
ldp x26, x25, [sp], 16 ldp x26, x25, [sp], 16
ldp x24, x23, [sp], 16 ldp x24, x23, [sp], 16
ldp x22, x21, [sp], 16 ldp x22, x21, [sp], 16
ldp x20, x19, [sp], 16 ldp x20, x19, [sp], 16
ldp x29, x30, [sp], 16 ldp x29, x30, [sp], 16
", ",
autisp!(), autisp!(),
" "
.cfi_window_save .cfi_window_save
ret ret
.cfi_endproc .cfi_endproc
", ",
),
); );
// fn( // fn(
@@ -112,26 +115,29 @@ asm_func!(
#[rustfmt::skip] #[rustfmt::skip]
asm_func!( asm_func!(
"wasmtime_fiber_init", "wasmtime_fiber_init",
" concat!(
.cfi_startproc "
hint #34 // bti c .cfi_startproc
sub x16, x0, #16 hint #34 // bti c
adrp x17, ", sym_adrp!("wasmtime_fiber_start"), " sub x16, x0, #16
add x17, x17, ", sym_add!("wasmtime_fiber_start"), " adrp x17, ", sym_adrp!("{fiber}"), "
", add x17, x17, ", sym_add!("{fiber}"), "
paci1716!(), ",
" paci1716!(),
str x17, [x16, -0x8] // x17 => lr "
str x0, [x16, -0x18] // x0 => x19 str x17, [x16, -0x8] // x17 => lr
stp x2, x1, [x0, -0x38] // x1 => x20, x2 => x21 str x0, [x16, -0x18] // x0 => x19
stp x2, x1, [x0, -0x38] // x1 => x20, x2 => x21
// `wasmtime_fiber_switch` has an 0xa0 byte stack, and we add 0x10 more for // `wasmtime_fiber_switch` has an 0xa0 byte stack, and we add 0x10 more for
// the original reserved 16 bytes. // the original reserved 16 bytes.
add x8, x0, -0xb0 add x8, x0, -0xb0
str x8, [x0, -0x10] str x8, [x0, -0x10]
ret ret
.cfi_endproc .cfi_endproc
", ",
),
fiber = sym wasmtime_fiber_start,
); );
// See the x86_64 file for more commentary on what these CFI directives are // See the x86_64 file for more commentary on what these CFI directives are

View File

@@ -92,17 +92,18 @@ asm_func!(
asm_func!( asm_func!(
"wasmtime_fiber_init", "wasmtime_fiber_init",
" "
lla t0,wasmtime_fiber_start lla t0,{}
sd t0,-0x18(a0) // ra,first should be wasmtime_fiber_start. sd t0,-0x18(a0) // ra,first should be wasmtime_fiber_start.
sd a0,-0x20(a0) // fp pointer. sd a0,-0x20(a0) // fp pointer.
sd a1,-0x28(a0) // entry_point will load to s1. sd a1,-0x28(a0) // entry_point will load to s1.
sd a2,-0x30(a0) // entry_arg0 will load to s2. sd a2,-0x30(a0) // entry_arg0 will load to s2.
// //
addi t0,a0,-0xe0 addi t0,a0,-0xe0
sd t0,-0x10(a0) sd t0,-0x10(a0)
ret ret
", ",
sym super::wasmtime_fiber_start,
); );
asm_func!( asm_func!(
@@ -118,8 +119,8 @@ asm_func!(
0x06, /* DW_OP_deref */ \ 0x06, /* DW_OP_deref */ \
0x08, 0xd0 , /* DW_OP_const1u 0xc8 */ \ 0x08, 0xd0 , /* DW_OP_const1u 0xc8 */ \
0x22 /* DW_OP_plus */ 0x22 /* DW_OP_plus */
.cfi_rel_offset ra,-0x8 .cfi_rel_offset ra,-0x8
.cfi_rel_offset fp,-0x10 .cfi_rel_offset fp,-0x10
.cfi_rel_offset s1,-0x18 .cfi_rel_offset s1,-0x18

View File

@@ -5,7 +5,7 @@
// all the other bits. Documentation tries to reference various bits here and // all the other bits. Documentation tries to reference various bits here and
// there but try to make sure to read over everything before tweaking things! // there but try to make sure to read over everything before tweaking things!
use wasmtime_asm_macros::{asm_func, asm_sym}; use wasmtime_asm_macros::asm_func;
// fn(top_of_stack(rdi): *mut u8) // fn(top_of_stack(rdi): *mut u8)
asm_func!( asm_func!(
@@ -58,7 +58,7 @@ asm_func!(
// //
// The first 16 bytes of stack are reserved for metadata, so we start // The first 16 bytes of stack are reserved for metadata, so we start
// storing values beneath that. // storing values beneath that.
lea rax, ", asm_sym!("wasmtime_fiber_start"), "[rip] lea rax, {start}[rip]
mov -0x18[rdi], rax mov -0x18[rdi], rax
mov -0x20[rdi], rdi // loaded into rbp during switch mov -0x20[rdi], rdi // loaded into rbp during switch
mov -0x28[rdi], rsi // loaded into rbx during switch mov -0x28[rdi], rsi // loaded into rbx during switch
@@ -73,6 +73,7 @@ asm_func!(
mov -0x10[rdi], rax mov -0x10[rdi], rax
ret ret
", ",
start = sym super::wasmtime_fiber_start,
); );
// This is a pretty special function that has no real signature. Its use is to // This is a pretty special function that has no real signature. Its use is to

View File

@@ -104,7 +104,12 @@ pub mod trampolines {
// This will delegate to the outer module to the actual // This will delegate to the outer module to the actual
// implementation and automatically perform `catch_unwind` along // implementation and automatically perform `catch_unwind` along
// with conversion of the return value in the face of traps. // with conversion of the return value in the face of traps.
#[no_mangle] //
// Note that rust targets which support `global_asm!` can use
// the `sym` operator to get the symbol here, but other targets
// like s390x need to use outlined assembly files which requires
// `no_mangle`.
#[cfg_attr(target_arch = "s390x", no_mangle)]
unsafe extern "C" fn [<impl_ $name>]( unsafe extern "C" fn [<impl_ $name>](
vmctx : *mut VMContext, vmctx : *mut VMContext,
$( $pname : libcall!(@ty $param), )* $( $pname : libcall!(@ty $param), )*

View File

@@ -112,10 +112,11 @@ macro_rules! wasm_to_libcall_trampoline {
stur lr, [x9, #32] stur lr, [x9, #32]
// Tail call to the actual implementation of this libcall. // Tail call to the actual implementation of this libcall.
b ", wasmtime_asm_macros::asm_sym!(stringify!($libcall_impl)), " b {}
.cfi_endproc .cfi_endproc
" ",
sym $libcall_impl
); );
}; };
} }

View File

@@ -23,7 +23,7 @@ asm_func!(
// Store the last Wasm SP into the `last_wasm_entry_sp` in the limits, if this // Store the last Wasm SP into the `last_wasm_entry_sp` in the limits, if this
// was core Wasm, otherwise store an invalid sentinal value. // was core Wasm, otherwise store an invalid sentinal value.
sd t1,40(t0) sd t1,40(t0)
ld t0,16(a1) ld t0,16(a1)
jr t0 jr t0
@@ -95,23 +95,26 @@ macro_rules! wasm_to_libcall_trampoline {
($libcall:ident ; $libcall_impl:ident) => { ($libcall:ident ; $libcall_impl:ident) => {
wasmtime_asm_macros::asm_func!( wasmtime_asm_macros::asm_func!(
stringify!($libcall), stringify!($libcall),
" concat!(
.cfi_startproc "
.cfi_startproc
// Load the pointer to `VMRuntimeLimits` in `t0`. // Load the pointer to `VMRuntimeLimits` in `t0`.
ld t0, 8(a0) ld t0, 8(a0)
// Store the last Wasm FP into the `last_wasm_exit_fp` in the limits. // Store the last Wasm FP into the `last_wasm_exit_fp` in the limits.
sd fp, 24(t0) sd fp, 24(t0)
// Store the last Wasm PC into the `last_wasm_exit_pc` in the limits. // Store the last Wasm PC into the `last_wasm_exit_pc` in the limits.
sd ra, 32(t0) sd ra, 32(t0)
// Tail call to the actual implementation of this libcall. // Tail call to the actual implementation of this libcall.
j ", wasmtime_asm_macros::asm_sym!(stringify!($libcall_impl)), " j {}
.cfi_endproc .cfi_endproc
" ",
),
sym $libcall_impl,
); );
}; };
} }

View File

@@ -22,27 +22,29 @@ cfg_if::cfg_if! {
#[rustfmt::skip] #[rustfmt::skip]
asm_func!( asm_func!(
"host_to_wasm_trampoline", "host_to_wasm_trampoline",
" concat!(
.cfi_startproc simple "
.cfi_def_cfa_offset 0 .cfi_startproc simple
.cfi_def_cfa_offset 0
// Load the pointer to `VMRuntimeLimits` in `scratch0`. // Load the pointer to `VMRuntimeLimits` in `scratch0`.
mov ", scratch0!(), ", 8[", arg1!(), "] mov ", scratch0!(), ", 8[", arg1!(), "]
// Check to see if this is a core `VMContext` (MAGIC == 'core'). // Check to see if this is a core `VMContext` (MAGIC == 'core').
cmp DWORD PTR [", arg0!(), "], 0x65726f63 cmp DWORD PTR [", arg0!(), "], 0x65726f63
// Store the last Wasm SP into the `last_wasm_entry_sp` in the limits, if this // Store the last Wasm SP into the `last_wasm_entry_sp` in the limits, if this
// was core Wasm, otherwise store an invalid sentinal value. // was core Wasm, otherwise store an invalid sentinal value.
mov ", scratch1!(), ", -1 mov ", scratch1!(), ", -1
cmove ", scratch1!(), ", rsp cmove ", scratch1!(), ", rsp
mov 40[", scratch0!(), "], ", scratch1!(), " mov 40[", scratch0!(), "], ", scratch1!(), "
// Tail call to the callee function pointer in the vmctx. // Tail call to the callee function pointer in the vmctx.
jmp 16[", arg1!(), "] jmp 16[", arg1!(), "]
.cfi_endproc .cfi_endproc
", ",
),
); );
#[cfg(test)] #[cfg(test)]
@@ -64,28 +66,30 @@ mod host_to_wasm_trampoline_offsets_tests {
#[rustfmt::skip] #[rustfmt::skip]
asm_func!( asm_func!(
"wasm_to_host_trampoline", "wasm_to_host_trampoline",
" concat!(
.cfi_startproc simple "
.cfi_def_cfa_offset 0 .cfi_startproc simple
.cfi_def_cfa_offset 0
// Load the pointer to `VMRuntimeLimits` in `scratch0`. // Load the pointer to `VMRuntimeLimits` in `scratch0`.
mov ", scratch0!(), ", 8[", arg1!(), "] mov ", scratch0!(), ", 8[", arg1!(), "]
// Store the last Wasm FP into the `last_wasm_exit_fp` in the limits. // Store the last Wasm FP into the `last_wasm_exit_fp` in the limits.
mov 24[", scratch0!(), "], rbp mov 24[", scratch0!(), "], rbp
// Store the last Wasm PC into the `last_wasm_exit_pc` in the limits. // Store the last Wasm PC into the `last_wasm_exit_pc` in the limits.
mov ", scratch1!(), ", [rsp] mov ", scratch1!(), ", [rsp]
mov 32[", scratch0!(), "], ", scratch1!(), " mov 32[", scratch0!(), "], ", scratch1!(), "
// Tail call to the actual host function. // Tail call to the actual host function.
// //
// This *must* be a tail call so that we do not push to the stack and mess // This *must* be a tail call so that we do not push to the stack and mess
// up the offsets of stack arguments (if any). // up the offsets of stack arguments (if any).
jmp 8[", arg0!(), "] jmp 8[", arg0!(), "]
.cfi_endproc .cfi_endproc
", ",
),
); );
#[cfg(test)] #[cfg(test)]
@@ -111,25 +115,28 @@ macro_rules! wasm_to_libcall_trampoline {
($libcall:ident ; $libcall_impl:ident) => { ($libcall:ident ; $libcall_impl:ident) => {
wasmtime_asm_macros::asm_func!( wasmtime_asm_macros::asm_func!(
stringify!($libcall), stringify!($libcall),
" concat!(
.cfi_startproc simple "
.cfi_def_cfa_offset 0 .cfi_startproc simple
.cfi_def_cfa_offset 0
// Load the pointer to `VMRuntimeLimits` in `", scratch0!(), "`. // Load the pointer to `VMRuntimeLimits` in `", scratch0!(), "`.
mov ", scratch0!(), ", 8[", arg0!(), "] mov ", scratch0!(), ", 8[", arg0!(), "]
// Store the last Wasm FP into the `last_wasm_exit_fp` in the limits. // Store the last Wasm FP into the `last_wasm_exit_fp` in the limits.
mov 24[", scratch0!(), "], rbp mov 24[", scratch0!(), "], rbp
// Store the last Wasm PC into the `last_wasm_exit_pc` in the limits. // Store the last Wasm PC into the `last_wasm_exit_pc` in the limits.
mov ", scratch1!(), ", [rsp] mov ", scratch1!(), ", [rsp]
mov 32[", scratch0!(), "], ", scratch1!(), " mov 32[", scratch0!(), "], ", scratch1!(), "
// Tail call to the actual implementation of this libcall. // Tail call to the actual implementation of this libcall.
jmp ", wasmtime_asm_macros::asm_sym!(stringify!($libcall_impl)), " jmp {}
.cfi_endproc .cfi_endproc
", ",
),
sym $libcall_impl
); );
}; };
} }

View File

@@ -8,6 +8,7 @@ license = "Apache-2.0 WITH LLVM-exception"
repository = "https://github.com/bytecodealliance/wasmtime" repository = "https://github.com/bytecodealliance/wasmtime"
readme = "README.md" readme = "README.md"
edition.workspace = true edition.workspace = true
rust-version.workspace = true
[package.metadata.docs.rs] [package.metadata.docs.rs]
rustdoc-args = ["--cfg", "nightlydoc"] rustdoc-args = ["--cfg", "nightlydoc"]