test/acceptance/sizes 3.6 KiB raw
1
#!/usr/bin/env python3
2
"""Check kernel source limits and report the built image's fixed memory."""
3
4
import pathlib
5
import struct
6
import sys
7
8
9
def source_sizes(root):
10
    """Count physical source lines and combine files that form one module."""
11
    totals = {"runtime": 0, "tests": 0, "tools": 0}
12
    modules = {}
13
    for path in sorted(root.rglob("*")):
14
        if path.suffix not in (".rad", ".ras"):
15
            continue
16
        relative = path.relative_to(root)
17
        category = "runtime"
18
        if "tests" in relative.parts or path.stem == "tests":
19
            category = "tests"
20
        elif relative.parts[0] == "tools":
21
            category = "tools"
22
        lines = len(path.read_bytes().splitlines())
23
        totals[category] += lines
24
        if category != "tests":
25
            module = str(relative.with_suffix(""))
26
            modules[module] = modules.get(module, 0) + lines
27
    if not totals["runtime"]:
28
        raise ValueError("kernel runtime sources are missing")
29
    for module, lines in sorted(modules.items()):
30
        print(f"module {module}: {lines} source lines")
31
        if lines >= 1000:
32
            raise ValueError(f"module {module} must have fewer than 1000 lines")
33
    for category, lines in totals.items():
34
        print(f"kernel {category}: {lines} source lines")
35
    if totals["runtime"] > 12000:
36
        raise ValueError("kernel runtime exceeds 12000 source lines")
37
38
39
def image_sizes(path):
40
    """Validate the native image extents and report each fixed memory segment."""
41
    with path.open("rb") as stream:
42
        header = stream.read(64)
43
    if len(header) != 64:
44
        raise ValueError("native image header is truncated")
45
    magic, version, entry = struct.unpack_from("<IIQ", header)
46
    if magic != 0x30444152 or version != 2:
47
        raise ValueError("expected a version 2 native image")
48
    segments = []
49
    payload = 64
50
    for index, name in enumerate(("code", "read-only", "writable")):
51
        address, initialized, memory = struct.unpack_from("<QII", header, 16 + index * 16)
52
        alignment = 4 if index == 0 else 8
53
        if initialized > memory or address + memory > 1 << 64:
54
            raise ValueError(f"invalid {name} segment extent")
55
        if memory and address % alignment:
56
            raise ValueError(f"misaligned {name} segment")
57
        segments.append((address, initialized, memory))
58
        payload += initialized
59
        print(f"image {name}: {memory} fixed bytes, {initialized} initialized bytes")
60
    code, initialized, _ = segments[0]
61
    if entry % 4 or initialized % 4 or not code <= entry < code + initialized:
62
        raise ValueError("entry is outside initialized instructions")
63
    for index, (start, _, size) in enumerate(segments):
64
        for other, _, length in segments[index + 1:]:
65
            if size and length and start < other + length and other < start + size:
66
                raise ValueError("native image segments overlap")
67
    if path.stat().st_size != payload:
68
        raise ValueError("native image payload size does not match its header")
69
    print(f"image total: {sum(segment[2] for segment in segments)} fixed bytes")
70
71
72
def main():
73
    """Check the worktree, or explicit source and image paths for a fixture."""
74
    if len(sys.argv) not in (1, 3):
75
        raise ValueError("usage: sizes [kernel-directory native-image]")
76
    root = pathlib.Path(sys.argv[1] if len(sys.argv) == 3 else "kernel")
77
    image = pathlib.Path(sys.argv[2] if len(sys.argv) == 3 else "bin/kernel.rv64")
78
    source_sizes(root)
79
    image_sizes(image)
80
81
82
if __name__ == "__main__":
83
    try:
84
        main()
85
    except (OSError, ValueError) as error:
86
        print(f"kernel size check: {error}", file=sys.stderr)
87
        sys.exit(1)