This switches `DCHECK` and `FATAL` as well. The goal is to reduce the code size impact of these assertions so that we can keep more of them enabled. Currently, the largest cost I see from `CHECK` is not the actual check or the cold code itself, but actually the failure to inline trivial functions due to the presence of the cold code. This means that our goal isn't to reduce apparent code size in the final binary but the LLVM IR cost assessed for these routines in the inliner, which closely correlates with code size but is a bit different. As discussed in #4283, experimentation shows that a single function call with a minimal number of arguments is the lowest cost model for these. This is easily achieved with a format-string API that internally uses `llvm::formatv`. This PR is essentially the `CHECK` version of #4283. However, the check macros are substantially harder to make work with both format strings and streaming because they also take a condition. Also, unexpectedly, I was very successful at devising a regular expression based automated rewrite from the streaming to the format string form with only low 10s of manual fixes. This includes compacting strings broken up across lines, etc. Given how well that went, I've prepared this PR which just directly switches to the format string API and migrate everything to use it. One nice side-effect is that the format string approach ends up greatly simplifying the implementation here as well. This is ... *shockingly* effective. Parsing speeds up by more than 3% with just this change. And checking speeds up by **8%** with this change alone: ``` BM_CompileAPIFileDenseDecls<Phase::Parse>/256 86.3µs ± 1% 82.9µs ± 1% -3.94% (p=0.000 n=17+19) BM_CompileAPIFileDenseDecls<Phase::Parse>/1024 431µs ± 1% 415µs ± 1% -3.76% (p=0.000 n=18+19) BM_CompileAPIFileDenseDecls<Phase::Parse>/4096 1.77ms ± 1% 1.71ms ± 1% -3.18% (p=0.000 n=18+19) BM_CompileAPIFileDenseDecls<Phase::Parse>/16384 7.44ms ± 1% 7.17ms ± 2% -3.56% (p=0.000 n=18+20) BM_CompileAPIFileDenseDecls<Phase::Parse>/65536 30.7ms ± 1% 29.7ms ± 1% -3.15% (p=0.000 n=18+20) BM_CompileAPIFileDenseDecls<Phase::Parse>/262144 131ms ± 1% 127ms ± 1% -2.81% (p=0.000 n=18+18) BM_CompileAPIFileDenseDecls<Phase::Check>/256 878µs ± 2% 800µs ± 1% -8.91% (p=0.000 n=19+20) BM_CompileAPIFileDenseDecls<Phase::Check>/1024 1.88ms ± 2% 1.72ms ± 1% -8.56% (p=0.000 n=19+20) BM_CompileAPIFileDenseDecls<Phase::Check>/4096 5.78ms ± 2% 5.28ms ± 1% -8.70% (p=0.000 n=20+18) BM_CompileAPIFileDenseDecls<Phase::Check>/16384 21.9ms ± 1% 20.1ms ± 1% -8.02% (p=0.000 n=18+20) BM_CompileAPIFileDenseDecls<Phase::Check>/65536 90.4ms ± 2% 83.1ms ± 1% -8.04% (p=0.000 n=19+20) BM_CompileAPIFileDenseDecls<Phase::Check>/262144 381ms ± 2% 352ms ± 1% -7.79% (p=0.000 n=19+19) ``` --------- Co-authored-by: Richard Smith <richard@metafoo.co.uk> Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com>
Explorer structured fuzzer
Overview
Fuzz testing is based on generating a large amount of random inputs for a software component in order to trigger bugs and unexpected behavior. Basic fuzzing uses randomly generated arrays of bytes as inputs, which works great for some applications but is problematic for testing the logic that operates on highly structured data, as most random inputs are immediately rejected as invalid before any interesting parts of the code get a chance to run.
Structured fuzzing addresses this issue by ensuring the randomly generated data is itself structured, and as such has a high chance of presenting a valid input.
explorer_fuzzer is a structured fuzzer based on
libprotobuf-mutator, which is a
library to randomly mutate
protobuffers.
The input to the fuzzer is an instance of Carbon::Fuzzing::Carbon proto
randomly generated by the libprotobuf-mutator framework. explorer_fuzzer
converts the proto to a Carbon source code string, and tries to parse and
execute the code using explorer implementation.
Fuzzer data format
libprotobuf-mutator supports fuzzer inputs in either text or binary protocol
buffer format. explorer_fuzzer uses text proto format with Carbon proto
message definition in testing/fuzzing/carbon.proto.
Incorporating AST changes into the fuzzer
Fuzzer AST representation in carbon.proto needs to be updated when changes are made to the AST, like adding a new AST node classes or changing relevant data members of existing nodes.
ast_to_proto_test normally should not require direct changes, as tests work off of Carbon test files in testdata.
To incorporate AST changes into fuzzing logic:
-
Add appropriate AST information to carbon.proto. Use existing similar cases as examples.
-
Modify proto_to_carbon.cpp which handles printing of a Carbon proto instance as a Carbon source string. For example, add code to print newly introduced proto fields.
-
Add logic to populate the proto to ast_to_proto.cpp.
-
Make sure
bazel test //explorer/fuzzing:ast_to_proto_testpasses with the new changes.
Running the fuzzer
The fuzzer can be run in 'unit test' mode, where the fuzzer executes on each
input file from the fuzzer_corpus/ folder, or in 'fuzzing' mode, where the
fuzzer will keep generating random inputs and executing the logic on them until
a crash is triggered, or forever in a bug-free program ;).
To run in 'unit test' mode:
bazel test //explorer/fuzzing:explorer_fuzzer
To run in 'fuzzing' mode:
bazel build --config=fuzzer //explorer/fuzzing:explorer_fuzzer.full_corpus
bazel-bin/explorer/fuzzing/explorer_fuzzer.full_corpus
Investigating a crash
Typically it's going to be easiest to run explorer on the problematic carbon program directly. You can do this with:
# Convert a specific fuzzer test to a source file
bazel run //testing/fuzzing:proto_to_carbon -- explorer/fuzzing/fuzzer_corpus/abcd1234 > crash.carbon
# Or convert the crash to a source file.
bazel run //testing/fuzzing:proto_to_carbon -- /tmp/crash.textproto > crash.carbon
# Run explorer on the crash.
bazel run //explorer -- crash.carbon
It's also possible to run the fuzzer on a single input:
bazel-bin/explorer/fuzzing/explorer_fuzzer.full_corpus /tmp/crash.textproto
Generating new fuzzer corpus entries
The ability of the fuzzing framework to generate 'interesting' inputs can be
improved by providing 'seed' inputs known as the fuzzer corpus. The inputs need
to be a Fuzzing::Carbon text proto.
To generate a text proto from Carbon source:
bazel run //explorer/fuzzing:ast_to_proto -- /tmp/crash.carbon > crash.textproto