Skip to content

Commit 93cbba7

Browse files
ayushr2gvisor-bot
authored andcommitted
Add support for parsing constants from NVIDIA driver in driver_ast_parser.
The `driver_ast_parser` tool is now able to parse constants alongside with structs. This is done by adding a new `constants` field in the JSON input, and a `constants` field in the JSON output. This will be used later to detect changes in constants between driver versions. Also, the parser's `structs` argument was renamed to `input` to account for both structs and constants. PiperOrigin-RevId: 792677998
1 parent b5a5365 commit 93cbba7

File tree

10 files changed

+140
-78
lines changed

10 files changed

+140
-78
lines changed

g3doc/proposals/nvidia_driver_differ.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,14 +317,15 @@ make things simple, the inputs and outputs can be encoded in JSON. Overall,
317317
interfacing with the parser would go something like this:
318318

319319
```bash
320-
./driver_ast_parser --structs=structs.json source_file.cc
320+
./driver_ast_parser --input=input.json source_file.cc
321321
```
322322

323323
Input:
324324

325325
```json
326326
{
327-
"structs": ["STRUCT", "NAMES", "HERE", ...]
327+
"structs": ["STRUCT", "NAMES", "HERE", ...],
328+
"constants": ["CONSTANT", "NAMES", "HERE", ...]
328329
}
329330
```
330331

@@ -346,6 +347,9 @@ Output:
346347
// All the typedefs found
347348
"aliases": {
348349
"NvHandle": "unsigned int"
350+
},
351+
"constants": {
352+
"CONSTANT_NAME": UINT_VALUE
349353
}
350354
}
351355
```

pkg/sentry/devices/nvproxy/nvproxy_driver_parity_test.go

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ package nvproxy_driver_parity_test
2222
import (
2323
"context"
2424
"fmt"
25-
"os"
2625
"reflect"
2726
"regexp"
2827
"strconv"
@@ -38,25 +37,21 @@ import (
3837
"gvisor.dev/gvisor/tools/nvidia_driver_differ/parser"
3938
)
4039

41-
func createParserRunner(t *testing.T) (*os.File, *parser.Runner) {
40+
func createParserRunner(t *testing.T) *parser.Runner {
4241
t.Helper()
4342

4443
// Find the parser binary
4544
parserPath, err := testutil.FindFile("tools/nvidia_driver_differ/driver_ast_parser")
4645
if err != nil {
4746
t.Fatalf("Failed to find driver_ast_parser: %v", err)
4847
}
49-
parserFile, err := os.Open(parserPath)
50-
if err != nil {
51-
t.Fatalf("Failed to open driver_ast_parser: %v", err)
52-
}
5348

54-
runner, err := parser.NewRunner((*parser.ParserFile)(parserFile))
49+
runner, err := parser.NewRunner(parserPath)
5550
if err != nil {
5651
t.Fatalf("Failed to create parser runner: %v", err)
5752
}
5853

59-
return parserFile, runner
54+
return runner
6055
}
6156

6257
func getDriverDefs(t *testing.T, runner *parser.Runner, version nvconf.DriverVersion) ([]nvproxy.DriverStruct, *parser.OutputJSON) {
@@ -89,8 +84,7 @@ func TestStructDefinitionParity(t *testing.T) {
8984
nvproxy.ForEachSupportDriver(func(version nvconf.DriverVersion, _ nvproxy.Checksums) {
9085
t.Run(version.String(), func(t *testing.T) {
9186
t.Parallel()
92-
f, runner := createParserRunner(t)
93-
defer f.Close()
87+
runner := createParserRunner(t)
9488

9589
nvproxyDefs, defs := getDriverDefs(t, runner, version)
9690

tools/nvidia_driver_differ/BUILD

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ cc_binary(
1919
"@com_google_absl//absl/strings",
2020
"@llvm-project//clang:ast",
2121
"@llvm-project//clang:ast_matchers",
22-
"@llvm-project//clang:basic",
2322
"@llvm-project//clang:tooling",
2423
"@llvm-project//llvm:Support",
2524
"@nlohmann_json//:json",

tools/nvidia_driver_differ/driver_ast_parser.cc

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@
2626
#include "nlohmann/json.hpp"
2727
#include "clang/include/clang/AST/ASTContext.h"
2828
#include "clang/include/clang/AST/Decl.h"
29+
#include "clang/include/clang/AST/Expr.h"
2930
#include "clang/include/clang/AST/Type.h"
3031
#include "clang/include/clang/ASTMatchers/ASTMatchFinder.h"
3132
#include "clang/include/clang/ASTMatchers/ASTMatchers.h"
32-
#include "clang/include/clang/Basic/SourceManager.h"
3333
#include "clang/include/clang/Tooling/CommonOptionsParser.h"
3434
#include "clang/include/clang/Tooling/Tooling.h"
3535
#include "llvm/include/llvm/Support/Casting.h"
@@ -43,6 +43,7 @@ using clang::ast_matchers::hasName;
4343
using clang::ast_matchers::hasType;
4444
using clang::ast_matchers::recordDecl;
4545
using clang::ast_matchers::typedefDecl;
46+
using clang::ast_matchers::varDecl;
4647

4748
using clang::ast_matchers::MatchFinder;
4849

@@ -51,6 +52,7 @@ using json = nlohmann::json;
5152
struct DriverStructReporter : public MatchFinder::MatchCallback {
5253
json RecordDefinitions;
5354
json TypeAliases;
55+
json Constants;
5456
absl::flat_hash_set<std::string> ParsedTypes;
5557

5658
// This matches the case where a struct is being defined.
@@ -89,13 +91,34 @@ struct DriverStructReporter : public MatchFinder::MatchCallback {
8991
.bind("typedef_decl");
9092
}
9193

94+
auto get_constant_matcher(std::string constant_name) {
95+
return varDecl(hasName(constant_name)).bind("constant_decl");
96+
}
97+
9298
void run(const MatchFinder::MatchResult &result) override {
9399
const auto *ctx = result.Context;
94100

101+
const auto *constant_decl =
102+
result.Nodes.getNodeAs<clang::VarDecl>("constant_decl");
103+
if (constant_decl) {
104+
std::string name = constant_decl->getNameAsString();
105+
if (!constant_decl->hasInit()) {
106+
std::cerr << "Constant " << name << " has no initializer\n";
107+
exit(1);
108+
}
109+
clang::Expr::EvalResult value;
110+
if (!constant_decl->getInit()->EvaluateAsInt(value, *ctx)) {
111+
std::cerr << "Unable to evaluate constant " << name << "\n";
112+
exit(1);
113+
}
114+
Constants[name] = value.Val.getInt().getZExtValue();
115+
return;
116+
}
117+
95118
const auto *typedef_decl =
96119
result.Nodes.getNodeAs<clang::TypedefDecl>("typedef_decl");
97120
if (typedef_decl == nullptr) {
98-
std::cerr << "Unable to find typedef decl\n";
121+
std::cerr << "Unable to find a matched declaration\n";
99122
exit(1);
100123
}
101124
std::string name = typedef_decl->getNameAsString();
@@ -218,10 +241,10 @@ static llvm::cl::extrahelp CommonHelp(
218241
clang::tooling::CommonOptionsParser::HelpMessage);
219242
static llvm::cl::extrahelp MoreHelp(ToolHelpDescription);
220243

221-
static llvm::cl::opt<std::string> StructNames(
222-
"structs",
223-
llvm::cl::desc(
224-
"Path to the input file containing the struct names to parse."),
244+
static llvm::cl::opt<std::string> InputFile(
245+
"input",
246+
llvm::cl::desc("Path to the input file containing the struct and constant "
247+
"names to parse."),
225248
llvm::cl::cat(DriverASTParserCategory), llvm::cl::Required);
226249

227250
static llvm::cl::opt<std::string> OutputFile(
@@ -247,25 +270,33 @@ int main(int argc, const char **argv) {
247270
MatchFinder finder;
248271

249272
// Read from StructNames file.
250-
std::ifstream StructNamesIS(StructNames);
251-
if (!StructNamesIS) {
252-
std::cerr << "Unable to open struct names file: " << StructNames << "\n";
273+
std::ifstream InputFileIS(InputFile);
274+
if (!InputFileIS) {
275+
std::cerr << "Unable to open struct names file: " << InputFile << "\n";
253276
return 1;
254277
}
255-
json StructNamesJSON;
256-
StructNamesIS >> StructNamesJSON;
257-
for (json::iterator it = StructNamesJSON["structs"].begin();
258-
it != StructNamesJSON["structs"].end(); ++it) {
278+
json input;
279+
InputFileIS >> input;
280+
for (json::iterator it = input["structs"].begin();
281+
it != input["structs"].end(); ++it) {
259282
finder.addMatcher(reporter.get_struct_definition_matcher(*it), &reporter);
260283
finder.addMatcher(reporter.get_struct_typedef_matcher(*it), &reporter);
261284
}
262285

286+
if (input.contains("constants")) {
287+
for (json::iterator it = input["constants"].begin();
288+
it != input["constants"].end(); ++it) {
289+
finder.addMatcher(reporter.get_constant_matcher(*it), &reporter);
290+
}
291+
}
292+
263293
// Run tool
264294
int ret = Tool.run(clang::tooling::newFrontendActionFactory(&finder).get());
265295

266296
// Print output.
267297
json output = json::object({{"records", reporter.RecordDefinitions},
268-
{"aliases", reporter.TypeAliases}});
298+
{"aliases", reporter.TypeAliases},
299+
{"constants", reporter.Constants}});
269300
if (OutputFile.empty()) {
270301
std::cout << output.dump() << "\n";
271302
} else {

tools/nvidia_driver_differ/driver_ast_parser.h

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717

1818
const char ToolHelpDescription[] =
1919
R"a(This tool parses a given C++ source file and outputs relevant definitions
20-
for a list of provided struct names. It finds the definitions for each struct
21-
given by the names provided, as well as any nested structs or types that they
22-
depend on.
20+
for a list of provided struct names and constant names. It finds the definitions
21+
for each struct given by the names provided, as well as any nested structs or
22+
types that they depend on.
2323

2424
This tool is intended to be used to parse the NVIDIA driver source code; as
2525
such, there are some assumptions made about how structs are defined and what
@@ -30,14 +30,15 @@ that includes all the files to be parsed. You will also need a
3030
compile_commands.json file that contains a compile command with the relevant
3131
include directories.
3232

33-
The struct names should be specified in a JSON file containing a JSON object,
34-
which has a "structs" key that maps to a list of strings. The tool will search
35-
for the struct definition in the given source files, and output the struct
36-
definition to the specified output file.
33+
The struct and constant names should be specified in a JSON file containing a
34+
JSON object, which has "structs" and "constants" keys that map to a list of
35+
strings. The tool will search for their definition in the given source files,
36+
and output their definition to the specified output file.
3737

3838
This output file will contain a JSON object with a "records" (structs or unions)
39-
field mapping each name to its definition, as well as an "aliases" field for
40-
any aliases that were found. A variety of information is outputted:
39+
field mapping each name to its definition, a "aliases" field for any aliases
40+
that were found, as well as a "constants" field mapping each name to its value.
41+
A variety of information is outputted:
4142
- For records, the fields are given as a JSON array of objects with "name",
4243
"type", and "offset" keys. The record also has a "size" key indicating the
4344
size of the struct in bytes, an "is_union" key indicating whether it is a
@@ -46,13 +47,17 @@ any aliases that were found. A variety of information is outputted:
4647
- For aliases, the type is given as a JSON object with a "type" and "size" key
4748

4849
Example usage:
49-
driver_ast_parser --structs=structs.json -o=output.json driver_source_files.h
50+
driver_ast_parser --input=input.json -o=output.json driver_source_files.h
5051

51-
structs.json:
52+
input.json:
5253
{
5354
"structs": [
5455
"TestStruct",
5556
"TestStruct2"
57+
],
58+
"constants": [
59+
"TEST_CONSTANT",
60+
"ANOTHER_CONSTANT"
5661
]
5762
}
5863
)a";

tools/nvidia_driver_differ/driver_ast_parser_test.go

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,34 +42,44 @@ func TestParser(t *testing.T) {
4242
}
4343

4444
// Write a file containing the struct name we want to parse.
45-
structsFile, err := os.CreateTemp(os.TempDir(), "structs.*.json")
45+
inputFile, err := os.CreateTemp(os.TempDir(), "input.*.json")
4646
if err != nil {
47-
t.Fatalf("failed to create structs file: %v", err)
47+
t.Fatalf("failed to create input file: %v", err)
4848
}
4949
defer func() {
50-
if err := structsFile.Close(); err != nil {
51-
t.Fatalf("failed to close structs file: %v", err)
50+
if err := inputFile.Close(); err != nil {
51+
t.Fatalf("failed to close input file: %v", err)
5252
}
53-
if err := os.Remove(structsFile.Name()); err != nil {
54-
t.Fatalf("failed to remove structs file: %v", err)
53+
if err := os.Remove(inputFile.Name()); err != nil {
54+
t.Fatalf("failed to remove input file: %v", err)
5555
}
5656
}()
5757

5858
input := parser.InputJSON{
5959
Structs: []string{"TestStruct", "TestStruct2"},
60+
Constants: []string{
61+
"VAR_CONSTANT_MACRO",
62+
"VAR_ADDITION_MACRO",
63+
"VAR_UNSIGNED_HEX_MACRO",
64+
"VAR_PARENTHESIZED_HEX_MACRO",
65+
"VAR_USES_FUNCTION_MACRO",
66+
},
6067
}
61-
if err := json.NewEncoder(structsFile).Encode(&input); err != nil {
62-
t.Fatalf("failed to write input structs file: %v", err)
68+
if err := json.NewEncoder(inputFile).Encode(&input); err != nil {
69+
t.Fatalf("failed to write input input file: %v", err)
6370
}
64-
structsFile.Sync()
71+
inputFile.Sync()
6572

66-
cmd := exec.Command(driverParser, "--structs", structsFile.Name(), testStructFile)
73+
cmd := exec.Command(driverParser, "--input", inputFile.Name(), testStructFile)
6774
var stderr strings.Builder
6875
cmd.Stderr = &stderr
6976
out, err := cmd.Output()
7077
if err != nil {
7178
t.Fatalf("failed to run driver_ast_parser: %v\n%s", err, stderr.String())
7279
}
80+
if stderr.Len() > 0 {
81+
t.Logf("driver_ast_parser stderr:\n%s", stderr.String())
82+
}
7383

7484
outputJSON := parser.OutputJSON{}
7585
if err := json.Unmarshal(out, &outputJSON); err != nil {
@@ -122,6 +132,13 @@ func TestParser(t *testing.T) {
122132
"OtherInt": parser.TypeDef{Type: "int", Size: 4},
123133
"int": parser.TypeDef{Type: "int", Size: 4},
124134
},
135+
Constants: map[string]uint64{
136+
"VAR_CONSTANT_MACRO": 0x1469,
137+
"VAR_ADDITION_MACRO": 0x1470,
138+
"VAR_UNSIGNED_HEX_MACRO": 0x279,
139+
"VAR_PARENTHESIZED_HEX_MACRO": 0x50a0,
140+
"VAR_USES_FUNCTION_MACRO": 0x1,
141+
},
125142
}
126143

127144
if diff := cmp.Diff(expectedOutput, outputJSON, cmpopts.IgnoreFields(parser.RecordDef{}, "Source")); diff != "" {

tools/nvidia_driver_differ/parser/json_definitions.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,19 @@ import (
2222
"strings"
2323

2424
"github.com/google/go-cmp/cmp"
25-
"gvisor.dev/gvisor/pkg/sentry/devices/nvproxy"
2625
)
2726

28-
// InputJSON is the format for the structs.json file that driver_ast_parser takes as input.
27+
// InputJSON is the format for the input.json file that driver_ast_parser takes as input.
2928
type InputJSON struct {
30-
Structs []string `json:"structs"`
29+
Structs []string `json:"structs"`
30+
Constants []string `json:"constants"`
3131
}
3232

3333
// OutputJSON is the format for the output of driver_ast_parser.
3434
type OutputJSON struct {
35-
Records RecordDefs
36-
Aliases TypeAliases
35+
Records RecordDefs
36+
Aliases TypeAliases
37+
Constants map[string]uint64
3738
}
3839

3940
// Merge merges the struct definitions from b into this OutputJSON.
@@ -44,8 +45,12 @@ func (a *OutputJSON) Merge(b OutputJSON) {
4445
if a.Aliases == nil {
4546
a.Aliases = make(TypeAliases)
4647
}
48+
if a.Constants == nil {
49+
a.Constants = make(map[string]uint64)
50+
}
4751
maps.Copy(a.Records, b.Records)
4852
maps.Copy(a.Aliases, b.Aliases)
53+
maps.Copy(a.Constants, b.Constants)
4954
}
5055

5156
// RecordField represents a field in a record (struct or union).
@@ -85,7 +90,7 @@ type RecordDefs map[string]RecordDef
8590
type TypeAliases map[string]TypeDef
8691

8792
// GetRecordDiff prints a diff between two records.
88-
func GetRecordDiff(name nvproxy.DriverStructName, a, b RecordDef) string {
93+
func GetRecordDiff(name string, a, b RecordDef) string {
8994
var sb strings.Builder
9095
fmt.Fprintf(&sb, "--- A: %s\n", a.Source)
9196
fmt.Fprintf(&sb, "+++ B: %s\n", b.Source)

0 commit comments

Comments
 (0)