diff --git a/tools/nvidia_driver_differ/BUILD b/tools/nvidia_driver_differ/BUILD index 6ddc05470..85913562d 100644 --- a/tools/nvidia_driver_differ/BUILD +++ b/tools/nvidia_driver_differ/BUILD @@ -12,8 +12,11 @@ cc_binary( "driver_ast_parser.h", ], deps = [ + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/strings", "@llvm-project//clang:ast", "@llvm-project//clang:ast_matchers", + "@llvm-project//clang:basic", "@llvm-project//clang:tooling", "@llvm-project//llvm:Support", "@nlohmann_json//:json", diff --git a/tools/nvidia_driver_differ/driver_ast_parser.cc b/tools/nvidia_driver_differ/driver_ast_parser.cc index 8ca8d4189..fd60d5a1e 100644 --- a/tools/nvidia_driver_differ/driver_ast_parser.cc +++ b/tools/nvidia_driver_differ/driver_ast_parser.cc @@ -14,16 +14,24 @@ #include "tools/nvidia_driver_differ/driver_ast_parser.h" +#include + +#include #include #include #include +#include "absl/container/flat_hash_set.h" +#include "absl/strings/str_cat.h" #include "nlohmann/json.hpp" #include "clang/include/clang/AST/Decl.h" +#include "clang/include/clang/AST/Type.h" #include "clang/include/clang/ASTMatchers/ASTMatchFinder.h" #include "clang/include/clang/ASTMatchers/ASTMatchers.h" +#include "clang/include/clang/Basic/SourceManager.h" #include "clang/include/clang/Tooling/CommonOptionsParser.h" #include "clang/include/clang/Tooling/Tooling.h" +#include "llvm/include/llvm/Support/Casting.h" #include "llvm/include/llvm/Support/CommandLine.h" #include "llvm/include/llvm/Support/raw_ostream.h" @@ -40,7 +48,9 @@ using clang::ast_matchers::MatchFinder; using json = nlohmann::json; struct DriverStructReporter : public MatchFinder::MatchCallback { - json StructDefinitions; + json RecordDefinitions; + json TypeAliases; + absl::flat_hash_set ParsedTypes; auto get_struct_matcher(std::string struct_name) { // Nvidia's driver typedefs all their struct. We search for the @@ -61,7 +71,7 @@ struct DriverStructReporter : public MatchFinder::MatchCallback { result.Nodes.getNodeAs("typedef_decl"); if (typedef_decl == nullptr) { std::cerr << "Unable to find typedef decl\n"; - return; + exit(1); } const auto *struct_decl = @@ -69,28 +79,88 @@ struct DriverStructReporter : public MatchFinder::MatchCallback { if (struct_decl == nullptr) { std::cerr << "Unable to find struct decl for " << typedef_decl->getNameAsString() << "\n"; - return; - } - - // Add struct definition to json. - // TODO(b/347796680): Consider improvements: - // Store alignment attributes as well? - // Make recursive? Relevant for recursive structs not defined in nvproxy, - // or unnamed structs/unions. - // Handle anonymous names? - json fields = json::array(); - for (const auto *field : struct_decl->fields()) { - fields.push_back( - json::object({{"name", field->getNameAsString()}, - {"type", field->getType().getAsString()}})); + exit(1); } std::string name = typedef_decl->getNameAsString(); - std::string source = typedef_decl->getLocation().printToString( - result.Context->getSourceManager()); + const auto &sm = result.Context->getSourceManager(); + add_type_definition(result.Context->getTypeDeclType(struct_decl), name, sm); + } - StructDefinitions[name] = - json::object({{"fields", fields}, {"source", source}}); + // Adds the type definition of `type` to either `RecordDefinitions` or + // `TypeAliases`, mapped to `name`. Recursively adds the type definitions + // of any nested types. + void add_type_definition(const clang::QualType &type, const std::string &name, + const clang::SourceManager &sm) { + // We've already handled this type. + if (ParsedTypes.contains(name)) { + return; + } + ParsedTypes.insert(name); + + // We use the canonical type to get past any typedefs. + const auto canonical_type = type.getCanonicalType(); + + if (canonical_type->isRecordType()) { + const auto record_decl = canonical_type->getAsRecordDecl(); + + add_record_definition(record_decl, name, sm); + } else { + TypeAliases[name] = canonical_type.getAsString(); + } + } + + // Adds the type definition of `record_decl` to `RecordDefinitions`, mapped + // to `name`. Recursively adds the type definitions of any nested types. + void add_record_definition(const clang::RecordDecl *record_decl, + const std::string &name, + const clang::SourceManager &sm) { + json fields; + for (const auto *field : record_decl->fields()) { + auto field_type = field->getType(); + + // If this is an array type, save the array size then get the underlying + // element type to recurse on later. + uint64_t array_size = 0; + if (field_type->isConstantArrayType()) { + const auto *CAT = llvm::dyn_cast( + field_type->castAsArrayTypeUnsafe()); + if (CAT == nullptr) { + std::cerr << "Unable to cast to ConstantArrayType\n"; + exit(1); + } + array_size = CAT->getSize().getZExtValue(); + field_type = CAT->getElementType(); + } + + // Get the type name. If the type is not named, we use the record name + // and field name to create a fake type name. + std::string base_type_name; + if (field_type->hasUnnamedOrLocalType()) { + base_type_name = + absl::StrCat(name, "::", field->getNameAsString(), "_t"); + } else { + base_type_name = field_type.getAsString(); + } + + // If this is an array type, add the array size to the type name. + std::string field_type_name = base_type_name; + if (array_size > 0) { + absl::StrAppend(&field_type_name, "[", array_size, "]"); + } + + // Add field to json. + fields.push_back(json::object( + {{"name", field->getNameAsString()}, {"type", field_type_name}})); + + // Recurse on the field type. + add_type_definition(field_type, base_type_name, sm); + } + + std::string source = record_decl->getLocation().printToString(sm); + + RecordDefinitions[name] = + json::object({{"source", source}, {"fields", fields}}); } }; @@ -108,7 +178,7 @@ static llvm::cl::opt StructNames( static llvm::cl::opt OutputFile( "output", "o", - llvm::cl::desc("Path to the output file for the parsed structs. " + llvm::cl::desc("Path to the output file for the parsed type definitions. " "By default, will print to stdout."), llvm::cl::cat(DriverASTParserCategory)); @@ -145,7 +215,8 @@ int main(int argc, const char **argv) { int ret = Tool.run(clang::tooling::newFrontendActionFactory(&finder).get()); // Print output. - json output = json::object({{"structs", reporter.StructDefinitions}}); + json output = json::object({{"records", reporter.RecordDefinitions}, + {"aliases", reporter.TypeAliases}}); if (OutputFile.empty()) { std::cout << output.dump() << "\n"; } else { diff --git a/tools/nvidia_driver_differ/driver_ast_parser_test.go b/tools/nvidia_driver_differ/driver_ast_parser_test.go index 5c911779a..e61f43816 100644 --- a/tools/nvidia_driver_differ/driver_ast_parser_test.go +++ b/tools/nvidia_driver_differ/driver_ast_parser_test.go @@ -76,35 +76,45 @@ func TestParser(t *testing.T) { t.Fatalf("failed to unmarshal output %s: %v", string(out), err) } expectedOutput := parser.OutputJSON{ - Structs: parser.StructDefs{ - "TestStruct": parser.StructDef{ - Fields: []parser.StructField{ - parser.StructField{ - Name: "a", - Type: "int", - }, - parser.StructField{ - Name: "b", - Type: "int", - }, - parser.StructField{ - Name: "c", - Type: "OtherInt", - }, + Records: parser.RecordDefs{ + "TestStruct": parser.RecordDef{ + Fields: []parser.RecordField{ + parser.RecordField{Name: "a", Type: "int"}, + parser.RecordField{Name: "b", Type: "int"}, + parser.RecordField{Name: "e", Type: "TestStruct::e_t[4]"}, + parser.RecordField{Name: "f", Type: "TestUnion"}, }, - Source: "test_struct.cc:24:3", + Source: "test_struct.cc:25:16", }, + "TestStruct::e_t": parser.RecordDef{ + Fields: []parser.RecordField{ + parser.RecordField{Name: "c", Type: "OtherInt"}, + parser.RecordField{Name: "d", Type: "OtherInt"}, + }, + Source: "test_struct.cc:28:3", + }, + "TestUnion": parser.RecordDef{ + Fields: []parser.RecordField{ + parser.RecordField{Name: "u_a", Type: "int"}, + parser.RecordField{Name: "u_b", Type: "int"}, + }, + Source: "test_struct.cc:20:9", + }, + }, + Aliases: parser.TypeAliases{ + "OtherInt": "int", + "int": "int", }, } - if diff := cmp.Diff(expectedOutput, outputJSON, cmpopts.IgnoreFields(parser.StructDef{}, "Source")); diff != "" { + if diff := cmp.Diff(expectedOutput, outputJSON, cmpopts.IgnoreFields(parser.RecordDef{}, "Source")); diff != "" { t.Fatalf("output mismatch (-want +got):\n%s", diff) } // Only check the source suffix since the absolute path will be different every run. - for name, structDef := range outputJSON.Structs { - if !strings.HasSuffix(structDef.Source, expectedOutput.Structs[name].Source) { - t.Fatalf("source mismatch for %s: should end with %s, got %s", name, expectedOutput.Structs[name].Source, structDef.Source) + for name, structDef := range outputJSON.Records { + if !strings.HasSuffix(structDef.Source, expectedOutput.Records[name].Source) { + t.Fatalf("source mismatch for %s: should end with %s, got %s", name, expectedOutput.Records[name].Source, structDef.Source) } } } diff --git a/tools/nvidia_driver_differ/parser/json_definitions.go b/tools/nvidia_driver_differ/parser/json_definitions.go index e7e1d92a0..2d7b9edd6 100644 --- a/tools/nvidia_driver_differ/parser/json_definitions.go +++ b/tools/nvidia_driver_differ/parser/json_definitions.go @@ -30,36 +30,40 @@ type InputJSON struct { // OutputJSON is the format for the output of driver_ast_parser. type OutputJSON struct { - Structs StructDefs `json:"structs"` + Records RecordDefs `json:"records"` + Aliases TypeAliases `json:"aliases"` } -// StructField represents a field in a struct. -type StructField struct { +// RecordField represents a field in a record (struct or union). +type RecordField struct { Name string Type string } -func (s StructField) String() string { +func (s RecordField) String() string { return fmt.Sprintf("%s %s", s.Type, s.Name) } -// StructDef represents a struct definition. -type StructDef struct { - Fields []StructField +// RecordDef represents the definition of a record (struct or union). +type RecordDef struct { + Fields []RecordField Source string } -// Equals returns true if the two struct definitions are equal. We only +// Equals returns true if the two record definitions are equal. We only // compare the fields, not the source. -func (s StructDef) Equals(other StructDef) bool { +func (s RecordDef) Equals(other RecordDef) bool { return slices.Equal(s.Fields, other.Fields) } -// StructDefs is a map of struct name to struct definition. -type StructDefs map[string]StructDef +// RecordDefs is a map of type names to definitions. +type RecordDefs map[string]RecordDef -// GetStructDiff prints a diff between two struct definitions. -func GetStructDiff(name string, s1, s2 StructDef) string { +// TypeAliases is a map of type aliases to their underlying type. +type TypeAliases map[string]string + +// GetRecordDiff prints a diff between two records. +func GetRecordDiff(name string, s1, s2 RecordDef) string { var b strings.Builder fmt.Fprintf(&b, "--- A: %s\n", s1.Source) fmt.Fprintf(&b, "+++ B: %s\n", s2.Source) diff --git a/tools/nvidia_driver_differ/test_struct.cc b/tools/nvidia_driver_differ/test_struct.cc index d8e854a41..347a51376 100644 --- a/tools/nvidia_driver_differ/test_struct.cc +++ b/tools/nvidia_driver_differ/test_struct.cc @@ -17,8 +17,17 @@ typedef int OtherInt; +typedef union { + int u_a; + int u_b; +} TestUnion; + typedef struct TestStruct { int a; int b; - OtherInt c; + struct { + OtherInt c; + OtherInt d; + } e[4]; + TestUnion f; } TestStruct;