Some of the notable differences:
* libFuzzer is fully integrated with the rest of LLVM (sanitizers and coverage instrumentation)
* AFL supports both in-process and out-of-process fuzzing, while libFuzzer is strictly in-process
* AFL supports more flavors of coverage instrumentation (but for most use cases when the source code is available it doesn't matter much)
* libFuzzer supports custom mutators, allowing
structure-aware-fuzzing, but IIRC some clones of AFL support that too,
* There are lots of subtle difference in various heuristics.
* The two engines have very different user interface (command lines parameters, output, etc) and are plug-compatible in one directly:
anything that can be fuzzed with libFuzzer can also be fuzzed by AFL.
(But since libFuzzer doesn't support out-of-process fuzzing the opposite direction may not work)
there are differences wrt availability on specific platforms.