#!/usr/bin/env python3 # MobileGL - scripts/gen_pipe_field_ownership.py # Copyright (c) 2025-2026 MobileGL-Dev # Licensed under the GNU Lesser General Public License v3.0: # https://www.gnu.org/licenses/gpl-3.0.txt # https://www.gnu.org/licenses/lgpl-3.0.txt # SPDX-License-Identifier: LGPL-3.0-only # End of Source File Header """TABLE 2 - where every PipeInputs field's value comes from once the backend is a server. CONTRACT-P5.md section 3 / BRIEF-P5 R-7. Reads MobileGL/MG_Pipe/Coverage.def the 63 accessors, the 7 sticky, the 40 emitted MobileGL/MG_Impl/Pipe/PipeFill.cpp EmittedCallSuppliesTheWholeField's refusals MobileGL/MG_Pipe/FieldOwnership.def the hand-maintained half and writes MobileGL/MG_Pipe/generated/PipeFieldOwnership.inc. The output is COMMITTED; CI regenerates it and fails on a diff, exactly as gen_pipe.py's seven outputs do. ONE CLASS PER ROW, AND THE BUILD FAILS OTHERWISE. 63 field rows + the 7 sticky forwards = 70, each in exactly one of RECORD_SUPPLIED / APPLIER_DERIVED / BARRIER_PULLED / FATAL. A field in none of them stops this script, so the committed header can never contain an unclassified row and --check is what catches a stale one. RECORD_SUPPLIED is DERIVED rather than listed - it is Coverage.def's emitted list minus PipeFill.cpp's refusals - so the mapping file cannot drift away from the emitters it describes without a red gate. python3 scripts/gen_pipe_field_ownership.py # write it, print the summary python3 scripts/gen_pipe_field_ownership.py --check # THE GATE: rc 1 on any hole python3 scripts/gen_pipe_field_ownership.py --self-test # the gate's own negative controls """ import argparse import os import re import sys REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) PIPE_DIR = os.path.join(REPO_ROOT, "MobileGL", "MG_Pipe") GENERATED_DIR = os.path.join(PIPE_DIR, "generated") COVERAGE_DEF = os.path.join(PIPE_DIR, "Coverage.def") OWNERSHIP_DEF = os.path.join(PIPE_DIR, "FieldOwnership.def") PIPE_FILL = os.path.join(REPO_ROOT, "MobileGL", "MG_Impl", "Pipe", "PipeFill.cpp") PIPE_CALLS = os.path.join(PIPE_DIR, "PipeCalls.def") FILL_POINTS = os.path.join(PIPE_DIR, "FillPoints.def") OUT_NAME = "PipeFieldOwnership.inc" CLASSES = ("RECORD_SUPPLIED", "APPLIER_DERIVED", "BARRIER_PULLED", "FATAL") ENUMERATOR = { "RECORD_SUPPLIED": "kRecordSupplied", "APPLIER_DERIVED": "kApplierDerived", "BARRIER_PULLED": "kBarrierPulled", "FATAL": "kFatal", } BANNER = """// MobileGL - MobileGL/MG_Pipe/generated/{name} // Copyright (c) 2025-2026 MobileGL-Dev // Licensed under the GNU Lesser General Public License v3.0: // https://www.gnu.org/licenses/gpl-3.0.txt // https://www.gnu.org/licenses/lgpl-3.0.txt // SPDX-License-Identifier: LGPL-3.0-only // End of Source File Header // TABLE 2: PipeInputs field ownership (CONTRACT-P5.md section 3, R-7). // // GENERATED by scripts/gen_pipe_field_ownership.py from Coverage.def, FieldOwnership.def and // MG_Impl/Pipe/PipeFill.cpp - DO NOT EDIT. Regenerate with // `python3 scripts/gen_pipe_field_ownership.py`; CI runs it and diffs the result. // // Included from MG_Backend/MGPipe/PipeInputs.h inside namespace MobileGL::MG_Pipe, which is // the one header that both the poison check and the server's verb stamp already see. It is // NOT included from MG_Pipe/MGPipe.h with the other seven generated files, on purpose: that // header is in the PULL build's include closure and G1 admits no symbol motion there. """ def read(path): with open(path, "r", encoding="utf-8") as handle: return handle.read() def mask_comments(text): """Blank comment bodies, keeping every offset and newline. String literals are LEFT ALONE - unlike gen_pipe_dirty_surface.py's masker - because this file's rows carry their retiring phase and their reason as quoted arguments.""" out = list(text) i = 0 n = len(text) while i < n: if text[i] == "/" and i + 1 < n and text[i + 1] == "/": while i < n and text[i] != "\n": out[i] = " " i += 1 elif text[i] == "/" and i + 1 < n and text[i + 1] == "*": out[i] = out[i + 1] = " " i += 2 while i < n and not (text[i] == "*" and i + 1 < n and text[i + 1] == "/"): if text[i] != "\n": out[i] = " " i += 1 if i < n: out[i] = out[i + 1] = " " i += 2 else: i += 1 return "".join(out) def macro_block(text, name): """The body of a `#define (X) ...` continued-line macro, comments blanked.""" masked = mask_comments(text) start = masked.find("#define %s(X)" % name) if start < 0: sys.exit("gen_pipe_field_ownership: %s is not in the file" % name) end = start while True: line_end = masked.find("\n", end) if line_end < 0: body = masked[start:] break if not masked[end:line_end].rstrip().endswith("\\"): body = masked[start:line_end] break end = line_end + 1 # The rows wrap, so the continuation backslashes have to go before a row regex can see a # row as one thing. Offsets do not matter here; only the token sequence does. return body.replace("\\\n", "\n") def parse_coverage(text=None): """The ordered 63 accessors, the 7 sticky and the 40 emitted, out of Coverage.def.""" text = read(COVERAGE_DEF) if text is None else text accessors = re.findall(r"X\(\s*(\w+)\s*,\s*\w+\s*\)", macro_block(text, "MGP_COVERAGE_ACCESSOR_LIST")) sticky = re.findall(r"X\(\s*(\w+)\s*,", macro_block(text, "MGP_COVERAGE_STICKY_LIST")) emitted = re.findall(r"X\(\s*(\w+)\s*,\s*\w+\s*\)", macro_block(text, "MGP_COVERAGE_EMITTED_LIST")) if not accessors: sys.exit("gen_pipe_field_ownership: Coverage.def's accessor list did not parse") seen = set() for name in accessors: if name in seen: sys.exit("gen_pipe_field_ownership: %s appears twice in the accessor list" % name) seen.add(name) for name in sticky + emitted: if name not in seen: sys.exit("gen_pipe_field_ownership: %s is not an accessor in MGP_COVERAGE_ACCESSOR_LIST" % name) return accessors, sticky, emitted def parse_supplies_whole_field(text=None): """The fields EmittedCallSuppliesTheWholeField REFUSES, read out of PipeFill.cpp rather than restated here - the derivation that keeps RECORD_SUPPLIED honest is only as good as its source, so the source is the function itself.""" text = read(PIPE_FILL) if text is None else text masked = mask_comments(text) start = masked.find("Bool EmittedCallSuppliesTheWholeField(MGPipeInputField field)") if start < 0: sys.exit("gen_pipe_field_ownership: EmittedCallSuppliesTheWholeField is not in %s" % os.path.basename(PIPE_FILL)) false_at = masked.find("return false;", start) if false_at < 0: sys.exit("gen_pipe_field_ownership: EmittedCallSuppliesTheWholeField has no `return false` arm") refused = re.findall(r"case\s+MGPipeInputField::(\w+)\s*:", masked[start:false_at]) if not refused: sys.exit("gen_pipe_field_ownership: EmittedCallSuppliesTheWholeField refuses nothing - " "either the function moved or the parse broke; a silently empty refusal set " "would make every emitted field RECORD_SUPPLIED") return refused ROW_RE = re.compile(r"X\(\s*(\w+)\s*,\s*(\w+)\s*,\s*\"([^\"]*)\"\s*,\s*\"([^\"]*)\"\s*\)") ARG_ROW_RE = re.compile(r"X\(\s*(\w+)\s*,\s*(\d+)\s*,\s*(\w+)\s*,\s*\"([^\"]*)\"\s*\)") PAIR_RE = re.compile(r"X\(\s*(\w+)\s*,\s*(\w+)\s*\)") def parse_ownership(text=None): text = read(OWNERSHIP_DEF) if text is None else text fields = ROW_RE.findall(macro_block(text, "MGP_FIELD_OWNERSHIP_LIST")) forwards = ROW_RE.findall(macro_block(text, "MGP_FIELD_OWNERSHIP_FORWARD_LIST")) args = ARG_ROW_RE.findall(macro_block(text, "MGP_FIELD_OWNERSHIP_ARG_LIST")) verb_ops = PAIR_RE.findall(macro_block(text, "MGP_VERB_OP_LIST")) if not fields: sys.exit("gen_pipe_field_ownership: FieldOwnership.def's field list did not parse") return fields, forwards, args, verb_ops def parse_ops_and_verbs(calls_text=None, fill_points_text=None): """The two name spaces the stamp map joins, read from the files that define them: the catalogue's calls (PipeCalls.def, which IS the MGPWireOp enum) and the verb set (FillPoints.def, which IS MG_Backend::GLFunctionsTable's member list).""" calls_text = read(PIPE_CALLS) if calls_text is None else calls_text fill_points_text = read(FILL_POINTS) if fill_points_text is None else fill_points_text ops = re.findall(r"X\(\s*(\w+)\s*,\s*\w+\s*,\s*\w+\s*,", macro_block(calls_text, "MGP_CALL_LIST")) verbs = re.findall(r"X\(\s*(\w+)\s*,\s*(k\w+)\s*\)", macro_block(fill_points_text, "MGP_FILL_VERB_LIST")) if not ops: sys.exit("gen_pipe_field_ownership: PipeCalls.def's call list did not parse") if not verbs: sys.exit("gen_pipe_field_ownership: FillPoints.def's verb list did not parse") return ops, [v for v, _ in verbs] def check_verb_ops(verb_ops, ops, verbs): """The stamp map, against both name spaces. A row whose op or verb does not exist would otherwise become a switch arm that fails to compile minutes later - or, worse, a silently absent stamp point.""" if not verb_ops: sys.exit("gen_pipe_field_ownership: MGP_VERB_OP_LIST is empty - the server would have " "no verb boundary to stamp at and every field would read @") seen = set() for op, verb in verb_ops: if op not in ops: sys.exit("gen_pipe_field_ownership: the stamp map names op %s, which is not a call " "in PipeCalls.def" % op) if verb not in verbs: sys.exit("gen_pipe_field_ownership: the stamp map names verb %s, which is not a " "verb in FillPoints.def" % verb) if op in seen: sys.exit("gen_pipe_field_ownership: op %s has two stamp rows" % op) seen.add(op) return verb_ops def build(accessors, sticky, emitted, refused, rows, forwards, args): """The join, and every gate it is allowed to fail on. Returns (ownership, phase, why, forward rows, argument rows, counts).""" supplied = [f for f in emitted if f not in set(refused)] ownership = {} phase = {} why = {} for field in supplied: ownership[field] = "RECORD_SUPPLIED" phase[field] = "-" why[field] = "derived: Coverage.def's emitted list, and PipeFill.cpp does not refuse it" for field, cls, retires, reason in rows: if field not in accessors: sys.exit("gen_pipe_field_ownership: FieldOwnership.def names %s, which is not a " "PipeInputs field" % field) if cls not in CLASSES: sys.exit("gen_pipe_field_ownership: %s is in class %s, which is not one of %s" % (field, cls, "/".join(CLASSES))) if cls == "RECORD_SUPPLIED": sys.exit("gen_pipe_field_ownership: %s claims RECORD_SUPPLIED, which is DERIVED and " "may not be asserted by hand" % field) if field in ownership: sys.exit("gen_pipe_field_ownership: %s is in TWO classes - the derivation says " "RECORD_SUPPLIED and FieldOwnership.def says %s" % (field, cls)) if cls == "BARRIER_PULLED" and retires.strip() in ("", "-"): sys.exit("gen_pipe_field_ownership: %s is BARRIER_PULLED and names no retiring " "phase - P5's debt is only sized if every row says who pays it" % field) if cls != "BARRIER_PULLED" and retires.strip() != "-": sys.exit("gen_pipe_field_ownership: %s is %s and names a retiring phase; only " "BARRIER_PULLED rows have one" % (field, cls)) ownership[field] = cls phase[field] = retires why[field] = reason missing = [f for f in accessors if f not in ownership] if missing: sys.exit("gen_pipe_field_ownership: %d field(s) in NO class, which is a build failure " "(R-7.1): %s" % (len(missing), ", ".join(missing))) # The seven sticky forwards' own rows. They are the seven that hand the server a frontend # object or write into the frontend, so the gate is structurally blind on them without a # row of their own - and the row has to agree with the field row, or the two halves of the # same accessor would be documented as different things. forward_map = {} for field, cls, retires, mechanism in forwards: if field not in sticky: sys.exit("gen_pipe_field_ownership: %s has a FORWARD row but is not sticky in " "Coverage.def" % field) if field in forward_map: sys.exit("gen_pipe_field_ownership: %s has two FORWARD rows" % field) if cls not in CLASSES: sys.exit("gen_pipe_field_ownership: forward %s is in class %s, which is not one of " "%s" % (field, cls, "/".join(CLASSES))) if ownership[field] != cls: sys.exit("gen_pipe_field_ownership: %s's field row says %s and its forward row says " "%s; a read of a sticky field IS a call of its forward" % (field, ownership[field], cls)) forward_map[field] = (cls, retires, mechanism) absent = [f for f in sticky if f not in forward_map] if absent: sys.exit("gen_pipe_field_ownership: sticky forward(s) with no row: %s" % ", ".join(absent)) arg_rows = [] for field, arg0, cls, reason in args: if field not in accessors: sys.exit("gen_pipe_field_ownership: argument exception names %s, which is not a " "PipeInputs field" % field) if cls not in CLASSES: sys.exit("gen_pipe_field_ownership: argument exception %s(%s) is in class %s" % (field, arg0, cls)) if cls == ownership[field]: sys.exit("gen_pipe_field_ownership: argument exception %s(%s) repeats the field's " "own class (%s) and narrows nothing" % (field, arg0, cls)) arg_rows.append((field, int(arg0), cls, reason)) counts = {cls: sum(1 for f in accessors if ownership[f] == cls) for cls in CLASSES} return ownership, phase, why, forward_map, arg_rows, counts def emit(accessors, sticky, ownership, phase, why, forward_map, arg_rows, counts, verb_ops): out = [BANNER.format(name=OUT_NAME)] add = out.append add(""" // The four classes. kUnclassified exists so the static_assert below has something to refuse; // the generator never emits it, which is what makes "a field in no class fails the build" // true at two independent points rather than one. enum class MGPipeFieldOwnership : Uint8 { kUnclassified = 0, kRecordSupplied, // a pushed record supplies the WHOLE field kApplierDerived, // the applier writes it out of records it already applies kBarrierPulled, // P5's debt: read out of the client's residual fill under the verb barrier kFatal, // no carrier and the reduced path never reads it }; inline constexpr const char* kMGPipeFieldOwnershipNames[] = { "UNCLASSIFIED", "RECORD-SUPPLIED", "APPLIER-DERIVED", "BARRIER-PULLED", "FATAL", }; """) add("inline constexpr MGPipeFieldOwnership kMGPipeFieldOwnership[kMGPipeInputFieldCount] = {") for field in accessors: add(" MGPipeFieldOwnership::%s, // %s" % (ENUMERATOR[ownership[field]], field)) add("};\n") add("// The ROADMAP phase whose row retires the pull. \"-\" for every class but BARRIER-PULLED.") add("inline constexpr const char* kMGPipeFieldRetiringPhase[kMGPipeInputFieldCount] = {") for field in accessors: add(" \"%s\", // %s" % (phase[field], field)) add("};\n") add("// The seven sticky forwards, which are among the 63 above and need a row of their own:") add("// they are the ones that hand the server a raw frontend object or write into the") add("// frontend, so the exit gate is structurally blind on them without one.") add("inline constexpr SizeT kMGPipeFieldOwnershipForwardCount = %d;" % len(sticky)) add("static_assert(kMGPipeFieldOwnershipForwardCount == kMGPipeInputStickyFieldCount,") add(" \"the forward rows and Coverage.def's sticky set are the same seven\");") add("inline constexpr MGPipeInputField kMGPipeFieldOwnershipForwardField[kMGPipeFieldOwnershipForwardCount] = {") for field in sticky: add(" MGPipeInputField::%s," % field) add("};") add("inline constexpr MGPipeFieldOwnership kMGPipeFieldOwnershipForward[kMGPipeFieldOwnershipForwardCount] = {") for field in sticky: add(" MGPipeFieldOwnership::%s, // %s" % (ENUMERATOR[forward_map[field][0]], field)) add("};") add("inline constexpr const char* kMGPipeFieldOwnershipForwardMechanism[kMGPipeFieldOwnershipForwardCount] = {") for field in sticky: add(" \"%s\", // %s, retires in %s" % (forward_map[field][2], field, forward_map[field][1])) add("};\n") add("// CONTRACT-P5.md section 3: \"70 rows, each in exactly one class\".") add("inline constexpr SizeT kMGPipeFieldOwnershipRowCount =") add(" kMGPipeInputFieldCount + kMGPipeFieldOwnershipForwardCount;") add("static_assert(kMGPipeFieldOwnershipRowCount == 70, \"table 2's row count moved\");\n") add("""// An ARGUMENT-KEYED narrowing of one field. The field keeps its single row above; this // says that one argument value of it belongs to a different class. Coverage.def:62-69 already // rules the shape for GetBufferBindingSlot - "THE ROW STAYS ONE ROW ... the field is ONE array // that a second row of the same name could only duplicate" - and m_pixelStore[2] is the same // shape indexed by its own isUnpack argument. struct MGPipeFieldArgumentOwnership { MGPipeInputField Field; Uint32 Arg0; MGPipeFieldOwnership Class; };""") add("inline constexpr SizeT kMGPipeFieldArgumentOwnershipCount = %d;" % len(arg_rows)) add("inline constexpr MGPipeFieldArgumentOwnership") add(" kMGPipeFieldArgumentOwnership[kMGPipeFieldArgumentOwnershipCount] = {") for field, arg0, cls, reason in arg_rows: add(" {MGPipeInputField::%s, %du, MGPipeFieldOwnership::%s}, // %s" % (field, arg0, ENUMERATOR[cls], reason)) add("};\n") add("""constexpr MGPipeFieldOwnership MGPipeFieldOwnershipOf(MGPipeInputField field) { return kMGPipeFieldOwnership[static_cast(field)]; } // The same answer, narrowed by the accessor's first argument. Every accessor that takes one // may call this; only the fields with a row above answer differently from the field's class. constexpr MGPipeFieldOwnership MGPipeFieldOwnershipOf(MGPipeInputField field, Uint32 arg0) { for (SizeT i = 0; i < kMGPipeFieldArgumentOwnershipCount; ++i) { if (kMGPipeFieldArgumentOwnership[i].Field == field && kMGPipeFieldArgumentOwnership[i].Arg0 == arg0) { return kMGPipeFieldArgumentOwnership[i].Class; } } return MGPipeFieldOwnershipOf(field); } constexpr const char* MGPipeFieldOwnershipName(MGPipeFieldOwnership ownership) { return kMGPipeFieldOwnershipNames[static_cast(ownership)]; } // THE BUILD FAILURE R-7.1 ASKS FOR. The generator refuses to emit an unclassified row, so // this can only fire on a hand-edited header - which is exactly the edit the DO NOT EDIT // banner cannot prevent on its own. constexpr Bool MGPipeEveryFieldIsClassified() { for (SizeT i = 0; i < kMGPipeInputFieldCount; ++i) { if (kMGPipeFieldOwnership[i] == MGPipeFieldOwnership::kUnclassified) return false; } for (SizeT i = 0; i < kMGPipeFieldOwnershipForwardCount; ++i) { if (kMGPipeFieldOwnershipForward[i] == MGPipeFieldOwnership::kUnclassified) return false; } return true; } static_assert(MGPipeEveryFieldIsClassified(), "a PipeInputs field is in none of the four ownership classes (CONTRACT-P5 table 2, R-7.1)"); """) add("""// WHERE THE SERVER STAMPS. The wire's op and the fill's verb are different name spaces // and do not line up by name (draw_vbo is DrawArrays, blit is BlitFramebuffer), so this is the // join. An op with no row is NOT a verb boundary and the applier must not stamp on it. // Present is deliberately absent: FillPoints.def:21 - "Present and SetSwapInterval go through // BackendObject virtuals and read no frontend state, so they are not verbs here". constexpr MGPipeVerb MGPipeVerbForWireOp(MGPWireOp op) { switch (op) {""") for op, verb in verb_ops: add(" case MGPWireOp::%s: return MGPipeVerb::%s;" % (op, verb)) add(""" default: return MGPipeVerb::kVerbCount; } } """) add("inline constexpr SizeT kMGPipeVerbBoundaryOpCount = %d;" % len(verb_ops)) add("") add("// The class sizes, as constants a test can pin without recounting the table.") for cls in CLASSES: add("inline constexpr SizeT kMGPipe%sFieldCount = %d;" % ("".join(p.capitalize() for p in cls.split("_")), counts[cls])) add("static_assert(%s == kMGPipeInputFieldCount, \"the four class sizes do not partition the field set\");" % " + ".join("kMGPipe%sFieldCount" % "".join(p.capitalize() for p in cls.split("_")) for cls in CLASSES)) return "\n".join(out) + "\n" def write(path, text, check_only, changed): existing = read(path) if os.path.exists(path) else None if existing == text: return changed.append(os.path.basename(path)) if not check_only: with open(path, "w", encoding="utf-8", newline="\n") as handle: handle.write(text) def expect_trip(name, fn): """A control that must exit. Returns 1 when it did, and says so when it did not - a silent pass here is the gate not checking anything.""" try: fn() except SystemExit: return 1 print("gen_pipe_field_ownership: self-test: control did NOT trip: %s" % name, file=sys.stderr) return 0 def self_test(): """The negative controls (gen_pipe.py --self-test's shape): each gate must go red for its own reason, and zero trips is itself an error.""" coverage = read(COVERAGE_DEF) ownership_text = read(OWNERSHIP_DEF) fill = read(PIPE_FILL) accessors, sticky, emitted = parse_coverage(coverage) refused = parse_supplies_whole_field(fill) ops, verbs = parse_ops_and_verbs() def run(own_text=None, cov=None, fill_text=None): acc, stk, emt = parse_coverage(cov if cov is not None else coverage) ref = parse_supplies_whole_field(fill_text if fill_text is not None else fill) rows, fwd, args, verb_ops = parse_ownership(own_text if own_text is not None else ownership_text) check_verb_ops(verb_ops, ops, verbs) return build(acc, stk, emt, ref, rows, fwd, args) # The rows wrap over two lines with a trailing backslash, so every control below edits # them through a regex whose gaps tolerate that rather than through a literal that would # silently stop matching the day someone re-aligns the file. GAP = r"[\s\\]*" def edit(pattern, replacement, what, text=None): text = ownership_text if text is None else text edited, count = re.subn(pattern, replacement, text, count=1) if count != 1: sys.exit("gen_pipe_field_ownership: self-test: could not %s - the control's own " "edit no longer matches the file it is supposed to break" % what) return edited # 1. THE HEADLINE CONTROL (exit gate E4): take one field out of the table. It is in no # class, and that is a build failure rather than a silent default. dropped = edit(r"X\(GetActiveTextureUnit," + GAP + r"BARRIER_PULLED," + GAP + r"\"[^\"]*\"," + GAP + r"\"[^\"]*\"\)", "", "remove GetActiveTextureUnit's row") controls = [("a field in NO class (GetActiveTextureUnit's row removed)", lambda: run(own_text=dropped))] # 2. The other direction: a field the derivation already placed in RECORD_SUPPLIED, also # claimed by hand. E4's negative control is "move a field from supplied to FATAL". doubled = ownership_text.replace( "#define MGP_FIELD_OWNERSHIP_LIST(X)", "#define MGP_FIELD_OWNERSHIP_LIST(X) X(GetClearColor, FATAL, \"-\", \"moved by hand\") \\\n", 1) controls.append(("a RECORD_SUPPLIED field claimed by hand (supplied -> FATAL)", lambda: run(own_text=doubled))) # 3. A row naming something that is not a field at all. typo = edit(r"X\(GetActiveTextureUnit,", "X(GetActiveTextureUnitt,", "misspell a field name") controls.append(("a row naming a non-field", lambda: run(own_text=typo))) # 4. A BARRIER_PULLED row with no retiring phase: the debt is only sized if every row # says who pays it, which is the whole of R-7.2's "rsp IS the size of the debt". unphased = edit(r"(X\(GetActiveTextureUnit," + GAP + r"BARRIER_PULLED,)" + GAP + r"\"[^\"]*\",", r"\1 \"-\",", "blank a BARRIER_PULLED row's retiring phase") controls.append(("a BARRIER_PULLED row with no retiring phase", lambda: run(own_text=unphased))) # 5. A class that is not one of the four. bogus = edit(r"(X\(GetActiveTextureUnit,)" + GAP + r"BARRIER_PULLED,", r"\1 SOMEHOW_FINE,", "introduce a fifth class") controls.append(("a fifth class", lambda: run(own_text=bogus))) # 6. A sticky forward that lost its own row - the seven most dangerous fields are exactly # the ones a table without forward rows is blind to. no_forward = edit(r"X\(RecordError," + GAP + r"BARRIER_PULLED," + GAP + r"\"P9\"," + GAP + r"\"OnGlError[^\"]*\"\)", "", "remove RecordError's forward row") controls.append(("a sticky forward with no row", lambda: run(own_text=no_forward))) # 7. A forward row that disagrees with its field row. disagree = edit(r"X\(GetTextureObject," + GAP + r"BARRIER_PULLED," + GAP + r"\"P7\"," + GAP + r"\"a server-side texture handle table\"\)", "X(GetTextureObject, FATAL, \"-\", \"a server-side texture handle table\")", "contradict GetTextureObject's field row") controls.append(("a forward row that contradicts its field row", lambda: run(own_text=disagree))) # 8. An argument exception that narrows nothing. same = edit(r"X\(GetPixelStoreParameters, 1, FATAL,", "X(GetPixelStoreParameters, 1, APPLIER_DERIVED,", "make the argument exception repeat the field's class") controls.append(("an argument exception that repeats the field's class", lambda: run(own_text=same))) # 9. THE DERIVATION'S OWN SOURCE. If EmittedCallSuppliesTheWholeField's refusals stop # parsing, every emitted field silently becomes RECORD_SUPPLIED and eight rows of this # table quietly contradict themselves - so an empty refusal set has to stop the script # rather than produce a plausible table. blinded = fill.replace("Bool EmittedCallSuppliesTheWholeField(MGPipeInputField field)", "Bool EmittedCallSuppliesTheWholeFieldXX(MGPipeInputField field)", 1) controls.append(("the derivation's source function renamed away", lambda: run(fill_text=blinded))) # 10. THE STAMP MAP against both name spaces. A row naming an op that is not a call, or a # verb that is not a verb, would otherwise be an arm that fails to compile - or an # absent stamp point, which is silent. bad_op = edit(r"X\(Clear,\s*Clear\)", "X(Klear, Clear)", "misspell a stamp map op") controls.append(("a stamp row naming an op that does not exist", lambda: run(own_text=bad_op))) bad_verb = edit(r"X\(DrawVbo,\s*DrawArrays\)", "X(DrawVbo, DrawArrayz)", "misspell a stamp map verb") controls.append(("a stamp row naming a verb that does not exist", lambda: run(own_text=bad_verb))) trips = 0 for name, fn in controls: trips += expect_trip(name, fn) # The positive control: the real tables pass, and they partition the real field set. _, _, _, _, _, counts = run() total = sum(counts.values()) if total != len(accessors): sys.exit("gen_pipe_field_ownership: self-test: the positive control does not partition " "the field set (%d of %d)" % (total, len(accessors))) if len(sticky) != 7: sys.exit("gen_pipe_field_ownership: self-test: Coverage.def no longer has seven sticky fields") if len(refused) != 9: sys.exit("gen_pipe_field_ownership: self-test: EmittedCallSuppliesTheWholeField refuses %d " "fields, not the nine the contract's derivation is written against" % len(refused)) if trips == 0: sys.exit("gen_pipe_field_ownership: self-test: no negative control tripped - the gates are " "not checking anything") if trips != len(controls): sys.exit("gen_pipe_field_ownership: self-test: %d of %d negative controls did not trip" % (len(controls) - trips, len(controls))) print("gen_pipe_field_ownership: self-test: %d negative-control trip(s), positive control OK " "(%d fields partitioned, 7 sticky forwards, 9 refusals)" % (trips, total)) return 0 def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--check", action="store_true", help="do not write; exit 1 if regenerating would change anything") parser.add_argument("--self-test", action="store_true", help="run the negative controls (each gate must trip) and exit") args = parser.parse_args() if args.self_test: return self_test() accessors, sticky, emitted = parse_coverage() refused = parse_supplies_whole_field() rows, forwards, arg_rows, verb_ops = parse_ownership() ops, verbs = parse_ops_and_verbs() check_verb_ops(verb_ops, ops, verbs) ownership, phase, why, forward_map, arg_list, counts = build( accessors, sticky, emitted, refused, rows, forwards, arg_rows) if not os.path.isdir(GENERATED_DIR): os.makedirs(GENERATED_DIR) changed = [] write(os.path.join(GENERATED_DIR, OUT_NAME), emit(accessors, sticky, ownership, phase, why, forward_map, arg_list, counts, verb_ops), args.check, changed) print("gen_pipe_field_ownership: %d fields + %d sticky forwards = %d rows; " "%d record-supplied (derived), %d applier-derived, %d barrier-pulled, %d fatal, " "%d argument exception(s), %d verb-boundary op(s)" % (len(accessors), len(sticky), len(accessors) + len(sticky), counts["RECORD_SUPPLIED"], counts["APPLIER_DERIVED"], counts["BARRIER_PULLED"], counts["FATAL"], len(arg_list), len(verb_ops))) pulled = [(f, phase[f]) for f in accessors if ownership[f] == "BARRIER_PULLED"] print("gen_pipe_field_ownership: the debt, by retiring phase:") by_phase = {} for field, retires in pulled: by_phase.setdefault(retires, []).append(field) for retires in sorted(by_phase): print("gen_pipe_field_ownership: %-42s %d" % (retires, len(by_phase[retires]))) if changed: if args.check: print("gen_pipe_field_ownership: OUT OF DATE: %s" % ", ".join(changed), file=sys.stderr) return 1 print("gen_pipe_field_ownership: wrote %s" % ", ".join(changed)) else: print("gen_pipe_field_ownership: generated file is up to date") return 0 if __name__ == "__main__": sys.exit(main())