Tests: Add multithreading attribute checks (#3748)
This commit is contained in:
parent
4f7df4a915
commit
7a15457511
|
|
@ -397,6 +397,7 @@ PY_PROGRAMS = \
|
||||||
src/flexfix \
|
src/flexfix \
|
||||||
src/vlcovgen \
|
src/vlcovgen \
|
||||||
test_regress/t/*.pf \
|
test_regress/t/*.pf \
|
||||||
|
nodist/clang_check_attributes \
|
||||||
nodist/code_coverage \
|
nodist/code_coverage \
|
||||||
nodist/dot_importer \
|
nodist/dot_importer \
|
||||||
nodist/fuzzer/actual_fail \
|
nodist/fuzzer/actual_fail \
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,11 @@ elif [ "$CI_BUILD_STAGE_NAME" = "test" ]; then
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
# libfl-dev needed for internal coverage's test runs
|
# libfl-dev needed for internal coverage's test runs
|
||||||
sudo apt-get install gdb gtkwave lcov libfl-dev ccache
|
sudo apt-get install gdb gtkwave lcov libfl-dev ccache
|
||||||
|
# Required for test_regress/t/t_dist_attributes.pl
|
||||||
|
if [ "$CI_RUNS_ON" = "ubuntu-22.04" ]; then
|
||||||
|
sudo apt-get install libclang-dev
|
||||||
|
pip3 install clang==14.0
|
||||||
|
fi
|
||||||
if [ "$CI_RUNS_ON" = "ubuntu-20.04" ] || [ "$CI_RUNS_ON" = "ubuntu-22.04" ]; then
|
if [ "$CI_RUNS_ON" = "ubuntu-20.04" ] || [ "$CI_RUNS_ON" = "ubuntu-22.04" ]; then
|
||||||
sudo apt-get install libsystemc-dev
|
sudo apt-get install libsystemc-dev
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -125,8 +125,8 @@ Those developing Verilator itself may also want these (see internals.rst):
|
||||||
::
|
::
|
||||||
|
|
||||||
sudo apt-get install gdb graphviz cmake clang clang-format-14 gprof lcov
|
sudo apt-get install gdb graphviz cmake clang clang-format-14 gprof lcov
|
||||||
sudo apt-get install yapf3
|
sudo apt-get install libclang-dev yapf3
|
||||||
sudo pip3 install sphinx sphinx_rtd_theme sphinxcontrib-spelling breathe
|
sudo pip3 install clang sphinx sphinx_rtd_theme sphinxcontrib-spelling breathe
|
||||||
cpan install Pod::Perldoc
|
cpan install Pod::Perldoc
|
||||||
cpan install Parallel::Forker
|
cpan install Parallel::Forker
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,16 +57,16 @@
|
||||||
# define VL_ATTR_WEAK __attribute__((weak))
|
# define VL_ATTR_WEAK __attribute__((weak))
|
||||||
# endif
|
# endif
|
||||||
# if defined(__clang__)
|
# if defined(__clang__)
|
||||||
# define VL_ACQUIRE(...) __attribute__((acquire_capability(__VA_ARGS__)))
|
# define VL_ACQUIRE(...) __attribute__((annotate("ACQUIRE"))) __attribute__((acquire_capability(__VA_ARGS__)))
|
||||||
# define VL_ACQUIRE_SHARED(...) __attribute__((acquire_shared_capability(__VA_ARGS__)))
|
# define VL_ACQUIRE_SHARED(...) __attribute__((annotate("ACQUIRE_SHARED"))) __attribute__((acquire_shared_capability(__VA_ARGS__)))
|
||||||
# define VL_RELEASE(...) __attribute__((release_capability(__VA_ARGS__)))
|
# define VL_RELEASE(...) __attribute__((annotate("RELEASE"))) __attribute__((release_capability(__VA_ARGS__)))
|
||||||
# define VL_RELEASE_SHARED(...) __attribute__((release_shared_capability(__VA_ARGS__)))
|
# define VL_RELEASE_SHARED(...) __attribute__((annotate("RELEASE_SHARED"))) __attribute__((release_shared_capability(__VA_ARGS__)))
|
||||||
# define VL_TRY_ACQUIRE(...) __attribute__((try_acquire_capability(__VA_ARGS__)))
|
# define VL_TRY_ACQUIRE(...) __attribute__((try_acquire_capability(__VA_ARGS__)))
|
||||||
# define VL_TRY_ACQUIRE_SHARED(...) __attribute__((try_acquire_shared_capability(__VA_ARGS__)))
|
# define VL_TRY_ACQUIRE_SHARED(...) __attribute__((try_acquire_shared_capability(__VA_ARGS__)))
|
||||||
# define VL_CAPABILITY(x) __attribute__((capability(x)))
|
# define VL_CAPABILITY(x) __attribute__((capability(x)))
|
||||||
# define VL_REQUIRES(x) __attribute__((requires_capability(x)))
|
# define VL_REQUIRES(x) __attribute__((annotate("REQUIRES"))) __attribute__((requires_capability(x)))
|
||||||
# define VL_GUARDED_BY(x) __attribute__((guarded_by(x)))
|
# define VL_GUARDED_BY(x) __attribute__((annotate("GUARDED_BY"))) __attribute__((guarded_by(x)))
|
||||||
# define VL_EXCLUDES(x) __attribute__((locks_excluded(x)))
|
# define VL_EXCLUDES(x) __attribute__((annotate("EXCLUDES"))) __attribute__((locks_excluded(x)))
|
||||||
# define VL_SCOPED_CAPABILITY __attribute__((scoped_lockable))
|
# define VL_SCOPED_CAPABILITY __attribute__((scoped_lockable))
|
||||||
# endif
|
# endif
|
||||||
# define VL_LIKELY(x) __builtin_expect(!!(x), 1) // Prefer over C++20 [[likely]]
|
# define VL_LIKELY(x) __builtin_expect(!!(x), 1) // Prefer over C++20 [[likely]]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,888 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# pylint: disable=C0114,C0115,C0116,C0209,C0302,R0902,R0911,R0912,R0914,R0915,E1101
|
||||||
|
#
|
||||||
|
# Copyright 2022 by Wilson Snyder. Verilator is free software; you
|
||||||
|
# can redistribute it and/or modify it under the terms of either the GNU Lesser
|
||||||
|
# General Public License Version 3 or the Apache License 2.0.
|
||||||
|
# SPDX-License-Identifier: LGPL-3.0-only OR Apache-2.0
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import shlex
|
||||||
|
from typing import Callable, Iterable, Optional, Union
|
||||||
|
import dataclasses
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import enum
|
||||||
|
from enum import Enum
|
||||||
|
import multiprocessing
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
import clang.cindex
|
||||||
|
from clang.cindex import CursorKind, Index, TranslationUnitSaveError, TranslationUnitLoadError
|
||||||
|
|
||||||
|
|
||||||
|
def fully_qualified_name(node):
|
||||||
|
if node is None:
|
||||||
|
return []
|
||||||
|
if node.kind == CursorKind.TRANSLATION_UNIT:
|
||||||
|
return []
|
||||||
|
res = fully_qualified_name(node.semantic_parent)
|
||||||
|
if res:
|
||||||
|
return res + ([node.displayname] if node.displayname else [])
|
||||||
|
return [node.displayname] if node.displayname else []
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class VlAnnotations:
|
||||||
|
mt_start: bool = False
|
||||||
|
mt_safe: bool = False
|
||||||
|
mt_safe_postinit: bool = False
|
||||||
|
mt_unsafe: bool = False
|
||||||
|
mt_unsafe_one: bool = False
|
||||||
|
pure: bool = False
|
||||||
|
guarded: bool = False
|
||||||
|
requires: bool = False
|
||||||
|
excludes: bool = False
|
||||||
|
acquire: bool = False
|
||||||
|
release: bool = False
|
||||||
|
|
||||||
|
def is_mt_safe_context(self):
|
||||||
|
return (not (self.mt_unsafe or self.mt_unsafe_one)
|
||||||
|
and (self.mt_safe or self.mt_start))
|
||||||
|
|
||||||
|
def is_pure_context(self):
|
||||||
|
return self.pure
|
||||||
|
|
||||||
|
def is_mt_unsafe_call(self):
|
||||||
|
return self.mt_unsafe or self.mt_unsafe_one
|
||||||
|
|
||||||
|
def is_mt_safe_call(self):
|
||||||
|
return (not self.is_mt_unsafe_call()
|
||||||
|
and (self.mt_safe or self.mt_safe_postinit or self.pure
|
||||||
|
or self.requires or self.excludes or self.acquire
|
||||||
|
or self.release))
|
||||||
|
|
||||||
|
def is_pure_call(self):
|
||||||
|
return self.pure
|
||||||
|
|
||||||
|
def __or__(self, other: "VlAnnotations"):
|
||||||
|
result = VlAnnotations()
|
||||||
|
for key, value in dataclasses.asdict(self).items():
|
||||||
|
setattr(result, key, value | getattr(other, key))
|
||||||
|
return result
|
||||||
|
|
||||||
|
def is_empty(self):
|
||||||
|
for value in dataclasses.asdict(self).values():
|
||||||
|
if value:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
result = []
|
||||||
|
for field, value in dataclasses.asdict(self).items():
|
||||||
|
if value:
|
||||||
|
result.append(field)
|
||||||
|
return ", ".join(result)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_nodes_list(nodes: Iterable):
|
||||||
|
result = VlAnnotations()
|
||||||
|
for node in nodes:
|
||||||
|
if node.kind == CursorKind.ANNOTATE_ATTR:
|
||||||
|
if node.displayname == "MT_START":
|
||||||
|
result.mt_start = True
|
||||||
|
elif node.displayname == "MT_SAFE":
|
||||||
|
result.mt_safe = True
|
||||||
|
elif node.displayname == "MT_SAFE_POSTINIT":
|
||||||
|
result.mt_safe_postinit = True
|
||||||
|
elif node.displayname == "MT_UNSAFE":
|
||||||
|
result.mt_unsafe = True
|
||||||
|
elif node.displayname == "MT_UNSAFE_ONE":
|
||||||
|
result.mt_unsafe_one = True
|
||||||
|
elif node.displayname == "PURE":
|
||||||
|
result.pure = True
|
||||||
|
elif node.displayname in ["ACQUIRE", "ACQUIRE_SHARED"]:
|
||||||
|
result.acquire = True
|
||||||
|
elif node.displayname in ["RELEASE", "RELEASE_SHARED"]:
|
||||||
|
result.release = True
|
||||||
|
elif node.displayname == "REQUIRES":
|
||||||
|
result.requires = True
|
||||||
|
elif node.displayname in ["EXCLUDES", "MT_SAFE_EXCLUDES"]:
|
||||||
|
result.excludes = True
|
||||||
|
elif node.displayname == "GUARDED_BY":
|
||||||
|
result.guarded = True
|
||||||
|
# Attributes are always at the beginning
|
||||||
|
elif not node.kind.is_attribute():
|
||||||
|
break
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class FunctionType(Enum):
|
||||||
|
UNKNOWN = enum.auto()
|
||||||
|
FUNCTION = enum.auto()
|
||||||
|
METHOD = enum.auto()
|
||||||
|
STATIC_METHOD = enum.auto()
|
||||||
|
CONSTRUCTOR = enum.auto()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_node(node: clang.cindex.Cursor):
|
||||||
|
if node is None:
|
||||||
|
return FunctionType.UNKNOWN
|
||||||
|
if node.kind == CursorKind.FUNCTION_DECL:
|
||||||
|
return FunctionType.FUNCTION
|
||||||
|
if node.kind == CursorKind.CXX_METHOD and node.is_static_method():
|
||||||
|
return FunctionType.STATIC_METHOD
|
||||||
|
if node.kind == CursorKind.CXX_METHOD:
|
||||||
|
return FunctionType.METHOD
|
||||||
|
if node.kind == CursorKind.CONSTRUCTOR:
|
||||||
|
return FunctionType.CONSTRUCTOR
|
||||||
|
return FunctionType.UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(eq=False)
|
||||||
|
class FunctionInfo:
|
||||||
|
name_parts: list[str]
|
||||||
|
usr: str
|
||||||
|
file: str
|
||||||
|
line: int
|
||||||
|
annotations: VlAnnotations
|
||||||
|
ftype: FunctionType
|
||||||
|
|
||||||
|
_hash: Optional[int] = dataclasses.field(default=None,
|
||||||
|
init=False,
|
||||||
|
repr=False)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return "::".join(self.name_parts)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"[{self.name}@{self.file}:{self.line}]"
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
if not self._hash:
|
||||||
|
self._hash = hash(f"{self.usr}:{self.file}:{self.line}")
|
||||||
|
return self._hash
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return (self.usr == other.usr and self.file == other.file
|
||||||
|
and self.line == other.line)
|
||||||
|
|
||||||
|
def copy(self, /, **changes):
|
||||||
|
return dataclasses.replace(self, **changes)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_node(node: clang.cindex.Cursor,
|
||||||
|
refd: Optional[clang.cindex.Cursor] = None,
|
||||||
|
annotations: Optional[VlAnnotations] = None):
|
||||||
|
file = os.path.abspath(node.location.file.name)
|
||||||
|
line = node.location.line
|
||||||
|
if annotations is None:
|
||||||
|
annotations = VlAnnotations.from_nodes_list(node.get_children())
|
||||||
|
if refd is None:
|
||||||
|
refd = node.referenced
|
||||||
|
if refd is not None:
|
||||||
|
refd = refd.canonical
|
||||||
|
assert refd is not None
|
||||||
|
name_parts = fully_qualified_name(refd)
|
||||||
|
usr = refd.get_usr()
|
||||||
|
ftype = FunctionType.from_node(refd)
|
||||||
|
|
||||||
|
return FunctionInfo(name_parts, usr, file, line, annotations, ftype)
|
||||||
|
|
||||||
|
|
||||||
|
class DiagnosticKind(Enum):
|
||||||
|
ANNOTATIONS_DEF_DECL_MISMATCH = enum.auto()
|
||||||
|
NON_PURE_CALL_IN_PURE_CTX = enum.auto()
|
||||||
|
NON_MT_SAFE_CALL_IN_MT_SAFE_CTX = enum.auto()
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.value < other.value
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Diagnostic:
|
||||||
|
target: FunctionInfo
|
||||||
|
source: FunctionInfo
|
||||||
|
source_ctx: FunctionInfo
|
||||||
|
kind: DiagnosticKind
|
||||||
|
|
||||||
|
_hash: Optional[int] = dataclasses.field(default=None,
|
||||||
|
init=False,
|
||||||
|
repr=False)
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
if not self._hash:
|
||||||
|
self._hash = hash(
|
||||||
|
hash(self.target) ^ hash(self.source_ctx) ^ hash(self.kind))
|
||||||
|
return self._hash
|
||||||
|
|
||||||
|
|
||||||
|
class CallAnnotationsValidator:
|
||||||
|
|
||||||
|
def __init__(self, diagnostic_cb: Callable[[Diagnostic], None],
|
||||||
|
is_ignored_top_level: Callable[[clang.cindex.Cursor], bool],
|
||||||
|
is_ignored_def: Callable[
|
||||||
|
[clang.cindex.Cursor, clang.cindex.Cursor], bool],
|
||||||
|
is_ignored_call: Callable[[clang.cindex.Cursor], bool]):
|
||||||
|
self._diagnostic_cb = diagnostic_cb
|
||||||
|
self._is_ignored_top_level = is_ignored_top_level
|
||||||
|
self._is_ignored_call = is_ignored_call
|
||||||
|
self._is_ignored_def = is_ignored_def
|
||||||
|
|
||||||
|
self._index = Index.create()
|
||||||
|
|
||||||
|
self._processed_headers: set[str] = set()
|
||||||
|
|
||||||
|
# Current context
|
||||||
|
self._call_location: Optional[FunctionInfo] = None
|
||||||
|
self._caller: Optional[FunctionInfo] = None
|
||||||
|
self._level: int = 0
|
||||||
|
|
||||||
|
def compile_and_analyze_file(self, source_file: str,
|
||||||
|
compiler_args: list[str],
|
||||||
|
build_dir: Optional[str]):
|
||||||
|
filename = os.path.abspath(source_file)
|
||||||
|
initial_cwd = "."
|
||||||
|
|
||||||
|
if build_dir:
|
||||||
|
initial_cwd = os.getcwd()
|
||||||
|
os.chdir(build_dir)
|
||||||
|
translation_unit = self._index.parse(filename, compiler_args)
|
||||||
|
has_errors = False
|
||||||
|
for diag in translation_unit.diagnostics:
|
||||||
|
if diag.severity > clang.cindex.Diagnostic.Error:
|
||||||
|
has_errors = True
|
||||||
|
if translation_unit and not has_errors:
|
||||||
|
self.process_translation_unit(translation_unit)
|
||||||
|
else:
|
||||||
|
print(f"%Error: parsing failed: {filename}", file=sys.stderr)
|
||||||
|
if build_dir:
|
||||||
|
os.chdir(initial_cwd)
|
||||||
|
|
||||||
|
def emit_diagnostic(self, target: Union[FunctionInfo, clang.cindex.Cursor],
|
||||||
|
kind: DiagnosticKind):
|
||||||
|
assert self._caller is not None
|
||||||
|
assert self._call_location is not None
|
||||||
|
source = self._caller
|
||||||
|
source_ctx = self._call_location
|
||||||
|
if isinstance(target, FunctionInfo):
|
||||||
|
self._diagnostic_cb(Diagnostic(target, source, source_ctx, kind))
|
||||||
|
else:
|
||||||
|
self._diagnostic_cb(
|
||||||
|
Diagnostic(FunctionInfo.from_node(target), source, source_ctx,
|
||||||
|
kind))
|
||||||
|
|
||||||
|
def iterate_children(self, children: Iterable[clang.cindex.Cursor],
|
||||||
|
handler: Callable[[clang.cindex.Cursor], None]):
|
||||||
|
if children:
|
||||||
|
self._level += 1
|
||||||
|
for child in children:
|
||||||
|
handler(child)
|
||||||
|
self._level -= 1
|
||||||
|
|
||||||
|
def get_referenced_node_info(
|
||||||
|
self, node: clang.cindex.Cursor
|
||||||
|
) -> tuple[bool, Optional[clang.cindex.Cursor], VlAnnotations,
|
||||||
|
Iterable[clang.cindex.Cursor]]:
|
||||||
|
if not node.spelling and not node.displayname:
|
||||||
|
return (False, None, VlAnnotations(), [])
|
||||||
|
|
||||||
|
refd = node.referenced
|
||||||
|
if refd is None:
|
||||||
|
raise ValueError("The node does not specify referenced node.")
|
||||||
|
|
||||||
|
refd = refd.canonical
|
||||||
|
children = list(refd.get_children())
|
||||||
|
|
||||||
|
annotations = VlAnnotations.from_nodes_list(children)
|
||||||
|
return (True, refd, annotations, children)
|
||||||
|
|
||||||
|
# Call handling
|
||||||
|
|
||||||
|
def process_method_call(self, node: clang.cindex.Cursor,
|
||||||
|
refd: clang.cindex.Cursor,
|
||||||
|
annotations: VlAnnotations):
|
||||||
|
assert self._call_location
|
||||||
|
ctx = self._call_location.annotations
|
||||||
|
|
||||||
|
# MT-safe context
|
||||||
|
if ctx.is_mt_safe_context():
|
||||||
|
is_mt_safe = False
|
||||||
|
|
||||||
|
if annotations.is_mt_safe_call():
|
||||||
|
is_mt_safe = True
|
||||||
|
elif not annotations.is_mt_unsafe_call():
|
||||||
|
# Check whether the object the method is called on is mt-safe
|
||||||
|
def find_object_ref(node):
|
||||||
|
try:
|
||||||
|
node = next(node.get_children())
|
||||||
|
if node.kind != CursorKind.MEMBER_REF_EXPR:
|
||||||
|
return None
|
||||||
|
node = next(node.get_children())
|
||||||
|
if node.kind == CursorKind.UNEXPOSED_EXPR:
|
||||||
|
node = next(node.get_children())
|
||||||
|
return node
|
||||||
|
except StopIteration:
|
||||||
|
return None
|
||||||
|
|
||||||
|
refn = find_object_ref(node)
|
||||||
|
# class/struct member
|
||||||
|
if refn and refn.kind == CursorKind.MEMBER_REF_EXPR and refn.referenced:
|
||||||
|
refn = refn.referenced
|
||||||
|
refna = VlAnnotations.from_nodes_list(refn.get_children())
|
||||||
|
if refna.guarded:
|
||||||
|
is_mt_safe = True
|
||||||
|
# variable
|
||||||
|
elif refn and refn.kind == CursorKind.DECL_REF_EXPR and refn.referenced:
|
||||||
|
# This is probably a local or an argument. Assume it's safe.
|
||||||
|
is_mt_safe = True
|
||||||
|
|
||||||
|
if not is_mt_safe:
|
||||||
|
self.emit_diagnostic(
|
||||||
|
FunctionInfo.from_node(refd, refd, annotations),
|
||||||
|
DiagnosticKind.NON_MT_SAFE_CALL_IN_MT_SAFE_CTX)
|
||||||
|
|
||||||
|
if ctx.is_pure_context():
|
||||||
|
if not annotations.is_pure_call():
|
||||||
|
self.emit_diagnostic(
|
||||||
|
FunctionInfo.from_node(refd, refd, annotations),
|
||||||
|
DiagnosticKind.NON_PURE_CALL_IN_PURE_CTX)
|
||||||
|
|
||||||
|
def process_function_call(self, refd: clang.cindex.Cursor,
|
||||||
|
annotations: VlAnnotations):
|
||||||
|
assert self._call_location
|
||||||
|
ctx = self._call_location.annotations
|
||||||
|
|
||||||
|
# MT-safe context
|
||||||
|
if ctx.is_mt_safe_context():
|
||||||
|
if not annotations.is_mt_safe_call():
|
||||||
|
self.emit_diagnostic(
|
||||||
|
FunctionInfo.from_node(refd, refd, annotations),
|
||||||
|
DiagnosticKind.NON_MT_SAFE_CALL_IN_MT_SAFE_CTX)
|
||||||
|
# pure context
|
||||||
|
if ctx.is_pure_context():
|
||||||
|
if not annotations.is_pure_call():
|
||||||
|
self.emit_diagnostic(
|
||||||
|
FunctionInfo.from_node(refd, refd, annotations),
|
||||||
|
DiagnosticKind.NON_PURE_CALL_IN_PURE_CTX)
|
||||||
|
|
||||||
|
def process_constructor_call(self, refd: clang.cindex.Cursor,
|
||||||
|
annotations: VlAnnotations):
|
||||||
|
assert self._call_location
|
||||||
|
ctx = self._call_location.annotations
|
||||||
|
|
||||||
|
# Constructors are always OK in MT-safe context.
|
||||||
|
|
||||||
|
# pure context
|
||||||
|
if ctx.is_pure_context():
|
||||||
|
if not annotations.is_pure_call(
|
||||||
|
) and not refd.is_default_constructor():
|
||||||
|
self.emit_diagnostic(
|
||||||
|
FunctionInfo.from_node(refd, refd, annotations),
|
||||||
|
DiagnosticKind.NON_PURE_CALL_IN_PURE_CTX)
|
||||||
|
|
||||||
|
def dispatch_call_node(self, node: clang.cindex.Cursor):
|
||||||
|
[supported, refd, annotations, _] = self.get_referenced_node_info(node)
|
||||||
|
|
||||||
|
if not supported:
|
||||||
|
self.iterate_children(node.get_children(),
|
||||||
|
self.dispatch_node_inside_definition)
|
||||||
|
return
|
||||||
|
|
||||||
|
assert refd is not None
|
||||||
|
if self._is_ignored_call(refd):
|
||||||
|
return
|
||||||
|
|
||||||
|
assert self._call_location is not None
|
||||||
|
node_file = os.path.abspath(node.location.file.name)
|
||||||
|
self._call_location = self._call_location.copy(file=node_file,
|
||||||
|
line=node.location.line)
|
||||||
|
|
||||||
|
# Standalone functions and static class methods
|
||||||
|
if (refd.kind == CursorKind.FUNCTION_DECL
|
||||||
|
or refd.kind == CursorKind.CXX_METHOD
|
||||||
|
and refd.is_static_method()):
|
||||||
|
self.process_function_call(refd, annotations)
|
||||||
|
return
|
||||||
|
# Function pointer
|
||||||
|
if refd.kind in [
|
||||||
|
CursorKind.VAR_DECL, CursorKind.FIELD_DECL,
|
||||||
|
CursorKind.PARM_DECL
|
||||||
|
]:
|
||||||
|
self.process_function_call(refd, annotations)
|
||||||
|
return
|
||||||
|
# Non-static class methods
|
||||||
|
if refd.kind == CursorKind.CXX_METHOD:
|
||||||
|
self.process_method_call(node, refd, annotations)
|
||||||
|
return
|
||||||
|
# Conversion method (e.g. `operator int()`)
|
||||||
|
if refd.kind == CursorKind.CONVERSION_FUNCTION:
|
||||||
|
self.process_method_call(node, refd, annotations)
|
||||||
|
return
|
||||||
|
# Constructors
|
||||||
|
if refd.kind == CursorKind.CONSTRUCTOR:
|
||||||
|
self.process_constructor_call(refd, annotations)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Ignore other callables
|
||||||
|
print(f"{refd.location.file.name}:{refd.location.line}: "
|
||||||
|
f"{refd.displayname} {refd.kind}\n"
|
||||||
|
f" from: {node.location.file.name}:{node.location.line}")
|
||||||
|
|
||||||
|
# Definition handling
|
||||||
|
|
||||||
|
def dispatch_node_inside_definition(self, node: clang.cindex.Cursor):
|
||||||
|
if node.kind == CursorKind.CALL_EXPR:
|
||||||
|
self.dispatch_call_node(node)
|
||||||
|
return None
|
||||||
|
|
||||||
|
return self.iterate_children(node.get_children(),
|
||||||
|
self.dispatch_node_inside_definition)
|
||||||
|
|
||||||
|
def process_function_definition(self, node: clang.cindex.Cursor):
|
||||||
|
[supported, refd, annotations, _] = self.get_referenced_node_info(node)
|
||||||
|
|
||||||
|
if refd and self._is_ignored_def(node, refd):
|
||||||
|
return None
|
||||||
|
|
||||||
|
node_children = list(node.get_children())
|
||||||
|
|
||||||
|
if not supported:
|
||||||
|
return self.iterate_children(node_children, self.dispatch_node)
|
||||||
|
|
||||||
|
assert refd is not None
|
||||||
|
|
||||||
|
prev_caller = self._caller
|
||||||
|
prev_call_location = self._call_location
|
||||||
|
|
||||||
|
def_annotations = VlAnnotations.from_nodes_list(node_children)
|
||||||
|
|
||||||
|
if not (def_annotations.is_empty() or def_annotations == annotations):
|
||||||
|
# Use definition's annotations for the diagnostic
|
||||||
|
# source (i.e. the definition)
|
||||||
|
self._caller = FunctionInfo.from_node(node, refd, def_annotations)
|
||||||
|
self._call_location = self._caller
|
||||||
|
|
||||||
|
self.emit_diagnostic(
|
||||||
|
FunctionInfo.from_node(refd, refd, annotations),
|
||||||
|
DiagnosticKind.ANNOTATIONS_DEF_DECL_MISMATCH)
|
||||||
|
|
||||||
|
# Use concatenation of definition and declaration annotations
|
||||||
|
# for callees validation.
|
||||||
|
self._caller = FunctionInfo.from_node(node, refd,
|
||||||
|
def_annotations | annotations)
|
||||||
|
self._call_location = self._caller
|
||||||
|
|
||||||
|
self.iterate_children(node_children,
|
||||||
|
self.dispatch_node_inside_definition)
|
||||||
|
|
||||||
|
self._call_location = prev_call_location
|
||||||
|
self._caller = prev_caller
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Nodes not located inside definition
|
||||||
|
|
||||||
|
def dispatch_node(self, node: clang.cindex.Cursor):
|
||||||
|
if node.is_definition() and node.kind in [
|
||||||
|
CursorKind.CXX_METHOD, CursorKind.FUNCTION_DECL,
|
||||||
|
CursorKind.CONSTRUCTOR, CursorKind.CONVERSION_FUNCTION
|
||||||
|
]:
|
||||||
|
return self.process_function_definition(node)
|
||||||
|
if node.is_definition() and node.kind in [
|
||||||
|
CursorKind.NAMESPACE, CursorKind.STRUCT_DECL,
|
||||||
|
CursorKind.UNION_DECL, CursorKind.CLASS_DECL
|
||||||
|
]:
|
||||||
|
return self.iterate_children(node.get_children(),
|
||||||
|
self.dispatch_node)
|
||||||
|
|
||||||
|
return self.iterate_children(node.get_children(), self.dispatch_node)
|
||||||
|
|
||||||
|
def process_translation_unit(
|
||||||
|
self, translation_unit: clang.cindex.TranslationUnit):
|
||||||
|
self._level += 1
|
||||||
|
for child in translation_unit.cursor.get_children():
|
||||||
|
if self._is_ignored_top_level(child):
|
||||||
|
continue
|
||||||
|
if self._processed_headers:
|
||||||
|
filename = os.path.abspath(child.location.file.name)
|
||||||
|
if filename in self._processed_headers:
|
||||||
|
continue
|
||||||
|
self.dispatch_node(child)
|
||||||
|
self._level -= 1
|
||||||
|
|
||||||
|
self._processed_headers.update([
|
||||||
|
os.path.abspath(str(hdr.source))
|
||||||
|
for hdr in translation_unit.get_includes()
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class CompileCommand:
|
||||||
|
refid: int
|
||||||
|
filename: str
|
||||||
|
args: list[str]
|
||||||
|
directory: str = dataclasses.field(default_factory=os.getcwd)
|
||||||
|
|
||||||
|
|
||||||
|
def get_filter_funcs(verilator_root: str):
|
||||||
|
verilator_root = os.path.abspath(verilator_root) + "/"
|
||||||
|
|
||||||
|
def is_ignored_top_level(node: clang.cindex.Cursor) -> bool:
|
||||||
|
# Anything defined in a header outside Verilator root
|
||||||
|
if not node.location.file:
|
||||||
|
return True
|
||||||
|
filename = os.path.abspath(node.location.file.name)
|
||||||
|
return not filename.startswith(verilator_root)
|
||||||
|
|
||||||
|
def is_ignored_def(node: clang.cindex.Cursor,
|
||||||
|
refd: clang.cindex.Cursor) -> bool:
|
||||||
|
# __*
|
||||||
|
if str(refd.spelling).startswith("__"):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Anything defined in a header outside Verilator root
|
||||||
|
if not node.location.file:
|
||||||
|
return True
|
||||||
|
filename = os.path.abspath(node.location.file.name)
|
||||||
|
if not filename.startswith(verilator_root):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_ignored_call(refd: clang.cindex.Cursor) -> bool:
|
||||||
|
# __*
|
||||||
|
if str(refd.spelling).startswith("__"):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# std::*
|
||||||
|
fqn = fully_qualified_name(refd)
|
||||||
|
if fqn and fqn[0] == "std":
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Anything declared in a header outside Verilator root
|
||||||
|
if not refd.location.file:
|
||||||
|
return True
|
||||||
|
filename = os.path.abspath(refd.location.file.name)
|
||||||
|
if not filename.startswith(verilator_root):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
return (is_ignored_top_level, is_ignored_def, is_ignored_call)
|
||||||
|
|
||||||
|
|
||||||
|
def precompile_header(compile_command: CompileCommand, tmp_dir: str) -> str:
|
||||||
|
try:
|
||||||
|
initial_cwd = os.getcwd()
|
||||||
|
os.chdir(compile_command.directory)
|
||||||
|
|
||||||
|
index = Index.create()
|
||||||
|
translation_unit = index.parse(compile_command.filename,
|
||||||
|
compile_command.args)
|
||||||
|
for diag in translation_unit.diagnostics:
|
||||||
|
if diag.severity > clang.cindex.Diagnostic.Error:
|
||||||
|
pch_file = None
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
pch_file = os.path.join(
|
||||||
|
tmp_dir,
|
||||||
|
f"{compile_command.refid:02}_{os.path.basename(compile_command.filename)}.pch"
|
||||||
|
)
|
||||||
|
translation_unit.save(pch_file)
|
||||||
|
|
||||||
|
os.chdir(initial_cwd)
|
||||||
|
|
||||||
|
if pch_file:
|
||||||
|
return pch_file
|
||||||
|
|
||||||
|
except (TranslationUnitSaveError, TranslationUnitLoadError, OSError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"%Warning: Precompiling failed, skipping: {compile_command.filename}")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
# Compile and analyze inputs in a single process.
|
||||||
|
def run_analysis(ccl: Iterable[CompileCommand], pccl: Iterable[CompileCommand],
|
||||||
|
diagnostic_cb: Callable[[Diagnostic],
|
||||||
|
None], verilator_root: str):
|
||||||
|
(is_ignored_top_level, is_ignored_def,
|
||||||
|
is_ignored_call) = get_filter_funcs(verilator_root)
|
||||||
|
|
||||||
|
prefix = "verilator_clang_check_attributes_"
|
||||||
|
with tempfile.TemporaryDirectory(prefix=prefix) as tmp_dir:
|
||||||
|
extra_args = []
|
||||||
|
for pcc in pccl:
|
||||||
|
pch_file = precompile_header(pcc, tmp_dir)
|
||||||
|
if pch_file:
|
||||||
|
extra_args += ["-include-pch", pch_file]
|
||||||
|
|
||||||
|
cav = CallAnnotationsValidator(diagnostic_cb, is_ignored_top_level,
|
||||||
|
is_ignored_def, is_ignored_call)
|
||||||
|
for compile_command in ccl:
|
||||||
|
cav.compile_and_analyze_file(compile_command.filename,
|
||||||
|
compile_command.args + extra_args,
|
||||||
|
compile_command.directory)
|
||||||
|
|
||||||
|
|
||||||
|
class ParallelAnalysisProcess:
|
||||||
|
cav: Optional[CallAnnotationsValidator] = None
|
||||||
|
diags: set[Diagnostic] = dataclasses.field(default_factory=set)
|
||||||
|
tmp_dir: str = ""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def init_data(verilator_root: str, tmp_dir: str):
|
||||||
|
(is_ignored_top_level, is_ignored_def,
|
||||||
|
is_ignored_call) = get_filter_funcs(verilator_root)
|
||||||
|
|
||||||
|
ParallelAnalysisProcess.cav = CallAnnotationsValidator(
|
||||||
|
ParallelAnalysisProcess._diagnostic_handler, is_ignored_top_level,
|
||||||
|
is_ignored_def, is_ignored_call)
|
||||||
|
ParallelAnalysisProcess.tmp_dir = tmp_dir
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _diagnostic_handler(diag: Diagnostic):
|
||||||
|
ParallelAnalysisProcess.diags.add(diag)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def analyze_cpp_file(compile_command: CompileCommand) -> set[Diagnostic]:
|
||||||
|
ParallelAnalysisProcess.diags = set()
|
||||||
|
assert ParallelAnalysisProcess.cav is not None
|
||||||
|
ParallelAnalysisProcess.cav.compile_and_analyze_file(
|
||||||
|
compile_command.filename, compile_command.args,
|
||||||
|
compile_command.directory)
|
||||||
|
return ParallelAnalysisProcess.diags
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def precompile_header(compile_command: CompileCommand) -> str:
|
||||||
|
return precompile_header(compile_command,
|
||||||
|
ParallelAnalysisProcess.tmp_dir)
|
||||||
|
|
||||||
|
|
||||||
|
# Compile and analyze inputs in multiple processes.
|
||||||
|
def run_parallel_analysis(ccl: Iterable[CompileCommand],
|
||||||
|
pccl: Iterable[CompileCommand],
|
||||||
|
diagnostic_cb: Callable[[Diagnostic], None],
|
||||||
|
jobs_count: int, verilator_root: str):
|
||||||
|
prefix = "verilator_clang_check_attributes_"
|
||||||
|
with tempfile.TemporaryDirectory(prefix=prefix) as tmp_dir:
|
||||||
|
with multiprocessing.Pool(
|
||||||
|
processes=jobs_count,
|
||||||
|
initializer=ParallelAnalysisProcess.init_data,
|
||||||
|
initargs=[verilator_root, tmp_dir]) as pool:
|
||||||
|
extra_args = []
|
||||||
|
for pch_file in pool.imap_unordered(
|
||||||
|
ParallelAnalysisProcess.precompile_header, pccl):
|
||||||
|
if pch_file:
|
||||||
|
extra_args += ["-include-pch", pch_file]
|
||||||
|
|
||||||
|
if extra_args:
|
||||||
|
for compile_command in ccl:
|
||||||
|
compile_command.args = compile_command.args + extra_args
|
||||||
|
|
||||||
|
for diags in pool.imap_unordered(
|
||||||
|
ParallelAnalysisProcess.analyze_cpp_file, ccl, 1):
|
||||||
|
for diag in diags:
|
||||||
|
diagnostic_cb(diag)
|
||||||
|
|
||||||
|
|
||||||
|
class TopDownSummaryPrinter():
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FunctionCallees:
|
||||||
|
info: FunctionInfo
|
||||||
|
calees: set[FunctionInfo]
|
||||||
|
mismatch: Optional[FunctionInfo] = None
|
||||||
|
non_mt_safe_call_in_mt_safe: Optional[FunctionInfo] = None
|
||||||
|
non_pure_call_in_pure: Optional[FunctionInfo] = None
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._is_first_group = True
|
||||||
|
|
||||||
|
self._funcs: dict[str, TopDownSummaryPrinter.FunctionCallees] = {}
|
||||||
|
self._unsafe_in_safe: set[str] = set()
|
||||||
|
|
||||||
|
def begin_group(self, label):
|
||||||
|
if not self._is_first_group:
|
||||||
|
print()
|
||||||
|
|
||||||
|
print(f"%Error: {label}")
|
||||||
|
|
||||||
|
self._is_first_group = False
|
||||||
|
|
||||||
|
def handle_diagnostic(self, diag: Diagnostic):
|
||||||
|
usr = diag.source.usr
|
||||||
|
func = self._funcs.get(usr, None)
|
||||||
|
if func is None:
|
||||||
|
func = TopDownSummaryPrinter.FunctionCallees(diag.source, set())
|
||||||
|
self._funcs[usr] = func
|
||||||
|
if diag.kind == DiagnosticKind.ANNOTATIONS_DEF_DECL_MISMATCH:
|
||||||
|
func.mismatch = diag.target
|
||||||
|
else:
|
||||||
|
func.calees.add(diag.target)
|
||||||
|
self._unsafe_in_safe.add(diag.target.usr)
|
||||||
|
if diag.kind == DiagnosticKind.NON_MT_SAFE_CALL_IN_MT_SAFE_CTX:
|
||||||
|
func.non_mt_safe_call_in_mt_safe = diag.target
|
||||||
|
elif diag.kind == DiagnosticKind.NON_PURE_CALL_IN_PURE_CTX:
|
||||||
|
func.non_pure_call_in_pure = diag.target
|
||||||
|
|
||||||
|
def print_summary(self, root_dir: str):
|
||||||
|
row_groups: dict[str, list[list[str]]] = {}
|
||||||
|
column_widths = [0, 0]
|
||||||
|
for func in sorted(self._funcs.values(),
|
||||||
|
key=lambda func:
|
||||||
|
(func.info.file, func.info.line, func.info.usr)):
|
||||||
|
func_info = func.info
|
||||||
|
relfile = os.path.relpath(func_info.file, root_dir)
|
||||||
|
|
||||||
|
row_group = []
|
||||||
|
name = f"\"{func_info.name}\" "
|
||||||
|
if func.mismatch:
|
||||||
|
name += "declaration does not match definition"
|
||||||
|
elif func.non_mt_safe_call_in_mt_safe:
|
||||||
|
name += "is mtsafe but calls non-mtsafe function(s)"
|
||||||
|
elif func.non_pure_call_in_pure:
|
||||||
|
name += "is pure but calls non-pure function(s)"
|
||||||
|
else:
|
||||||
|
name += "for unknown reason (please add description)"
|
||||||
|
|
||||||
|
if func.mismatch:
|
||||||
|
mrelfile = os.path.relpath(func.mismatch.file, root_dir)
|
||||||
|
row_group.append([
|
||||||
|
f"{mrelfile}:{func.mismatch.line}:",
|
||||||
|
f"[{func.mismatch.annotations}]",
|
||||||
|
func.mismatch.name + " [declaration]"
|
||||||
|
])
|
||||||
|
|
||||||
|
row_group.append([
|
||||||
|
f"{relfile}:{func_info.line}:", f"[{func_info.annotations}]",
|
||||||
|
func_info.name
|
||||||
|
])
|
||||||
|
|
||||||
|
for callee in sorted(func.calees,
|
||||||
|
key=lambda func:
|
||||||
|
(func.file, func.line, func.usr)):
|
||||||
|
crelfile = os.path.relpath(callee.file, root_dir)
|
||||||
|
row_group.append([
|
||||||
|
f"{crelfile}:{callee.line}:", f"[{callee.annotations}]",
|
||||||
|
" " + callee.name
|
||||||
|
])
|
||||||
|
|
||||||
|
row_groups[name] = row_group
|
||||||
|
|
||||||
|
for row in row_group:
|
||||||
|
for row_id, value in enumerate(row[0:-1]):
|
||||||
|
column_widths[row_id] = max(column_widths[row_id],
|
||||||
|
len(value))
|
||||||
|
|
||||||
|
for label, rows in sorted(row_groups.items(), key=lambda kv: kv[0]):
|
||||||
|
self.begin_group(label)
|
||||||
|
for row in rows:
|
||||||
|
print(f"{row[0]:<{column_widths[0]}} "
|
||||||
|
f"{row[1]:<{column_widths[1]}} "
|
||||||
|
f"{row[2]}")
|
||||||
|
print(
|
||||||
|
f"Number of functions reported unsafe: {len(self._unsafe_in_safe)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
default_verilator_root = os.path.abspath(
|
||||||
|
os.path.join(os.path.dirname(__file__), ".."))
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
allow_abbrev=False,
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
description="""Check function annotations for correctness""",
|
||||||
|
epilog=
|
||||||
|
"""Copyright 2022 by Wilson Snyder. Verilator is free software; you
|
||||||
|
can redistribute it and/or modify it under the terms of either the GNU
|
||||||
|
Lesser General Public License Version 3 or the Apache License 2.0.
|
||||||
|
SPDX-License-Identifier: LGPL-3.0-only OR Apache-2.0""")
|
||||||
|
|
||||||
|
parser.add_argument("--verilator-root",
|
||||||
|
type=str,
|
||||||
|
default=default_verilator_root,
|
||||||
|
help="Path to Verilator sources root directory.")
|
||||||
|
parser.add_argument("--jobs",
|
||||||
|
"-j",
|
||||||
|
type=int,
|
||||||
|
default=0,
|
||||||
|
help="Number of parallel jobs to use.")
|
||||||
|
parser.add_argument("--cxxflags",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help="Flags passed to clang++.")
|
||||||
|
parser.add_argument(
|
||||||
|
"--compilation-root",
|
||||||
|
type=str,
|
||||||
|
default=os.getcwd(),
|
||||||
|
help="Directory used as CWD when compiling source files.")
|
||||||
|
parser.add_argument(
|
||||||
|
"-c",
|
||||||
|
"--precompile",
|
||||||
|
action="append",
|
||||||
|
help="Header file to be precompiled and cached at the start.")
|
||||||
|
parser.add_argument("file",
|
||||||
|
type=str,
|
||||||
|
nargs="+",
|
||||||
|
help="Source file to analyze.")
|
||||||
|
|
||||||
|
cmdline = parser.parse_args()
|
||||||
|
|
||||||
|
if cmdline.jobs == 0:
|
||||||
|
cmdline.jobs = max(1, len(os.sched_getaffinity(0)))
|
||||||
|
|
||||||
|
if not cmdline.compilation_root:
|
||||||
|
cmdline.compilation_root = cmdline.verilator_root
|
||||||
|
|
||||||
|
verilator_root = os.path.abspath(cmdline.verilator_root)
|
||||||
|
compilation_root = os.path.abspath(cmdline.compilation_root)
|
||||||
|
|
||||||
|
default_cxx_flags = [
|
||||||
|
f"-I{verilator_root}/src",
|
||||||
|
f"-I{verilator_root}/include",
|
||||||
|
f"-I{verilator_root}/src/obj_opt",
|
||||||
|
"-fcoroutines-ts",
|
||||||
|
]
|
||||||
|
if cmdline.cxxflags is not None:
|
||||||
|
cxxflags = shlex.split(cmdline.cxxflags)
|
||||||
|
else:
|
||||||
|
cxxflags = default_cxx_flags
|
||||||
|
|
||||||
|
precompile_commands_list = []
|
||||||
|
|
||||||
|
if cmdline.precompile:
|
||||||
|
hdr_cxxflags = ['-xc++-header'] + cxxflags
|
||||||
|
for refid, file in enumerate(cmdline.precompile):
|
||||||
|
filename = os.path.abspath(file)
|
||||||
|
compile_command = CompileCommand(refid, filename, hdr_cxxflags,
|
||||||
|
compilation_root)
|
||||||
|
precompile_commands_list.append(compile_command)
|
||||||
|
|
||||||
|
compile_commands_list = []
|
||||||
|
for refid, file in enumerate(cmdline.file):
|
||||||
|
filename = os.path.abspath(file)
|
||||||
|
compile_command = CompileCommand(refid, filename, cxxflags,
|
||||||
|
compilation_root)
|
||||||
|
compile_commands_list.append(compile_command)
|
||||||
|
|
||||||
|
summary_printer = TopDownSummaryPrinter()
|
||||||
|
|
||||||
|
if cmdline.jobs == 1:
|
||||||
|
run_analysis(compile_commands_list, precompile_commands_list,
|
||||||
|
summary_printer.handle_diagnostic, verilator_root)
|
||||||
|
else:
|
||||||
|
run_parallel_analysis(compile_commands_list, precompile_commands_list,
|
||||||
|
summary_printer.handle_diagnostic, cmdline.jobs,
|
||||||
|
verilator_root)
|
||||||
|
|
||||||
|
summary_printer.print_summary(verilator_root)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
#!/usr/bin/env perl
|
||||||
|
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
|
||||||
|
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||||
|
#
|
||||||
|
# Copyright 2022 by Wilson Snyder. This program is free software; you
|
||||||
|
# can redistribute it and/or modify it under the terms of either the GNU
|
||||||
|
# Lesser General Public License Version 3 or the Perl Artistic License
|
||||||
|
# Version 2.0.
|
||||||
|
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||||
|
|
||||||
|
scenarios(dist => 1);
|
||||||
|
rerunnable(0);
|
||||||
|
if ($ENV{VERILATOR_TEST_NO_ATTRIBUTES}) {
|
||||||
|
skip("Skipping due to VERILATOR_TEST_NO_ATTRIBUTES");
|
||||||
|
} else {
|
||||||
|
check();
|
||||||
|
}
|
||||||
|
sub check {
|
||||||
|
my $root = "..";
|
||||||
|
# some of the files are only used in verilation
|
||||||
|
# and are only in "include" folder
|
||||||
|
my @srcfiles = glob("$root/include/*.cpp");
|
||||||
|
my $srcfiles_str = join(" ", @srcfiles);
|
||||||
|
my $clang_args = "-I$root/include/ -I$root/include/vltstd/ -fcoroutines-ts";
|
||||||
|
|
||||||
|
sub run_clang_check {
|
||||||
|
{
|
||||||
|
my $cmd = qq{python3 -c "from clang.cindex import Index; index = Index.create(); print(\\"Clang imported\\")";};
|
||||||
|
print "\t$cmd\n" if $::Debug;
|
||||||
|
my $out = `$cmd`;
|
||||||
|
if (!$out || $out !~ /Clang imported/) { skip("No libclang installed\n"); return 1; }
|
||||||
|
}
|
||||||
|
run(logfile => $Self->{run_log_filename},
|
||||||
|
tee => 1,
|
||||||
|
cmd => ["python3", "$root/nodist/clang_check_attributes --verilator-root=$root --cxxflags='$clang_args' $srcfiles_str"]);
|
||||||
|
|
||||||
|
file_grep($Self->{run_log_filename}, "Number of functions reported unsafe: 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
run_clang_check();
|
||||||
|
}
|
||||||
|
|
||||||
|
ok(1);
|
||||||
|
1;
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
#!/usr/bin/env perl
|
||||||
|
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
|
||||||
|
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||||
|
#
|
||||||
|
# Copyright 2022 by Wilson Snyder. This program is free software; you
|
||||||
|
# can redistribute it and/or modify it under the terms of either the GNU
|
||||||
|
# Lesser General Public License Version 3 or the Perl Artistic License
|
||||||
|
# Version 2.0.
|
||||||
|
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||||
|
|
||||||
|
scenarios(dist => 1);
|
||||||
|
rerunnable(0);
|
||||||
|
if ($ENV{VERILATOR_TEST_NO_ATTRIBUTES}) {
|
||||||
|
skip("Skipping due to VERILATOR_TEST_NO_ATTRIBUTES");
|
||||||
|
} else {
|
||||||
|
check();
|
||||||
|
}
|
||||||
|
sub check {
|
||||||
|
my $root = "..";
|
||||||
|
# some of the files are only used in verilation
|
||||||
|
# and are only in "include" folder
|
||||||
|
my @srcfiles = grep { !/\/(V3Const|Vlc\w*|\w*_test|\w*_sc|\w*.yy).cpp$/ }
|
||||||
|
glob("$root/src/*.cpp $root/src/obj_opt/V3Const__gen.cpp");
|
||||||
|
my $srcfiles_str = join(" ", @srcfiles);
|
||||||
|
my $precompile_args = "-c $root/src/V3Ast.h";
|
||||||
|
my $clang_args = "-I$root/src/ -I$root/include/ -I$root/src/obj_opt/ -fcoroutines-ts";
|
||||||
|
|
||||||
|
sub run_clang_check {
|
||||||
|
{
|
||||||
|
my $cmd = qq{python3 -c "from clang.cindex import Index; index = Index.create(); print(\\"Clang imported\\")";};
|
||||||
|
print "\t$cmd\n" if $::Debug;
|
||||||
|
my $out = `$cmd`;
|
||||||
|
if (!$out || $out !~ /Clang imported/) { skip("No libclang installed\n"); return 1; }
|
||||||
|
}
|
||||||
|
run(logfile => $Self->{run_log_filename},
|
||||||
|
tee => 1,
|
||||||
|
cmd => ["python3", "$root/nodist/clang_check_attributes --verilator-root=$root --cxxflags='$clang_args' $precompile_args $srcfiles_str"]);
|
||||||
|
|
||||||
|
file_grep($Self->{run_log_filename}, "Number of functions reported unsafe: 24");
|
||||||
|
}
|
||||||
|
|
||||||
|
run_clang_check();
|
||||||
|
}
|
||||||
|
|
||||||
|
ok(1);
|
||||||
|
1;
|
||||||
|
|
@ -0,0 +1,169 @@
|
||||||
|
#include "verilatedos.h"
|
||||||
|
|
||||||
|
#include "t_dist_attributes_bad.h"
|
||||||
|
|
||||||
|
// Non-Static Functions, Annotated declaration, Unannotated definition.
|
||||||
|
// (definitions)
|
||||||
|
EMIT_ALL(SIG_UNANNOTATED, nsf_au, /**/, {})
|
||||||
|
|
||||||
|
// Non-Static Functions, Unannotated declaration, Annotated definition.
|
||||||
|
// (definitions)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, nsf_ua, /**/, {})
|
||||||
|
|
||||||
|
// Non-Static Functions, Annotated declaration, Annotated definition.
|
||||||
|
// (definitions)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, nsf_aa, /**/, {})
|
||||||
|
|
||||||
|
// Non-Static Functions, Annotated declaration, Annotated definition.
|
||||||
|
// Definitions have extra annotations.
|
||||||
|
// (definitions)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, nsf_ae, /**/, VL_PURE VL_MT_SAFE{})
|
||||||
|
|
||||||
|
// Non-Static Functions, Annotated declaration, Annotated definition.
|
||||||
|
// Declarations have extra annotations.
|
||||||
|
// (definitions)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, nsf_ea, /**/, {})
|
||||||
|
|
||||||
|
// Non-Static Functions (call test).
|
||||||
|
EMIT_ALL(VL_ATTR_UNUSED static SIG_ANNOTATED, nsf_test_caller_func, /**/, {
|
||||||
|
VerilatedMutex m;
|
||||||
|
|
||||||
|
EMIT_ALL(CALL, nsf_au, m, ;)
|
||||||
|
EMIT_ALL(CALL, nsf_ua, m, ;)
|
||||||
|
EMIT_ALL(CALL, nsf_aa, m, ;)
|
||||||
|
EMIT_ALL(CALL, nsf_ae, m, ;)
|
||||||
|
EMIT_ALL(CALL, nsf_ea, m, ;)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Inline Functions in Header (call test).
|
||||||
|
EMIT_ALL(VL_ATTR_UNUSED static SIG_ANNOTATED, ifh_test_caller_func, /**/, {
|
||||||
|
VerilatedMutex m;
|
||||||
|
|
||||||
|
EMIT_ALL(CALL, ifh, m, ;)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Static Functions in Cpp file.
|
||||||
|
EMIT_ALL(inline SIG_ANNOTATED, sfc, /**/, {})
|
||||||
|
|
||||||
|
// Static Functions in Cpp file (call test).
|
||||||
|
EMIT_ALL(VL_ATTR_UNUSED static SIG_ANNOTATED, sfc_test_caller_func, /**/, {
|
||||||
|
VerilatedMutex m;
|
||||||
|
|
||||||
|
EMIT_ALL(CALL, sfc, m, ;)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Static Class Methods, Annotated declaration, Unannotated definition.
|
||||||
|
// (definitions)
|
||||||
|
EMIT_ALL(SIG_UNANNOTATED, TestClass::scm_au, /**/, {})
|
||||||
|
|
||||||
|
// Static Class Methods, Unannotated declaration, Annotated definition.
|
||||||
|
// (definitions)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, TestClass::scm_ua, /**/, {})
|
||||||
|
|
||||||
|
// Static Class Methods, Annotated declaration, Annotated definition.
|
||||||
|
// (definitions)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, TestClass::scm_aa, /**/, {})
|
||||||
|
|
||||||
|
// Static Class Methods, Annotated declaration, Annotated definition.
|
||||||
|
// Definitions have extra annotations.
|
||||||
|
// (definitions)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, TestClass::scm_ae, /**/, VL_PURE VL_MT_SAFE{})
|
||||||
|
|
||||||
|
// Static Class Methods, Annotated declaration, Annotated definition.
|
||||||
|
// Declarations have extra annotations.
|
||||||
|
// (definitions)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, TestClass::scm_ea, /**/, {})
|
||||||
|
|
||||||
|
// Static Class Methods (call test).
|
||||||
|
// (definition)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, TestClass::scm_test_caller_smethod, /**/, {
|
||||||
|
VerilatedMutex m;
|
||||||
|
|
||||||
|
EMIT_ALL(CALL, TestClass::scm_au, m, ;)
|
||||||
|
EMIT_ALL(CALL, TestClass::scm_ua, m, ;)
|
||||||
|
EMIT_ALL(CALL, TestClass::scm_aa, m, ;)
|
||||||
|
EMIT_ALL(CALL, TestClass::scm_ae, m, ;)
|
||||||
|
EMIT_ALL(CALL, TestClass::scm_ea, m, ;)
|
||||||
|
|
||||||
|
TestClass tc;
|
||||||
|
EMIT_ALL(CALL, tc.scm_au, m, ;)
|
||||||
|
EMIT_ALL(CALL, tc.scm_ua, m, ;)
|
||||||
|
EMIT_ALL(CALL, tc.scm_aa, m, ;)
|
||||||
|
EMIT_ALL(CALL, tc.scm_ae, m, ;)
|
||||||
|
EMIT_ALL(CALL, tc.scm_ea, m, ;)
|
||||||
|
|
||||||
|
TestClass* tcp = &tc;
|
||||||
|
EMIT_ALL(CALL, tcp->scm_au, m, ;)
|
||||||
|
EMIT_ALL(CALL, tcp->scm_ua, m, ;)
|
||||||
|
EMIT_ALL(CALL, tcp->scm_aa, m, ;)
|
||||||
|
EMIT_ALL(CALL, tcp->scm_ae, m, ;)
|
||||||
|
EMIT_ALL(CALL, tcp->scm_ea, m, ;)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Inline Static Class Methods (call test).
|
||||||
|
// (definition)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, TestClass::iscm_test_caller_smethod, /**/, {
|
||||||
|
VerilatedMutex m;
|
||||||
|
|
||||||
|
EMIT_ALL(CALL, TestClass::iscm, m, ;)
|
||||||
|
|
||||||
|
TestClass tc;
|
||||||
|
EMIT_ALL(CALL, tc.iscm, m, ;)
|
||||||
|
|
||||||
|
TestClass* tcp = &tc;
|
||||||
|
EMIT_ALL(CALL, tcp->iscm, m, ;)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Class Methods, Annotated declaration, Unannotated definition.
|
||||||
|
// (definitions)
|
||||||
|
EMIT_ALL(SIG_UNANNOTATED, TestClass::cm_au, /**/, {})
|
||||||
|
|
||||||
|
// Class Methods, Unannotated declaration, Annotated definition.
|
||||||
|
// (definitions)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, TestClass::cm_ua, /**/, {})
|
||||||
|
|
||||||
|
// Class Methods, Annotated declaration, Annotated definition.
|
||||||
|
// (definitions)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, TestClass::cm_aa, /**/, {})
|
||||||
|
|
||||||
|
// Class Methods, Annotated declaration, Annotated definition.
|
||||||
|
// Definitions have extra annotations.
|
||||||
|
// (definitions)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, TestClass::cm_ae, /**/, VL_PURE VL_MT_SAFE{})
|
||||||
|
|
||||||
|
// Class Methods, Annotated declaration, Annotated definition.
|
||||||
|
// Declarations have extra annotations.
|
||||||
|
// (definitions)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, TestClass::cm_ea, /**/, {})
|
||||||
|
|
||||||
|
// Class Methods (call test).
|
||||||
|
// (definition)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, TestClass::cm_test_caller_smethod, /**/, {
|
||||||
|
VerilatedMutex m;
|
||||||
|
|
||||||
|
TestClass tc;
|
||||||
|
EMIT_ALL(CALL, tc.cm_au, m, ;)
|
||||||
|
EMIT_ALL(CALL, tc.cm_ua, m, ;)
|
||||||
|
EMIT_ALL(CALL, tc.cm_aa, m, ;)
|
||||||
|
EMIT_ALL(CALL, tc.cm_ae, m, ;)
|
||||||
|
EMIT_ALL(CALL, tc.cm_ea, m, ;)
|
||||||
|
|
||||||
|
TestClass* tcp = &tc;
|
||||||
|
EMIT_ALL(CALL, tcp->cm_au, m, ;)
|
||||||
|
EMIT_ALL(CALL, tcp->cm_ua, m, ;)
|
||||||
|
EMIT_ALL(CALL, tcp->cm_aa, m, ;)
|
||||||
|
EMIT_ALL(CALL, tcp->cm_ae, m, ;)
|
||||||
|
EMIT_ALL(CALL, tcp->cm_ea, m, ;)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Inline Class Methods (call test).
|
||||||
|
// (definition)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, TestClass::icm_test_caller_smethod, /**/, {
|
||||||
|
VerilatedMutex m;
|
||||||
|
|
||||||
|
TestClass tc;
|
||||||
|
EMIT_ALL(CALL, tc.icm, m, ;)
|
||||||
|
|
||||||
|
TestClass* tcp = &tc;
|
||||||
|
EMIT_ALL(CALL, tcp->icm, m, ;)
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,256 @@
|
||||||
|
#ifndef T_DIST_ATTRIBUTES_BAD_H_
|
||||||
|
#define T_DIST_ATTRIBUTES_BAD_H_
|
||||||
|
|
||||||
|
#include "verilatedos.h"
|
||||||
|
|
||||||
|
#include "verilated.h"
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#define NO_ANNOTATION
|
||||||
|
|
||||||
|
#define CALL_0(prefix, annotation, aarg_type, aarg, val) prefix##_##annotation(val)
|
||||||
|
|
||||||
|
#define CALL_1(prefix, annotation, aarg_type, aarg, val) prefix##_##annotation(val)
|
||||||
|
|
||||||
|
#define SIG_ANNOTATED_0(prefix, annotation, aarg_type, aarg, val) \
|
||||||
|
void prefix##_##annotation(aarg_type aarg) annotation
|
||||||
|
|
||||||
|
#define SIG_ANNOTATED_1(prefix, annotation, aarg_type, aarg, val) \
|
||||||
|
void prefix##_##annotation(aarg_type aarg) annotation(aarg)
|
||||||
|
|
||||||
|
#define SIG_UNANNOTATED_0(prefix, annotation, aarg_type, aarg, val) \
|
||||||
|
void prefix##_##annotation(aarg_type aarg)
|
||||||
|
|
||||||
|
#define SIG_UNANNOTATED_1(prefix, annotation, aarg_type, aarg, val) \
|
||||||
|
void prefix##_##annotation(aarg_type aarg)
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
#define EMIT_ALL(before, sig_prefix, val, after) \
|
||||||
|
before##_0(sig_prefix, NO_ANNOTATION, VerilatedMutex&, mtx, val) after \
|
||||||
|
before##_0(sig_prefix, VL_PURE, VerilatedMutex&, mtx, val) after \
|
||||||
|
before##_0(sig_prefix, VL_MT_SAFE, VerilatedMutex&, mtx, val) after \
|
||||||
|
before##_0(sig_prefix, VL_MT_SAFE_POSTINIT, VerilatedMutex&, mtx, val) after \
|
||||||
|
before##_0(sig_prefix, VL_MT_UNSAFE, VerilatedMutex&, mtx, val) after \
|
||||||
|
before##_0(sig_prefix, VL_MT_UNSAFE_ONE, VerilatedMutex&, mtx, val) after \
|
||||||
|
before##_0(sig_prefix, VL_MT_START, VerilatedMutex&, mtx, val) after \
|
||||||
|
before##_1(sig_prefix, VL_ACQUIRE, VerilatedMutex&, mtx, val) after \
|
||||||
|
before##_1(sig_prefix, VL_REQUIRES, VerilatedMutex&, mtx, val) after \
|
||||||
|
before##_1(sig_prefix, VL_RELEASE, VerilatedMutex&, mtx, val) after \
|
||||||
|
before##_1(sig_prefix, VL_ACQUIRE_SHARED, VerilatedMutex&, mtx, val) after \
|
||||||
|
before##_1(sig_prefix, VL_RELEASE_SHARED, VerilatedMutex&, mtx, val) after \
|
||||||
|
before##_1(sig_prefix, VL_EXCLUDES, VerilatedMutex&, mtx, val) after \
|
||||||
|
before##_1(sig_prefix, VL_MT_SAFE_EXCLUDES, VerilatedMutex&, mtx, val) after
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
// Non-Static Functions, Annotated declaration, Unannotated definition.
|
||||||
|
// (declarations)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, nsf_au, /**/, ;)
|
||||||
|
|
||||||
|
// Non-Static Functions, Unannotated declaration, Annotated definition.
|
||||||
|
// (declarations)
|
||||||
|
EMIT_ALL(SIG_UNANNOTATED, nsf_ua, /**/, ;)
|
||||||
|
|
||||||
|
// Non-Static Functions, Annotated declaration, Annotated definition.
|
||||||
|
// (declarations)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, nsf_aa, /**/, ;)
|
||||||
|
|
||||||
|
// Non-Static Functions, Annotated declaration, Annotated definition.
|
||||||
|
// Definitions have extra annotations.
|
||||||
|
// (declarations)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, nsf_ae, /**/, ;)
|
||||||
|
|
||||||
|
// Non-Static Functions, Annotated declaration, Annotated definition.
|
||||||
|
// Declarations have extra annotations.
|
||||||
|
// (declarations)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, nsf_ea, /**/, VL_PURE VL_MT_SAFE;)
|
||||||
|
|
||||||
|
// Non-Static Functions (call test in header).
|
||||||
|
EMIT_ALL(inline SIG_ANNOTATED, nsf_test_caller_func_hdr, /**/, {
|
||||||
|
VerilatedMutex m;
|
||||||
|
|
||||||
|
EMIT_ALL(CALL, nsf_au, m, ;)
|
||||||
|
EMIT_ALL(CALL, nsf_ua, m, ;)
|
||||||
|
EMIT_ALL(CALL, nsf_aa, m, ;)
|
||||||
|
EMIT_ALL(CALL, nsf_ae, m, ;)
|
||||||
|
EMIT_ALL(CALL, nsf_ea, m, ;)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Inline Functions in Header.
|
||||||
|
EMIT_ALL(inline SIG_ANNOTATED, ifh, /**/, {})
|
||||||
|
|
||||||
|
// Inline Functions in Header (call test in header).
|
||||||
|
EMIT_ALL(inline SIG_ANNOTATED, ifh_test_caller_func_hdr, /**/, {
|
||||||
|
VerilatedMutex m;
|
||||||
|
|
||||||
|
EMIT_ALL(CALL, ifh, m, ;)
|
||||||
|
})
|
||||||
|
|
||||||
|
struct GuardMe {
|
||||||
|
void safe_if_guarded_or_local() {}
|
||||||
|
|
||||||
|
operator int() const { return 4; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class TestClass {
|
||||||
|
VerilatedMutex m_mtx;
|
||||||
|
GuardMe m_guardme VL_GUARDED_BY(m_mtx);
|
||||||
|
|
||||||
|
GuardMe m_guardme_unguarded;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Static Class Methods, Annotated declaration, Unannotated definition.
|
||||||
|
// (declarations)
|
||||||
|
EMIT_ALL(static SIG_ANNOTATED, scm_au, /**/, ;)
|
||||||
|
|
||||||
|
// Static Class Methods, Unannotated declaration, Annotated definition.
|
||||||
|
// (declarations)
|
||||||
|
EMIT_ALL(static SIG_UNANNOTATED, scm_ua, /**/, ;)
|
||||||
|
|
||||||
|
// Static Class Methods, Annotated declaration, Annotated definition.
|
||||||
|
// (declarations)
|
||||||
|
EMIT_ALL(static SIG_ANNOTATED, scm_aa, /**/, ;)
|
||||||
|
|
||||||
|
// Static Class Methods, Annotated declaration, Annotated definition.
|
||||||
|
// Definitions have extra annotations.
|
||||||
|
// (declarations)
|
||||||
|
EMIT_ALL(static SIG_ANNOTATED, scm_ae, /**/, ;)
|
||||||
|
|
||||||
|
// Static Class Methods, Annotated declaration, Annotated definition.
|
||||||
|
// Declarations have extra annotations.
|
||||||
|
// (declarations)
|
||||||
|
EMIT_ALL(static SIG_ANNOTATED, scm_ea, /**/, VL_PURE VL_MT_SAFE;)
|
||||||
|
|
||||||
|
// Static Class Methods (call test in header).
|
||||||
|
EMIT_ALL(static SIG_ANNOTATED, scm_test_caller_smethod_hdr, /**/, {
|
||||||
|
VerilatedMutex m;
|
||||||
|
|
||||||
|
EMIT_ALL(CALL, TestClass::scm_au, m, ;)
|
||||||
|
EMIT_ALL(CALL, TestClass::scm_ua, m, ;)
|
||||||
|
EMIT_ALL(CALL, TestClass::scm_aa, m, ;)
|
||||||
|
EMIT_ALL(CALL, TestClass::scm_ae, m, ;)
|
||||||
|
EMIT_ALL(CALL, TestClass::scm_ea, m, ;)
|
||||||
|
|
||||||
|
TestClass tc;
|
||||||
|
EMIT_ALL(CALL, tc.scm_au, m, ;)
|
||||||
|
EMIT_ALL(CALL, tc.scm_ua, m, ;)
|
||||||
|
EMIT_ALL(CALL, tc.scm_aa, m, ;)
|
||||||
|
EMIT_ALL(CALL, tc.scm_ae, m, ;)
|
||||||
|
EMIT_ALL(CALL, tc.scm_ea, m, ;)
|
||||||
|
|
||||||
|
TestClass* tcp = &tc;
|
||||||
|
EMIT_ALL(CALL, tcp->scm_au, m, ;)
|
||||||
|
EMIT_ALL(CALL, tcp->scm_ua, m, ;)
|
||||||
|
EMIT_ALL(CALL, tcp->scm_aa, m, ;)
|
||||||
|
EMIT_ALL(CALL, tcp->scm_ae, m, ;)
|
||||||
|
EMIT_ALL(CALL, tcp->scm_ea, m, ;)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Static Class Methods (call test).
|
||||||
|
// (declaration)
|
||||||
|
EMIT_ALL(static SIG_ANNOTATED, scm_test_caller_smethod, /**/, ;)
|
||||||
|
|
||||||
|
// Inline Static Class Methods.
|
||||||
|
EMIT_ALL(static SIG_ANNOTATED, iscm, /**/, {})
|
||||||
|
|
||||||
|
// Inline Static Class Methods (call test in header).
|
||||||
|
EMIT_ALL(static SIG_ANNOTATED, iscm_test_caller_smethod_hdr, /**/, {
|
||||||
|
VerilatedMutex m;
|
||||||
|
|
||||||
|
EMIT_ALL(CALL, TestClass::iscm, m, ;)
|
||||||
|
|
||||||
|
TestClass tc;
|
||||||
|
EMIT_ALL(CALL, tc.iscm, m, ;)
|
||||||
|
|
||||||
|
TestClass* tcp = &tc;
|
||||||
|
EMIT_ALL(CALL, tcp->iscm, m, ;)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Inline Static Class Methods (call test).
|
||||||
|
// (declaration)
|
||||||
|
EMIT_ALL(static SIG_ANNOTATED, iscm_test_caller_smethod, /**/, ;)
|
||||||
|
|
||||||
|
// Class Methods, Annotated declaration, Unannotated definition.
|
||||||
|
// (declarations)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, cm_au, /**/, ;)
|
||||||
|
|
||||||
|
// Class Methods, Unannotated declaration, Annotated definition.
|
||||||
|
// (declarations)
|
||||||
|
EMIT_ALL(SIG_UNANNOTATED, cm_ua, /**/, ;)
|
||||||
|
|
||||||
|
// Class Methods, Annotated declaration, Annotated definition.
|
||||||
|
// (declarations)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, cm_aa, /**/, ;)
|
||||||
|
|
||||||
|
// Class Methods, Annotated declaration, Annotated definition.
|
||||||
|
// Definitions have extra annotations.
|
||||||
|
// (declarations)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, cm_ae, /**/, ;)
|
||||||
|
|
||||||
|
// Class Methods, Annotated declaration, Annotated definition.
|
||||||
|
// Declarations have extra annotations.
|
||||||
|
// (declarations)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, cm_ea, /**/, VL_PURE VL_MT_SAFE;)
|
||||||
|
|
||||||
|
// Class Methods (call test in header).
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, cm_test_caller_smethod_hdr, /**/, {
|
||||||
|
VerilatedMutex m;
|
||||||
|
|
||||||
|
TestClass tc;
|
||||||
|
EMIT_ALL(CALL, tc.cm_au, m, ;)
|
||||||
|
EMIT_ALL(CALL, tc.cm_ua, m, ;)
|
||||||
|
EMIT_ALL(CALL, tc.cm_aa, m, ;)
|
||||||
|
EMIT_ALL(CALL, tc.cm_ae, m, ;)
|
||||||
|
EMIT_ALL(CALL, tc.cm_ea, m, ;)
|
||||||
|
|
||||||
|
TestClass* tcp = &tc;
|
||||||
|
EMIT_ALL(CALL, tcp->cm_au, m, ;)
|
||||||
|
EMIT_ALL(CALL, tcp->cm_ua, m, ;)
|
||||||
|
EMIT_ALL(CALL, tcp->cm_aa, m, ;)
|
||||||
|
EMIT_ALL(CALL, tcp->cm_ae, m, ;)
|
||||||
|
EMIT_ALL(CALL, tcp->cm_ea, m, ;)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Class Methods (call test).
|
||||||
|
// (declaration)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, cm_test_caller_smethod, /**/, ;)
|
||||||
|
|
||||||
|
// Inline Class Methods.
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, icm, /**/, {})
|
||||||
|
|
||||||
|
// Inline Class Methods (call test in header).
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, icm_test_caller_smethod_hdr, /**/, {
|
||||||
|
VerilatedMutex m;
|
||||||
|
|
||||||
|
TestClass tc;
|
||||||
|
EMIT_ALL(CALL, tc.icm, m, ;)
|
||||||
|
|
||||||
|
TestClass* tcp = &tc;
|
||||||
|
EMIT_ALL(CALL, tcp->icm, m, ;)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Inline Class Methods (call test).
|
||||||
|
// (declaration)
|
||||||
|
EMIT_ALL(SIG_ANNOTATED, icm_test_caller_smethod, /**/, ;)
|
||||||
|
|
||||||
|
void guarded_by_test_pass(GuardMe& guardme_arg) VL_MT_SAFE {
|
||||||
|
guardme_arg.safe_if_guarded_or_local();
|
||||||
|
int a = guardme_arg;
|
||||||
|
|
||||||
|
m_mtx.lock();
|
||||||
|
m_guardme.safe_if_guarded_or_local();
|
||||||
|
int b = m_guardme;
|
||||||
|
m_mtx.unlock();
|
||||||
|
|
||||||
|
GuardMe guardme_local;
|
||||||
|
guardme_local.safe_if_guarded_or_local();
|
||||||
|
int c = guardme_local;
|
||||||
|
}
|
||||||
|
|
||||||
|
void guarded_by_test_fail() VL_MT_SAFE {
|
||||||
|
m_guardme_unguarded.safe_if_guarded_or_local();
|
||||||
|
int a = m_guardme_unguarded;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // T_DIST_ATTRIBUTES_BAD_H_
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,44 @@
|
||||||
|
#!/usr/bin/env perl
|
||||||
|
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
|
||||||
|
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||||
|
#
|
||||||
|
# Copyright 2022 by Wilson Snyder. This program is free software; you
|
||||||
|
# can redistribute it and/or modify it under the terms of either the GNU
|
||||||
|
# Lesser General Public License Version 3 or the Perl Artistic License
|
||||||
|
# Version 2.0.
|
||||||
|
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||||
|
|
||||||
|
scenarios(dist => 1);
|
||||||
|
if ($ENV{VERILATOR_TEST_NO_ATTRIBUTES}) {
|
||||||
|
skip("Skipping due to VERILATOR_TEST_NO_ATTRIBUTES");
|
||||||
|
} else {
|
||||||
|
check();
|
||||||
|
}
|
||||||
|
sub check {
|
||||||
|
my $root = "..";
|
||||||
|
my @srcfiles = glob("$root/test_regress/t/t_dist_attributes_bad.cpp");
|
||||||
|
my $srcfiles_str = join(" ", @srcfiles);
|
||||||
|
my $clang_args = "-I$root/include";
|
||||||
|
|
||||||
|
sub run_clang_check {
|
||||||
|
{
|
||||||
|
my $cmd = qq{python3 -c "from clang.cindex import Index; index = Index.create(); print(\\"Clang imported\\")";};
|
||||||
|
print "\t$cmd\n" if $::Debug;
|
||||||
|
my $out = `$cmd`;
|
||||||
|
if (!$out || $out !~ /Clang imported/) { skip("No libclang installed\n"); return 1; }
|
||||||
|
}
|
||||||
|
run(logfile => $Self->{run_log_filename},
|
||||||
|
tee => 1,
|
||||||
|
# With `--verilator-root` set to the current directory
|
||||||
|
# (i.e. `test_regress`) the script will skip annotation issues in
|
||||||
|
# headers from the `../include` directory.
|
||||||
|
cmd => ["python3", "$root/nodist/clang_check_attributes --verilator-root=. --cxxflags='$clang_args' $srcfiles_str"]);
|
||||||
|
|
||||||
|
files_identical($Self->{run_log_filename}, $Self->{golden_filename});
|
||||||
|
}
|
||||||
|
|
||||||
|
run_clang_check();
|
||||||
|
}
|
||||||
|
|
||||||
|
ok(1);
|
||||||
|
1;
|
||||||
Loading…
Reference in New Issue