Skip to content

Commit c905978

Browse files
committed
fuzz: Replace generated C++ with a source file
This is easier to read and allows for some cleanup. As part of this, switch from passing packed structures back and forth to passing integers as separate arguments to be more certain about FFI-safety, rather than passing a Rust enum. This will also allow us to fuzz with rounding modes and verifying status in the future.
1 parent 7916bea commit c905978

5 files changed

Lines changed: 285 additions & 187 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
/target
22
/Cargo.lock
3+
compile_commands.json
4+
.cache
35

46
# Fuzzing data/state.
57
/fuzz/in*

fuzz/build.rs

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,25 @@ use std::process::{Command, ExitCode};
66

77
mod ops;
88

9+
// NB: Any new symbols exported from the C++ source file need to be listed here,
10+
// everything else will get pruned.
11+
const CXX_EXPORTED_SYMBOLS: &[&str] = &[
12+
"cxx_apf_eval_op_brainf16",
13+
"cxx_apf_eval_op_ieee16",
14+
"cxx_apf_eval_op_ieee32",
15+
"cxx_apf_eval_op_ieee64",
16+
"cxx_apf_eval_op_ieee128",
17+
"cxx_apf_eval_op_ppcdoubledouble",
18+
"cxx_apf_eval_op_f8e5m2",
19+
"cxx_apf_eval_op_f8e4m3fn",
20+
"cxx_apf_eval_op_x87_f80",
21+
];
22+
923
fn main() -> io::Result<ExitCode> {
10-
// HACK(eddyb) disable the default of re-running the build script on *any*
11-
// change to *the entire source tree* (i.e. the default is roughly `./`).
12-
println!("cargo:rerun-if-changed=build.rs");
24+
// Only rerun if sources run by the fuzzer change
25+
println!("cargo::rerun-if-changed=build.rs");
26+
println!("cargo::rerun-if-changed=cxx");
27+
println!("cargo::rerun-if-changed=src/apf_fuzz.cpp");
1328

1429
// NOTE(eddyb) `rustc_apfloat`'s own `build.rs` validated the version string.
1530
let (_, llvm_commit_hash) = env!("CARGO_PKG_VERSION").split_once("+llvm-").unwrap();
@@ -25,12 +40,6 @@ fn main() -> io::Result<ExitCode> {
2540
return Ok(ExitCode::SUCCESS);
2641
}
2742

28-
let mut cxx_exported_symbols = vec![];
29-
fs::write(
30-
out_dir.join("cxx_apf_fuzz.cpp"),
31-
ops::generate_cxx(&mut cxx_exported_symbols),
32-
)?;
33-
3443
let manifest_dir =
3544
PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR unset"));
3645
let target_dir = env::var_os("CARGO_TARGET_DIR")
@@ -91,7 +100,7 @@ fn main() -> io::Result<ExitCode> {
91100
.arg("--include")
92101
.arg(manifest_dir.join("cxx/fuzz_unity_build.cpp"))
93102
.arg("--include")
94-
.arg(out_dir.join("cxx_apf_fuzz.cpp"))
103+
.arg(manifest_dir.join("src/apf_fuzz.cpp"))
95104
.args(["-c", "-emit-llvm", "-o"])
96105
.arg(&bc_out);
97106
println!("+ {clang:?}");
@@ -101,7 +110,7 @@ fn main() -> io::Result<ExitCode> {
101110
let mut opt = Command::new("opt");
102111
opt.env_clear()
103112
.arg("--internalize-public-api-list")
104-
.arg(cxx_exported_symbols.join(","))
113+
.arg(CXX_EXPORTED_SYMBOLS.join(","))
105114
.arg(&bc_out)
106115
.arg("-o")
107116
.arg(&bc_opt_out);

fuzz/ops.rs

Lines changed: 4 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@
88
99
// HACK(eddyb) newtypes to make it easy to tell apart Rust vs C++ specifics.
1010
struct Rust<T>(T);
11-
struct Cxx<T>(T);
1211

1312
use self::OpKind::*;
1413
enum OpKind {
1514
Unary(char),
1615
Binary(char),
17-
Ternary(Rust<&'static str>, Cxx<&'static str>),
16+
Ternary(Rust<&'static str>),
1817

1918
// HACK(eddyb) all other ops have floating-point inputs *and* outputs, so
2019
// the easiest way to fuzz conversions from/to other types, even if it won't
@@ -58,7 +57,7 @@ const OPS: &[(&str, OpKind)] = &[
5857
("Div", Binary('/')),
5958
("Rem", Binary('%')),
6059
// Ternary (`(F, F) -> F`) ops.
61-
("MulAdd", Ternary(Rust("mul_add"), Cxx("fusedMultiplyAdd"))),
60+
("MulAdd", Ternary(Rust("mul_add"))),
6261
// Roundtrip (`F -> T -> F`) ops.
6362
("FToI128ToF", Roundtrip(Type::SInt(128))),
6463
("FToU128ToF", Roundtrip(Type::UInt(128))),
@@ -151,7 +150,7 @@ impl<HF> FuzzOp<HF>
151150
let expr = match kind {
152151
Unary(op) => format!("{op}{}", inputs[0]),
153152
Binary(op) => format!("{} {op} {}", inputs[0], inputs[1]),
154-
Ternary(Rust(method), _) => {
153+
Ternary(Rust(method)) => {
155154
format!("{}.{method}({}, {})", inputs[0], inputs[1], inputs[2])
156155
}
157156
Roundtrip(ty) => format!(
@@ -186,7 +185,7 @@ impl<F> FuzzOp<F>
186185
let expr = match kind {
187186
Unary(op) => format!("{op}{}", inputs[0]),
188187
Binary(op) => format!("({} {op} {}).value", inputs[0], inputs[1]),
189-
Ternary(Rust(method), _) => {
188+
Ternary(Rust(method)) => {
190189
format!("{}.{method}({}).value", inputs[0], inputs[1..].join(", "))
191190
}
192191
Roundtrip(ty @ (Type::SInt(_) | Type::UInt(_))) => {
@@ -224,159 +223,3 @@ impl<F> FuzzOp<F>
224223
}
225224
}"
226225
}
227-
228-
pub fn generate_cxx(exported_symbols: &mut Vec<String>) -> String {
229-
String::new()
230-
+ r#"
231-
#include <array>
232-
#include <llvm/ADT/APFloat.h>
233-
234-
using namespace llvm;
235-
236-
#pragma clang diagnostic error "-Wall"
237-
#pragma clang diagnostic error "-Wextra"
238-
#pragma clang diagnostic error "-Wunknown-attributes"
239-
240-
using uint128_t = __uint128_t;
241-
242-
template<typename F>
243-
struct FuzzOp {
244-
enum : uint8_t {"#
245-
+ &all_ops_map_concat(|tag, name, _kind| {
246-
format!(
247-
"
248-
{name} = {tag},"
249-
)
250-
})
251-
+ "
252-
} tag;
253-
F a, b, c;
254-
255-
F eval() const {
256-
257-
// HACK(eddyb) 'scratch' variables used by expressions below.
258-
APFloat r(0.0);
259-
APSInt i;
260-
bool scratch_bool;
261-
262-
switch(tag) {
263-
"
264-
+ &all_ops_map_concat(|_tag, name, kind| {
265-
let inputs = kind.inputs(&["a.to_apf()", "b.to_apf()", "c.to_apf()"]);
266-
let expr = match kind {
267-
// HACK(eddyb) `APFloat` doesn't overload `operator%`, so we have
268-
// to go through the `mod` method instead.
269-
Binary('%') => format!("((r = {}), r.mod({}), r)", inputs[0], inputs[1]),
270-
271-
Unary(op) => format!("{op}{}", inputs[0]),
272-
Binary(op) => format!("{} {op} {}", inputs[0], inputs[1]),
273-
274-
Ternary(_, Cxx(method)) => {
275-
format!(
276-
"((r = {}), r.{method}({}, {}, APFloat::rmNearestTiesToEven), r)",
277-
inputs[0], inputs[1], inputs[2]
278-
)
279-
}
280-
281-
Roundtrip(ty @ (Type::SInt(_) | Type::UInt(_))) => {
282-
let (w, signed) = match ty {
283-
Type::SInt(w) => (w, true),
284-
Type::UInt(w) => (w, false),
285-
Type::Float(_) => unreachable!(),
286-
};
287-
format!(
288-
"((r = {}),
289-
(i = APSInt({w}, !{signed})),
290-
r.convertToInteger(i, APFloat::rmTowardZero, &scratch_bool),
291-
r.convertFromAPInt(i, {signed}, APFloat::rmNearestTiesToEven),
292-
r)",
293-
inputs[0]
294-
)
295-
}
296-
Roundtrip(Type::Float(w)) => {
297-
let cxx_apf_semantics = match w {
298-
32 => "APFloat::IEEEsingle()",
299-
64 => "APFloat::IEEEdouble()",
300-
_ => unreachable!(),
301-
};
302-
format!(
303-
"((r = {input}),
304-
r.convert({cxx_apf_semantics}, APFloat::rmNearestTiesToEven, &scratch_bool),
305-
r.convert({input}.getSemantics(), APFloat::rmNearestTiesToEven, &scratch_bool),
306-
r)",
307-
input = inputs[0]
308-
)
309-
}
310-
};
311-
format!(
312-
"
313-
case {name}: return F::from_apf({expr});"
314-
)
315-
})
316-
+ "
317-
}
318-
}
319-
};
320-
" + &[
321-
(16, "IEEEhalf"),
322-
(32, "IEEEsingle"),
323-
(64, "IEEEdouble"),
324-
(128, "IEEEquad"),
325-
(16, "BFloat"),
326-
(8, "Float8E5M2"),
327-
(8, "Float8E4M3FN"),
328-
(80, "x87DoubleExtended"),
329-
]
330-
.into_iter()
331-
.map(|(w, cxx_apf_semantics): (usize, _)| {
332-
let uint_width = w.next_power_of_two();
333-
let name = match (w, cxx_apf_semantics) {
334-
(16, "BFloat") => "BrainF16".into(),
335-
(8, s) if s.starts_with("Float8") => s.replace("Float8", "F8"),
336-
(80, "x87DoubleExtended") => "X87_F80".into(),
337-
_ => {
338-
assert!(cxx_apf_semantics.starts_with("IEEE"));
339-
format!("IEEE{w}")
340-
}
341-
};
342-
let exported_symbol = format!("cxx_apf_fuzz_eval_op_{}", name.to_ascii_lowercase());
343-
exported_symbols.push(exported_symbol.clone());
344-
let uint = format!("uint{uint_width}_t");
345-
format!(
346-
r#"
347-
struct __attribute__((packed)) {name} {{
348-
{uint} bits;
349-
350-
// HACK(eddyb) these work around `APInt` only being convenient up to 64 bits.
351-
352-
static {name} from_apf(APFloat apf) {{
353-
auto ap_bits = apf.bitcastToAPInt();
354-
assert(ap_bits.getBitWidth() == {w});
355-
356-
{uint} bits = 0;
357-
for(int i = 0; i < {w}; i += APInt::APINT_BITS_PER_WORD)
358-
bits |= static_cast<{uint}>(
359-
ap_bits.getRawData()[i / APInt::APINT_BITS_PER_WORD]
360-
) << i;
361-
return {{ bits }};
362-
}}
363-
364-
APFloat to_apf() const {{
365-
std::array<
366-
APInt::WordType,
367-
({w} + APInt::APINT_BITS_PER_WORD - 1) / APInt::APINT_BITS_PER_WORD
368-
> words;
369-
for(int i = 0; i < {w}; i += APInt::APINT_BITS_PER_WORD)
370-
words[i / APInt::APINT_BITS_PER_WORD] = bits >> i;
371-
return APFloat(APFloat::{cxx_apf_semantics}(), APInt({w}, words));
372-
}}
373-
}};
374-
extern "C" {{
375-
void {exported_symbol}({name} *out, FuzzOp<{name}> op) {{
376-
*out = op.eval();
377-
}}
378-
}}"#
379-
)
380-
})
381-
.collect::<String>()
382-
}

0 commit comments

Comments
 (0)