diff --git a/packages/cultmath/tools/device/nocontract.py b/packages/cultmath/tools/device/nocontract.py new file mode 100644 index 00000000..26a200a7 --- /dev/null +++ b/packages/cultmath/tools/device/nocontract.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +"""The strict-form harness pass of the GLSL equality gate: decorates the result of every OpFMul, OpFAdd, OpFSub, +OpVectorTimesScalar and OpDot NoContraction, so the driver may not fuse them into a multiply-add. Binary, instruction +level, reusing spvedit.py's load and mk. It lives in the gate's job-local modules only, never in shipped GLSL. + +usage: nocontract.py prints the count of decorated results""" +import importlib.util, os, struct, sys + +here = os.path.dirname(os.path.abspath(__file__)) +spec = importlib.util.spec_from_file_location('spvedit', os.path.join(here, '..', 'glsl', 'spvedit.py')) +spvedit = importlib.util.module_from_spec(spec) +spec.loader.exec_module(spvedit) + +OP_DOT, OP_FADD, OP_FSUB, OP_FMUL, OP_VECTOR_TIMES_SCALAR = 148, 129, 131, 133, 142 +FLOAT_OPS = (OP_FADD, OP_FSUB, OP_FMUL, OP_VECTOR_TIMES_SCALAR, OP_DOT) +DECORATION_NO_CONTRACTION = 42 +FIRST_TYPE_OP, LAST_TYPE_OP = 19, 39 # OpTypeVoid .. OpTypeForwardPointer: the annotations end where types begin + + +def main(src, dst): + h, ins = spvedit.load(src) + done = {x[1] for x in ins if spvedit.op(x) == spvedit.OP_DECORATE and x[2] == DECORATION_NO_CONTRACTION} + ids = [x[2] for x in ins if spvedit.op(x) in FLOAT_OPS and x[2] not in done] + at = next((k for k, x in enumerate(ins) if FIRST_TYPE_OP <= spvedit.op(x) <= LAST_TYPE_OP), None) + if at is None: + sys.exit('refuse: no type section to place the decorations before') + ins[at:at] = [spvedit.mk(spvedit.OP_DECORATE, i, DECORATION_NO_CONTRACTION) for i in ids] + words = h + [v for x in ins for v in x] + open(dst, 'wb').write(struct.pack('<%dI' % len(words), *words)) + print('nocontract: %d results decorated' % len(ids)) + + +if __name__ == '__main__': + main(*sys.argv[1:3]) diff --git a/packages/cultmath/tools/device/run-lavapipe.sh b/packages/cultmath/tools/device/run-lavapipe.sh index 4505aca7..2cbb1aaa 100644 --- a/packages/cultmath/tools/device/run-lavapipe.sh +++ b/packages/cultmath/tools/device/run-lavapipe.sh @@ -7,7 +7,9 @@ # SUMMARY= tools/stopgap/ygg-verify.sh mcr.microsoft.com/dotnet/sdk:10.0 'bash packages/cultmath/tools/device/run-lavapipe.sh' # GLSL_DIR= (the output of tools/glsl/build-glsl.sh) runs the built # route instead of the committed one and skips the bounds judge: every lavapipe-glsl/.out must equal its -# lavapipe-hlsl/.out byte for byte, and a difference exits 1. It records no bounds. +# lavapipe-hlsl/.out byte for byte, and a difference exits 1. It records no bounds. That compare is in strict form: +# dxc gets -Gis and nocontract.py decorates every float result of both modules NoContraction, so the two routes cannot +# differ by a fused multiply-add. # WRITE_BOUNDS=1 re-records packages/cultmath/tests/CultMath.Tests/fixtures/device-bounds-lavapipe.txt and prints it # between BOUNDS-BEGIN and BOUNDS-END, because the job's clone is discarded. set -euo pipefail @@ -38,12 +40,19 @@ export CULTMATH_DEVICE_WORK=$W/work dotnet test "$TESTS" --no-build --filter "FullyQualifiedName~GlslMirrorTests.WriteDeviceWork" python3 "$DEV/gen.py" "$W/work" "$CM" +STRICT=(); [ -z "${GLSL_DIR:-}" ] || STRICT=(-Gis) while IFS=$'\t' read -r tag _ _ n _ wout; do in=$W/work/$tag.in - "$DXC" -T cs_6_0 -E main -HV 2021 -spirv -fspv-target-env=vulkan1.2 -I "$CM/shaders" "$W/work/$tag.hlsl" -Fo "$W/work/$tag.h.spv" >"$W/work/$tag.h.log" 2>&1 \ + "$DXC" -T cs_6_0 -E main -HV 2021 "${STRICT[@]}" -spirv -fspv-target-env=vulkan1.2 -I "$CM/shaders" "$W/work/$tag.hlsl" -Fo "$W/work/$tag.h.spv" >"$W/work/$tag.h.log" 2>&1 \ || { echo "dxc failed for $tag"; head -5 "$W/work/$tag.h.log"; exit 1; } glslangValidator -V --target-env vulkan1.2 -o "$W/work/$tag.g.spv" "$W/work/$tag.comp" >"$W/work/$tag.g.log" 2>&1 \ || { echo "glslang failed for $tag"; head -5 "$W/work/$tag.g.log"; exit 1; } + if [ -n "${GLSL_DIR:-}" ]; then + for m in h g; do + python3 "$DEV/nocontract.py" "$W/work/$tag.$m.spv" "$W/work/$tag.$m.nc.spv" >>"$W/nocontract.txt" + mv "$W/work/$tag.$m.nc.spv" "$W/work/$tag.$m.spv" + done + fi "$W/vkrun" "$W/work/$tag.h.spv" "$in" "$W/results/lavapipe-hlsl/$tag.out" "$n" "$wout" >>"$W/device.txt" "$W/vkrun" "$W/work/$tag.g.spv" "$in" "$W/results/lavapipe-glsl/$tag.out" "$n" "$wout" >>"$W/device.txt" done <"$W/work/manifest.txt" @@ -54,6 +63,7 @@ if [ -n "${GLSL_DIR:-}" ]; then tag=$(basename "$h" .out); total=$((total + 1)) if cmp -s "$h" "$W/results/lavapipe-glsl/$tag.out"; then equal=$((equal + 1)); else echo "differs: $tag"; fi done + echo "strict form: $(wc -l <"$W/nocontract.txt") modules rewritten, $(awk '{n += $2} END {print n}' "$W/nocontract.txt") results decorated NoContraction" echo "lavapipe-glsl equals lavapipe-hlsl: $equal/$total" [ "$equal" = "$total" ] && [ "$total" -gt 0 ] exit