From 1b46591c2714e18323c04d7ed23ea11597a16f88 Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Sat, 11 Jul 2026 15:47:41 +0300 Subject: [PATCH 01/32] Add portable GPU attribute metadata --- docs/API-Overview.md | 4 + docs/Getting-Started.md | 4 +- docs/Public-API-And-Extension-Contract.md | 5 + .../sixik/ga_utils/examples/GpuShowcase.java | 3 +- .../examples/ReusableMathLibrary.java | 4 +- .../javatogpu/api/GpuAnnotationSupport.java | 5 + .../javatogpu/api/annotations/GPUAligned.java | 22 +++++ .../api/annotations/GPUAlwaysInline.java | 16 +++ .../javatogpu/api/annotations/GPUPacked.java | 16 +++ .../api/annotations/GPUVectorTypeHint.java | 22 +++++ .../api/annotations/GPUWorkGroupSizeHint.java | 33 +++++++ .../api/annotations/package-info.java | 7 +- .../frontend/GpuFrontendService.java | 22 ++++- .../ir/artifact/IrGpuArtifactParser.java | 22 ++++- .../ir/artifact/IrGpuArtifactSerializer.java | 20 ++++ .../ir/artifact/IrGpuAttributeMetadata.java | 21 ++++ .../frontend/ir/artifact/IrGpuModule.java | 17 +++- .../ir/artifact/IrGpuModuleMethod.java | 19 +++- .../ir/artifact/IrGpuStructFieldMetadata.java | 8 +- .../ir/artifact/IrGpuStructMetadata.java | 13 ++- .../frontend/model/GpuAttributeMetadata.java | 45 +++++++++ .../frontend/model/ParsedGpuMethod.java | 44 +++++++++ .../frontend/model/ParsedGpuStruct.java | 22 ++++- .../frontend/model/ParsedGpuStructField.java | 12 ++- .../frontend/parser/GpuMethodParser.java | 97 +++++++++++++++++-- .../frontend/parser/GpuStructParser.java | 64 +++++++++++- .../frontend/GpuFrontendServiceTest.java | 75 ++++++++++++++ .../frontend/parser/GpuMethodParserTest.java | 51 ++++++++++ .../frontend/parser/GpuStructParserTest.java | 45 +++++++++ .../processors/GpuCompilerProcessorTest.java | 52 ++++++++++ 30 files changed, 763 insertions(+), 27 deletions(-) create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/GPUAligned.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/GPUAlwaysInline.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/GPUPacked.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/GPUVectorTypeHint.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/GPUWorkGroupSizeHint.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuAttributeMetadata.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/model/GpuAttributeMetadata.java create mode 100644 processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/parser/GpuStructParserTest.java diff --git a/docs/API-Overview.md b/docs/API-Overview.md index 5240e53c..9771b9f4 100644 --- a/docs/API-Overview.md +++ b/docs/API-Overview.md @@ -15,6 +15,9 @@ Use these in source code that should compile to GPU code. - `@GPU` marks a static Java method as a GPU kernel entry point. - `@GPUGlobal`, `@GPUConstant`, and `@GPULocal` choose the OpenCL address space for array or pointer-like parameters. - `@GPUWorkGroupSize` declares a portable required work-group size for the kernel. +- `@GPUWorkGroupSizeHint` declares a portable preferred work-group size hint for backends that support it. +- `@GPUVectorTypeHint` declares a portable preferred vector type hint for backend lowerers that use it. +- `@GPUPacked`, `@GPUAligned`, and `@GPUAlwaysInline` cover common layout and helper emission metadata without raw backend strings. - `@GPUOptimize` records method-level optimizer policy such as `fastMath`; the default remains strict. - `@GPUDeviceConstraint` restricts a method to supported backends, vendors, device classes, and required runtime features. - `@GPUFallbackVariant` groups ABI-compatible implementations that runtime may choose for different devices. @@ -30,6 +33,7 @@ Prefer portable annotations first. Use raw attributes only for backend-specific ```java @GPU @GPUWorkGroupSize(x = 8, y = 8, z = 1) +@GPUWorkGroupSizeHint(x = 8, y = 8, z = 1) @GPUOptimize(fastMath = false) static void kernel(@GPUGlobal float[] output) { output[GPU.get_global_id(0)] = 1.0f; diff --git a/docs/Getting-Started.md b/docs/Getting-Started.md index 533a4d77..6cd5f6f3 100644 --- a/docs/Getting-Started.md +++ b/docs/Getting-Started.md @@ -24,11 +24,13 @@ import net.sixik.ga_utils.javatogpu.api.GPU; import net.sixik.ga_utils.javatogpu.api.annotations.GPUGlobal; import net.sixik.ga_utils.javatogpu.api.annotations.GPUOptimize; import net.sixik.ga_utils.javatogpu.api.annotations.GPUWorkGroupSize; +import net.sixik.ga_utils.javatogpu.api.annotations.GPUWorkGroupSizeHint; public final class DemoKernel { @net.sixik.ga_utils.javatogpu.api.annotations.GPU @GPUWorkGroupSize(x = 64) + @GPUWorkGroupSizeHint(x = 64) @GPUOptimize(fastMath = false) public static void transform( @GPUGlobal float[] input, @@ -45,7 +47,7 @@ Keep first kernels simple: - `@GPU` entry methods return `void`. - Use output arrays or supported output parameters for results. - Use `GPU.*` for GPU builtins instead of arbitrary Java library calls. -- Use portable annotations such as `@GPUWorkGroupSize` and `@GPUOptimize` before raw backend attributes. +- Use portable annotations such as `@GPUWorkGroupSize`, `@GPUWorkGroupSizeHint`, `@GPUVectorTypeHint`, `@GPUPacked`, `@GPUAligned`, `@GPUAlwaysInline`, and `@GPUOptimize` before raw backend attributes. - Avoid object allocation, exceptions, recursion, virtual dispatch, and heap-heavy Java patterns inside kernels. `@GPUOptimize(fastMath = false)` is the safe default. Use `fastMath = true` only when you explicitly allow future proof-backed optimizer passes to use non-strict floating-point rewrites. diff --git a/docs/Public-API-And-Extension-Contract.md b/docs/Public-API-And-Extension-Contract.md index 39884edb..1b996080 100644 --- a/docs/Public-API-And-Extension-Contract.md +++ b/docs/Public-API-And-Extension-Contract.md @@ -9,11 +9,16 @@ Portable intent should be represented by backend-neutral annotations and `IrGpu` ```java @GPU @GPUWorkGroupSize(x = 8, y = 8, z = 1) +@GPUWorkGroupSizeHint(x = 8, y = 8, z = 1) static void kernel(@GPUGlobal float[] output) { output[GPU.get_global_id(0)] = 1.0f; } ``` +Common backend metadata should also prefer portable annotations before raw strings: `@GPUWorkGroupSizeHint`, +`@GPUVectorTypeHint`, `@GPUPacked`, `@GPUAligned`, and `@GPUAlwaysInline` lower to the current OpenCL metadata path +today while preserving source intent for future CUDA, Vulkan/SPIR-V, and Metal lowerers. + Use `@GPUAttribute` only when no portable annotation exists yet. It supports repeated usage on the same method, field, type, or parameter: ```java diff --git a/examples-app/src/main/java/net/sixik/ga_utils/examples/GpuShowcase.java b/examples-app/src/main/java/net/sixik/ga_utils/examples/GpuShowcase.java index 4c07885b..05d4d6ad 100644 --- a/examples-app/src/main/java/net/sixik/ga_utils/examples/GpuShowcase.java +++ b/examples-app/src/main/java/net/sixik/ga_utils/examples/GpuShowcase.java @@ -24,6 +24,7 @@ import net.sixik.ga_utils.javatogpu.api.annotations.GPUGlobal; import net.sixik.ga_utils.javatogpu.api.annotations.GPULocal; import net.sixik.ga_utils.javatogpu.api.annotations.GPUConstant; +import net.sixik.ga_utils.javatogpu.api.annotations.GPUWorkGroupSizeHint; import net.sixik.ga_utils.javatogpu.api.annotations.OpenCLQualifiers; import net.sixik.ga_utils.javatogpu.api.annotations.OpenCLAttributes; @@ -120,7 +121,7 @@ public static void libraryHelperExample( output[id] = ReusableMathLibrary.norm(input[id]); } - @OpenCLAttributes({"work_group_size_hint(4, 1, 1)"}) + @GPUWorkGroupSizeHint(x = 4) @net.sixik.ga_utils.javatogpu.api.annotations.GPU public static void attributeExample( @GPUGlobal float[] input, diff --git a/examples-app/src/main/java/net/sixik/ga_utils/examples/ReusableMathLibrary.java b/examples-app/src/main/java/net/sixik/ga_utils/examples/ReusableMathLibrary.java index 54b5ced1..13863f70 100644 --- a/examples-app/src/main/java/net/sixik/ga_utils/examples/ReusableMathLibrary.java +++ b/examples-app/src/main/java/net/sixik/ga_utils/examples/ReusableMathLibrary.java @@ -2,7 +2,7 @@ import net.sixik.ga_utils.javatogpu.api.annotations.CCode; import net.sixik.ga_utils.javatogpu.api.annotations.CCodeLibrary; -import net.sixik.ga_utils.javatogpu.api.annotations.OpenCLAttributes; +import net.sixik.ga_utils.javatogpu.api.annotations.GPUAlwaysInline; @CCodeLibrary public final class ReusableMathLibrary { @@ -12,7 +12,7 @@ public final class ReusableMathLibrary { private ReusableMathLibrary() { } - @OpenCLAttributes({"always_inline"}) + @GPUAlwaysInline @CCode(inline = true) public static float square(float value) { return (value * value) * SCALE; diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/GpuAnnotationSupport.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/GpuAnnotationSupport.java index 238b4966..93620043 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/GpuAnnotationSupport.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/GpuAnnotationSupport.java @@ -23,7 +23,12 @@ public final class GpuAnnotationSupport { public static final List GPU_STRUCT_ANNOTATION_TYPES = List.of(CANONICAL_PACKAGE + "GPUStruct"); public static final List GPU_ATTRIBUTE_ANNOTATION_TYPES = List.of(CANONICAL_PACKAGE + "GPUAttribute"); public static final List GPU_ATTRIBUTES_ANNOTATION_TYPES = List.of(CANONICAL_PACKAGE + "GPUAttributes"); + public static final List GPU_ALIGNED_ANNOTATION_TYPES = List.of(CANONICAL_PACKAGE + "GPUAligned"); + public static final List GPU_ALWAYS_INLINE_ANNOTATION_TYPES = List.of(CANONICAL_PACKAGE + "GPUAlwaysInline"); + public static final List GPU_PACKED_ANNOTATION_TYPES = List.of(CANONICAL_PACKAGE + "GPUPacked"); + public static final List GPU_VECTOR_TYPE_HINT_ANNOTATION_TYPES = List.of(CANONICAL_PACKAGE + "GPUVectorTypeHint"); public static final List GPU_WORK_GROUP_SIZE_ANNOTATION_TYPES = List.of(CANONICAL_PACKAGE + "GPUWorkGroupSize"); + public static final List GPU_WORK_GROUP_SIZE_HINT_ANNOTATION_TYPES = List.of(CANONICAL_PACKAGE + "GPUWorkGroupSizeHint"); public static final List GPU_OPTIMIZE_ANNOTATION_TYPES = List.of(CANONICAL_PACKAGE + "GPUOptimize"); public static final List OPENCL_ATTRIBUTES_ANNOTATION_TYPES = List.of(CANONICAL_PACKAGE + "OpenCLAttributes"); public static final List OPENCL_QUALIFIERS_ANNOTATION_TYPES = List.of(CANONICAL_PACKAGE + "OpenCLQualifiers"); diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/GPUAligned.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/GPUAligned.java new file mode 100644 index 00000000..a3a7c203 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/GPUAligned.java @@ -0,0 +1,22 @@ +package net.sixik.ga_utils.javatogpu.api.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Declares portable alignment metadata for GPU structs and struct fields. + * + *

This is the portable JavaToGpu form of backend-specific attributes such as OpenCL {@code aligned(n)}. + * Backend lowerers are responsible for translating this intent into the selected backend's layout metadata.

+ */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.FIELD}) +public @interface GPUAligned { + + /** + * Required alignment in bytes. + */ + int value(); +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/GPUAlwaysInline.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/GPUAlwaysInline.java new file mode 100644 index 00000000..a8155f35 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/GPUAlwaysInline.java @@ -0,0 +1,16 @@ +package net.sixik.ga_utils.javatogpu.api.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Requests backend helper emission as an always-inline function when the selected backend supports it. + * + *

This is the portable JavaToGpu form of backend-specific attributes such as OpenCL {@code always_inline}.

+ */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface GPUAlwaysInline { +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/GPUPacked.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/GPUPacked.java new file mode 100644 index 00000000..ecb4e0dd --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/GPUPacked.java @@ -0,0 +1,16 @@ +package net.sixik.ga_utils.javatogpu.api.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Requests packed layout for a GPU struct when the selected backend supports explicit layout packing. + * + *

This is the portable JavaToGpu form of backend-specific attributes such as OpenCL {@code packed}.

+ */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface GPUPacked { +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/GPUVectorTypeHint.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/GPUVectorTypeHint.java new file mode 100644 index 00000000..c4e4f802 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/GPUVectorTypeHint.java @@ -0,0 +1,22 @@ +package net.sixik.ga_utils.javatogpu.api.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Declares a preferred vector type hint for a GPU entry method. + * + *

This is the portable JavaToGpu form of backend-specific attributes such as OpenCL {@code vec_type_hint(T)}. + * The hint is advisory and must not be treated as a correctness contract.

+ */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface GPUVectorTypeHint { + + /** + * Backend-facing vector type token, for example {@code float4} or {@code int4}. + */ + String value(); +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/GPUWorkGroupSizeHint.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/GPUWorkGroupSizeHint.java new file mode 100644 index 00000000..5442fb3a --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/GPUWorkGroupSizeHint.java @@ -0,0 +1,33 @@ +package net.sixik.ga_utils.javatogpu.api.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Declares a preferred work-group size hint for a GPU entry method. + * + *

This is the portable JavaToGpu form of backend-specific hints such as OpenCL + * {@code work_group_size_hint(x, y, z)}. Backends may use the hint when it maps cleanly to their metadata model, but + * it is advisory and must not be treated as a required launch contract.

+ */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface GPUWorkGroupSizeHint { + + /** + * Preferred work-group size in the X dimension. + */ + int x() default 1; + + /** + * Preferred work-group size in the Y dimension. + */ + int y() default 1; + + /** + * Preferred work-group size in the Z dimension. + */ + int z() default 1; +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/package-info.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/package-info.java index b64a539e..137831b4 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/package-info.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/package-info.java @@ -3,7 +3,12 @@ * *

Use these annotations to mark GPU entry methods, helper methods, address spaces, structs, and backend metadata in * ordinary Java source. The public Java facade intentionally uses OpenCL-style naming; portable annotations such as - * {@link net.sixik.ga_utils.javatogpu.api.annotations.GPUWorkGroupSize} and + * {@link net.sixik.ga_utils.javatogpu.api.annotations.GPUWorkGroupSize}, + * {@link net.sixik.ga_utils.javatogpu.api.annotations.GPUWorkGroupSizeHint}, + * {@link net.sixik.ga_utils.javatogpu.api.annotations.GPUPacked}, + * {@link net.sixik.ga_utils.javatogpu.api.annotations.GPUAligned}, + * {@link net.sixik.ga_utils.javatogpu.api.annotations.GPUAlwaysInline}, + * {@link net.sixik.ga_utils.javatogpu.api.annotations.GPUVectorTypeHint} and * {@link net.sixik.ga_utils.javatogpu.api.annotations.GPUOptimize} should be preferred over backend-specific escape * hatches whenever possible. */ diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendService.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendService.java index 9606beca..39d7d0fe 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendService.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendService.java @@ -9,6 +9,7 @@ import net.sixik.ga_utils.javatogpu.api.GpuVendorTarget; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuAttributeMetadata; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBodyIndex; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuConstantDataMetadata; @@ -268,6 +269,7 @@ static IrGpuArtifact buildIrGpuArtifact( compiledKernel.parsedMethod().name(), compiledKernel.emittedName(), compiledKernel.parsedMethod().openClAttributes(), + buildAttributeMetadata(compiledKernel.parsedMethod().attributeMetadata()), helperMethods.stream() .map(helper -> new IrGpuModuleMethod( helper.parsedMethod().name(), @@ -275,6 +277,7 @@ static IrGpuArtifact buildIrGpuArtifact( helper.parsedMethod().returnType(), buildMethodParameters(helper), helper.parsedMethod().openClAttributes(), + buildAttributeMetadata(helper.parsedMethod().attributeMetadata()), helper.parsedMethod().inline() )) .toList(), @@ -545,10 +548,25 @@ private static List buildStructMetadata(List new IrGpuStructFieldMetadata( field.name(), field.javaType(), - field.openClAttributes() + field.openClAttributes(), + buildAttributeMetadata(field.attributeMetadata()) )) .toList(), - struct.openClAttributes() + struct.openClAttributes(), + buildAttributeMetadata(struct.attributeMetadata()) + )) + .toList(); + } + + private static List buildAttributeMetadata( + List metadata + ) { + return (metadata == null ? List.of() : metadata) + .stream() + .map(attribute -> new IrGpuAttributeMetadata( + attribute.kind(), + attribute.value(), + attribute.source() )) .toList(); } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifactParser.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifactParser.java index bfd80588..c1b95cd5 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifactParser.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifactParser.java @@ -33,6 +33,7 @@ public static IrGpuArtifact parse(String manifest) { require(properties, "entryMethod"), require(properties, "entryEmittedName"), parseIndexedValues(properties, "entry.openClAttribute"), + parseAttributeMetadata(properties, "entry.attributeMetadata"), parseHelpers(properties), parseStructs(properties), parseMethodBodies(properties) @@ -114,6 +115,7 @@ private static List parseHelpers(Properties properties) { properties.getProperty("helper." + index + ".returnType", "unknown"), parseMethodParameters(properties, "helper." + index + ".parameter"), parseIndexedValues(properties, "helper." + index + ".openClAttribute"), + parseAttributeMetadata(properties, "helper." + index + ".attributeMetadata"), Boolean.parseBoolean(properties.getProperty("helper." + index + ".inline", "false")) )); } @@ -347,7 +349,8 @@ private static List parseStructMetadata(Properties properti require(properties, prefix + "ownerQualifiedName"), properties.getProperty(prefix + "ownerSimpleName", ""), parseStructFields(properties, prefix), - parseIndexedValues(properties, prefix + "openClAttribute") + parseIndexedValues(properties, prefix + "openClAttribute"), + parseAttributeMetadata(properties, prefix + "attributeMetadata") )); } return List.copyOf(structs); @@ -361,12 +364,27 @@ private static List parseStructFields(Properties prope fields.add(new IrGpuStructFieldMetadata( require(properties, prefix + "name"), require(properties, prefix + "javaType"), - parseIndexedValues(properties, prefix + "openClAttribute") + parseIndexedValues(properties, prefix + "openClAttribute"), + parseAttributeMetadata(properties, prefix + "attributeMetadata") )); } return List.copyOf(fields); } + private static List parseAttributeMetadata(Properties properties, String prefix) { + int count = parseInt(properties, prefix + ".count", 0); + ArrayList metadata = new ArrayList<>(); + for (int index = 0; index < count; index++) { + String itemPrefix = prefix + "." + index + "."; + metadata.add(new IrGpuAttributeMetadata( + require(properties, itemPrefix + "kind"), + properties.getProperty(itemPrefix + "value", ""), + properties.getProperty(itemPrefix + "source", "unknown") + )); + } + return List.copyOf(metadata); + } + private static List parseConstants(Properties properties) { int count = parseInt(properties, "constant.count", 0); ArrayList constants = new ArrayList<>(); diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifactSerializer.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifactSerializer.java index a448c145..74c6b7c1 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifactSerializer.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifactSerializer.java @@ -50,6 +50,7 @@ private static void writeModule(TreeMap properties, IrGpuModule properties.put("entryMethod", module.entryMethod()); properties.put("entryEmittedName", module.entryEmittedName()); writeStringList(properties, "entry.openClAttribute", module.entryOpenClAttributes()); + writeAttributeMetadata(properties, "entry.attributeMetadata", module.entryAttributeMetadata()); properties.put("helper.count", Integer.toString(module.helperMethods().size())); for (int index = 0; index < module.helperMethods().size(); index++) { IrGpuModuleMethod helper = module.helperMethods().get(index); @@ -58,6 +59,7 @@ private static void writeModule(TreeMap properties, IrGpuModule properties.put("helper." + index + ".returnType", helper.returnType()); properties.put("helper." + index + ".inline", Boolean.toString(helper.inline())); writeStringList(properties, "helper." + index + ".openClAttribute", helper.openClAttributes()); + writeAttributeMetadata(properties, "helper." + index + ".attributeMetadata", helper.attributeMetadata()); writeMethodParameters(properties, "helper." + index + ".parameter", helper.parameters()); } properties.put("struct.count", Integer.toString(module.structs().size())); @@ -349,6 +351,7 @@ private static void writeStructMetadata(TreeMap properties, java properties.put(prefix + "ownerQualifiedName", struct.ownerQualifiedName()); properties.put(prefix + "ownerSimpleName", struct.ownerSimpleName()); writeStringList(properties, prefix + "openClAttribute", struct.openClAttributes()); + writeAttributeMetadata(properties, prefix + "attributeMetadata", struct.attributeMetadata()); properties.put(prefix + "field.count", Integer.toString(struct.fields().size())); for (int fieldIndex = 0; fieldIndex < struct.fields().size(); fieldIndex++) { IrGpuStructFieldMetadata field = struct.fields().get(fieldIndex); @@ -356,10 +359,27 @@ private static void writeStructMetadata(TreeMap properties, java properties.put(fieldPrefix + "name", field.name()); properties.put(fieldPrefix + "javaType", field.javaType()); writeStringList(properties, fieldPrefix + "openClAttribute", field.openClAttributes()); + writeAttributeMetadata(properties, fieldPrefix + "attributeMetadata", field.attributeMetadata()); } } } + private static void writeAttributeMetadata( + TreeMap properties, + String prefix, + java.util.List metadata + ) { + java.util.List values = metadata == null ? java.util.List.of() : metadata; + properties.put(prefix + ".count", Integer.toString(values.size())); + for (int index = 0; index < values.size(); index++) { + IrGpuAttributeMetadata attribute = values.get(index); + String itemPrefix = prefix + "." + index + "."; + properties.put(itemPrefix + "kind", attribute.kind()); + properties.put(itemPrefix + "value", attribute.value()); + properties.put(itemPrefix + "source", attribute.source()); + } + } + private static void writeConstants(TreeMap properties, java.util.List constants) { properties.put("constant.count", Integer.toString(constants.size())); for (int index = 0; index < constants.size(); index++) { diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuAttributeMetadata.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuAttributeMetadata.java new file mode 100644 index 00000000..caf2cbc0 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuAttributeMetadata.java @@ -0,0 +1,21 @@ +package net.sixik.ga_utils.javatogpu.frontend.ir.artifact; + +/** + * Backend-neutral attribute metadata persisted in IrGpu artifacts. + */ +public record IrGpuAttributeMetadata( + String kind, + String value, + String source +) { + + public IrGpuAttributeMetadata { + kind = normalize(kind, "unknown"); + value = value == null ? "" : value; + source = normalize(source, "unknown"); + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuModule.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuModule.java index 6be76da1..6899f948 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuModule.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuModule.java @@ -6,6 +6,7 @@ public record IrGpuModule( String entryMethod, String entryEmittedName, List entryOpenClAttributes, + List entryAttributeMetadata, List helperMethods, List structs, List methodBodies @@ -14,11 +15,22 @@ public record IrGpuModule( public IrGpuModule( String entryMethod, String entryEmittedName, + List entryOpenClAttributes, List helperMethods, List structs, List methodBodies ) { - this(entryMethod, entryEmittedName, List.of(), helperMethods, structs, methodBodies); + this(entryMethod, entryEmittedName, entryOpenClAttributes, List.of(), helperMethods, structs, methodBodies); + } + + public IrGpuModule( + String entryMethod, + String entryEmittedName, + List helperMethods, + List structs, + List methodBodies + ) { + this(entryMethod, entryEmittedName, List.of(), List.of(), helperMethods, structs, methodBodies); } public IrGpuModule( @@ -27,11 +39,12 @@ public IrGpuModule( List helperMethods, List structs ) { - this(entryMethod, entryEmittedName, List.of(), helperMethods, structs, List.of()); + this(entryMethod, entryEmittedName, List.of(), List.of(), helperMethods, structs, List.of()); } public IrGpuModule { entryOpenClAttributes = entryOpenClAttributes == null ? List.of() : List.copyOf(entryOpenClAttributes); + entryAttributeMetadata = entryAttributeMetadata == null ? List.of() : List.copyOf(entryAttributeMetadata); helperMethods = helperMethods == null ? List.of() : List.copyOf(helperMethods); structs = structs == null ? List.of() : List.copyOf(structs); methodBodies = methodBodies == null ? List.of() : List.copyOf(methodBodies); diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuModuleMethod.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuModuleMethod.java index f4be2eb8..d96f1e52 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuModuleMethod.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuModuleMethod.java @@ -11,11 +11,12 @@ public record IrGpuModuleMethod( String returnType, List parameters, List openClAttributes, + List attributeMetadata, boolean inline ) { public IrGpuModuleMethod(String name, String emittedName) { - this(name, emittedName, "unknown", List.of(), List.of(), false); + this(name, emittedName, "unknown", List.of(), List.of(), List.of(), false); } public IrGpuModuleMethod( @@ -24,7 +25,7 @@ public IrGpuModuleMethod( String returnType, List parameters ) { - this(name, emittedName, returnType, parameters, List.of(), false); + this(name, emittedName, returnType, parameters, List.of(), List.of(), false); } public IrGpuModuleMethod( @@ -34,7 +35,18 @@ public IrGpuModuleMethod( List parameters, List openClAttributes ) { - this(name, emittedName, returnType, parameters, openClAttributes, false); + this(name, emittedName, returnType, parameters, openClAttributes, List.of(), false); + } + + public IrGpuModuleMethod( + String name, + String emittedName, + String returnType, + List parameters, + List openClAttributes, + boolean inline + ) { + this(name, emittedName, returnType, parameters, openClAttributes, List.of(), inline); } public IrGpuModuleMethod { @@ -43,6 +55,7 @@ public IrGpuModuleMethod( returnType = normalize(returnType, "unknown"); parameters = parameters == null ? List.of() : List.copyOf(parameters); openClAttributes = openClAttributes == null ? List.of() : List.copyOf(openClAttributes); + attributeMetadata = attributeMetadata == null ? List.of() : List.copyOf(attributeMetadata); } private static String normalize(String value, String fallback) { diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuStructFieldMetadata.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuStructFieldMetadata.java index 1fff7686..846a8a20 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuStructFieldMetadata.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuStructFieldMetadata.java @@ -8,13 +8,19 @@ public record IrGpuStructFieldMetadata( String name, String javaType, - List openClAttributes + List openClAttributes, + List attributeMetadata ) { + public IrGpuStructFieldMetadata(String name, String javaType, List openClAttributes) { + this(name, javaType, openClAttributes, List.of()); + } + public IrGpuStructFieldMetadata { name = normalize(name, ""); javaType = normalize(javaType, "unknown"); openClAttributes = openClAttributes == null ? List.of() : List.copyOf(openClAttributes); + attributeMetadata = attributeMetadata == null ? List.of() : List.copyOf(attributeMetadata); } private static String normalize(String value, String fallback) { diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuStructMetadata.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuStructMetadata.java index d38dd535..743743f2 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuStructMetadata.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuStructMetadata.java @@ -9,14 +9,25 @@ public record IrGpuStructMetadata( String ownerQualifiedName, String ownerSimpleName, List fields, - List openClAttributes + List openClAttributes, + List attributeMetadata ) { + public IrGpuStructMetadata( + String ownerQualifiedName, + String ownerSimpleName, + List fields, + List openClAttributes + ) { + this(ownerQualifiedName, ownerSimpleName, fields, openClAttributes, List.of()); + } + public IrGpuStructMetadata { ownerQualifiedName = normalize(ownerQualifiedName, ""); ownerSimpleName = normalize(ownerSimpleName, ownerQualifiedName); fields = fields == null ? List.of() : List.copyOf(fields); openClAttributes = openClAttributes == null ? List.of() : List.copyOf(openClAttributes); + attributeMetadata = attributeMetadata == null ? List.of() : List.copyOf(attributeMetadata); } private static String normalize(String value, String fallback) { diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/model/GpuAttributeMetadata.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/model/GpuAttributeMetadata.java new file mode 100644 index 00000000..b2b0a0f4 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/model/GpuAttributeMetadata.java @@ -0,0 +1,45 @@ +package net.sixik.ga_utils.javatogpu.frontend.model; + +/** + * Backend-neutral representation of source metadata that may lower to backend attributes. + */ +public record GpuAttributeMetadata( + String kind, + String value, + String source +) { + + public GpuAttributeMetadata { + kind = normalize(kind, "unknown"); + value = value == null ? "" : value; + source = normalize(source, "unknown"); + } + + public static GpuAttributeMetadata packed(String source) { + return new GpuAttributeMetadata("packed", "true", source); + } + + public static GpuAttributeMetadata aligned(int bytes, String source) { + return new GpuAttributeMetadata("aligned", Integer.toString(bytes), source); + } + + public static GpuAttributeMetadata requiredWorkGroupSize(int x, int y, int z, String source) { + return new GpuAttributeMetadata("required-work-group-size", x + "," + y + "," + z, source); + } + + public static GpuAttributeMetadata workGroupSizeHint(int x, int y, int z, String source) { + return new GpuAttributeMetadata("work-group-size-hint", x + "," + y + "," + z, source); + } + + public static GpuAttributeMetadata vectorTypeHint(String value, String source) { + return new GpuAttributeMetadata("vector-type-hint", value, source); + } + + public static GpuAttributeMetadata alwaysInline(String source) { + return new GpuAttributeMetadata("always-inline", "true", source); + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/model/ParsedGpuMethod.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/model/ParsedGpuMethod.java index 292f2e07..29a99540 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/model/ParsedGpuMethod.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/model/ParsedGpuMethod.java @@ -15,9 +15,53 @@ public record ParsedGpuMethod( MethodDeclaration declaration, boolean inline, List openClAttributes, + List attributeMetadata, String nativeCode, String supportCondition, String callbackMethodName, boolean nativeDeclaration ) { + + public ParsedGpuMethod( + String ownerSimpleName, + String ownerQualifiedName, + String name, + String returnType, + List parameters, + List constants, + List constantData, + MethodDeclaration declaration, + boolean inline, + List openClAttributes, + String nativeCode, + String supportCondition, + String callbackMethodName, + boolean nativeDeclaration + ) { + this( + ownerSimpleName, + ownerQualifiedName, + name, + returnType, + parameters, + constants, + constantData, + declaration, + inline, + openClAttributes, + List.of(), + nativeCode, + supportCondition, + callbackMethodName, + nativeDeclaration + ); + } + + public ParsedGpuMethod { + parameters = parameters == null ? List.of() : List.copyOf(parameters); + constants = constants == null ? List.of() : List.copyOf(constants); + constantData = constantData == null ? List.of() : List.copyOf(constantData); + openClAttributes = openClAttributes == null ? List.of() : List.copyOf(openClAttributes); + attributeMetadata = attributeMetadata == null ? List.of() : List.copyOf(attributeMetadata); + } } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/model/ParsedGpuStruct.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/model/ParsedGpuStruct.java index e72d9ed0..b7a51eba 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/model/ParsedGpuStruct.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/model/ParsedGpuStruct.java @@ -8,6 +8,26 @@ public record ParsedGpuStruct( List fields, List constants, List constantData, - List openClAttributes + List openClAttributes, + List attributeMetadata ) { + + public ParsedGpuStruct( + String ownerSimpleName, + String ownerQualifiedName, + List fields, + List constants, + List constantData, + List openClAttributes + ) { + this(ownerSimpleName, ownerQualifiedName, fields, constants, constantData, openClAttributes, List.of()); + } + + public ParsedGpuStruct { + fields = fields == null ? List.of() : List.copyOf(fields); + constants = constants == null ? List.of() : List.copyOf(constants); + constantData = constantData == null ? List.of() : List.copyOf(constantData); + openClAttributes = openClAttributes == null ? List.of() : List.copyOf(openClAttributes); + attributeMetadata = attributeMetadata == null ? List.of() : List.copyOf(attributeMetadata); + } } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/model/ParsedGpuStructField.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/model/ParsedGpuStructField.java index 18059188..c52a4c89 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/model/ParsedGpuStructField.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/model/ParsedGpuStructField.java @@ -5,6 +5,16 @@ public record ParsedGpuStructField( String name, String javaType, - List openClAttributes + List openClAttributes, + List attributeMetadata ) { + + public ParsedGpuStructField(String name, String javaType, List openClAttributes) { + this(name, javaType, openClAttributes, List.of()); + } + + public ParsedGpuStructField { + openClAttributes = openClAttributes == null ? List.of() : List.copyOf(openClAttributes); + attributeMetadata = attributeMetadata == null ? List.of() : List.copyOf(attributeMetadata); + } } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/parser/GpuMethodParser.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/parser/GpuMethodParser.java index c160b09b..273f374a 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/parser/GpuMethodParser.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/parser/GpuMethodParser.java @@ -10,6 +10,7 @@ import com.github.javaparser.ast.body.Parameter; import net.sixik.ga_utils.javatogpu.frontend.model.ParsedGpuConstant; import net.sixik.ga_utils.javatogpu.frontend.model.ParsedGpuConstantData; +import net.sixik.ga_utils.javatogpu.frontend.model.GpuAttributeMetadata; import net.sixik.ga_utils.javatogpu.frontend.model.GpuAddressSpace; import net.sixik.ga_utils.javatogpu.frontend.model.ParsedGpuMethod; import net.sixik.ga_utils.javatogpu.frontend.model.ParsedGpuParameter; @@ -64,6 +65,7 @@ public ParsedGpuMethod parseMethod( declaration, parseInlineFlag(declaration), parseOpenClAttributes(declaration), + parseAttributeMetadata(declaration), parseNativeCode(declaration), parseAnnotationStringValue(declaration, "support"), parseAnnotationStringValue(declaration, "callback"), @@ -110,22 +112,78 @@ private List parseOpenClQualifiers(Parameter parameter) { private List parseOpenClAttributes(MethodDeclaration declaration) { List attributes = new ArrayList<>(GpuStructParser.parseOpenClAttributes(declaration.getAnnotations())); parseWorkGroupSizeAttribute(declaration).ifPresent(attributes::add); + parseWorkGroupSizeHintAttribute(declaration).ifPresent(attributes::add); + parseVectorTypeHintAttribute(declaration).ifPresent(attributes::add); + if (declaration.getAnnotationByName("GPUAlwaysInline").isPresent()) { + attributes.add("always_inline"); + } return List.copyOf(attributes); } + private List parseAttributeMetadata(MethodDeclaration declaration) { + ArrayList metadata = new ArrayList<>(); + parseWorkGroupSizeMetadata(declaration).ifPresent(metadata::add); + parseWorkGroupSizeHintMetadata(declaration).ifPresent(metadata::add); + parseVectorTypeHintMetadata(declaration).ifPresent(metadata::add); + if (declaration.getAnnotationByName("GPUAlwaysInline").isPresent()) { + metadata.add(GpuAttributeMetadata.alwaysInline("GPUAlwaysInline")); + } + return List.copyOf(metadata); + } + private java.util.Optional parseWorkGroupSizeAttribute(MethodDeclaration declaration) { return declaration.getAnnotationByName("GPUWorkGroupSize") - .map(this::workGroupSizeAttribute); + .map(annotation -> workGroupSizeAttribute(annotation, "GPUWorkGroupSize", "reqd_work_group_size")); + } + + private java.util.Optional parseWorkGroupSizeMetadata(MethodDeclaration declaration) { + return declaration.getAnnotationByName("GPUWorkGroupSize") + .map(annotation -> workGroupSizeMetadata(annotation, "GPUWorkGroupSize", "required")); + } + + private java.util.Optional parseWorkGroupSizeHintAttribute(MethodDeclaration declaration) { + return declaration.getAnnotationByName("GPUWorkGroupSizeHint") + .map(annotation -> workGroupSizeAttribute(annotation, "GPUWorkGroupSizeHint", "work_group_size_hint")); + } + + private java.util.Optional parseWorkGroupSizeHintMetadata(MethodDeclaration declaration) { + return declaration.getAnnotationByName("GPUWorkGroupSizeHint") + .map(annotation -> workGroupSizeMetadata(annotation, "GPUWorkGroupSizeHint", "hint")); + } + + private java.util.Optional parseVectorTypeHintAttribute(MethodDeclaration declaration) { + return declaration.getAnnotationByName("GPUVectorTypeHint") + .map(annotation -> "vec_type_hint(" + parseStringAnnotationValue(annotation, "value", "GPUVectorTypeHint.value") + ")"); } - private String workGroupSizeAttribute(AnnotationExpr annotation) { - int x = parseIntAnnotationValue(annotation, "x", 1, "GPUWorkGroupSize"); - int y = parseIntAnnotationValue(annotation, "y", 1, "GPUWorkGroupSize"); - int z = parseIntAnnotationValue(annotation, "z", 1, "GPUWorkGroupSize"); + private java.util.Optional parseVectorTypeHintMetadata(MethodDeclaration declaration) { + return declaration.getAnnotationByName("GPUVectorTypeHint") + .map(annotation -> GpuAttributeMetadata.vectorTypeHint( + parseStringAnnotationValue(annotation, "value", "GPUVectorTypeHint.value"), + "GPUVectorTypeHint" + )); + } + + private String workGroupSizeAttribute(AnnotationExpr annotation, String annotationName, String attributeName) { + int x = parseIntAnnotationValue(annotation, "x", 1, annotationName); + int y = parseIntAnnotationValue(annotation, "y", 1, annotationName); + int z = parseIntAnnotationValue(annotation, "z", 1, annotationName); + if (x <= 0 || y <= 0 || z <= 0) { + throw new IllegalArgumentException(annotationName + " dimensions must be positive integers"); + } + return attributeName + "(" + x + ", " + y + ", " + z + ")"; + } + + private GpuAttributeMetadata workGroupSizeMetadata(AnnotationExpr annotation, String annotationName, String mode) { + int x = parseIntAnnotationValue(annotation, "x", 1, annotationName); + int y = parseIntAnnotationValue(annotation, "y", 1, annotationName); + int z = parseIntAnnotationValue(annotation, "z", 1, annotationName); if (x <= 0 || y <= 0 || z <= 0) { - throw new IllegalArgumentException("GPUWorkGroupSize dimensions must be positive integers"); + throw new IllegalArgumentException(annotationName + " dimensions must be positive integers"); } - return "reqd_work_group_size(" + x + ", " + y + ", " + z + ")"; + return "required".equals(mode) + ? GpuAttributeMetadata.requiredWorkGroupSize(x, y, z, annotationName) + : GpuAttributeMetadata.workGroupSizeHint(x, y, z, annotationName); } private int parseIntAnnotationValue(AnnotationExpr annotation, String propertyName, int defaultValue, String errorLabel) { @@ -146,6 +204,31 @@ private int parsePositiveIntLiteral(com.github.javaparser.ast.expr.Expression ex throw new IllegalArgumentException(errorLabel + " must be an integer literal: " + expression); } + private String parseStringAnnotationValue(AnnotationExpr annotation, String propertyName, String errorLabel) { + com.github.javaparser.ast.expr.Expression value; + if (annotation.isSingleMemberAnnotationExpr()) { + value = annotation.asSingleMemberAnnotationExpr().getMemberValue(); + } else if (annotation.isNormalAnnotationExpr()) { + value = annotation.asNormalAnnotationExpr().getPairs().stream() + .filter(pair -> pair.getNameAsString().equals(propertyName)) + .findFirst() + .map(pair -> pair.getValue()) + .orElseThrow(() -> new IllegalArgumentException(errorLabel + " must be declared")); + } else { + throw new IllegalArgumentException(errorLabel + " must be declared"); + } + if (value instanceof StringLiteralExpr stringLiteralExpr) { + return stringLiteralExpr.asString(); + } + if (value instanceof TextBlockLiteralExpr textBlockLiteralExpr) { + return textBlockLiteralExpr.asString(); + } + if (value instanceof LiteralStringValueExpr literalStringValueExpr) { + return literalStringValueExpr.getValue(); + } + throw new IllegalArgumentException(errorLabel + " must be a string literal: " + value); + } + private GpuAddressSpace resolveAddressSpace(boolean isGlobal, boolean isConstantAddressSpace, boolean isLocal) { if (isGlobal) { return GpuAddressSpace.GLOBAL; diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/parser/GpuStructParser.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/parser/GpuStructParser.java index 31dd6282..a9226dfd 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/parser/GpuStructParser.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/parser/GpuStructParser.java @@ -20,6 +20,7 @@ import net.sixik.ga_utils.javatogpu.frontend.model.ParsedGpuConstant; import net.sixik.ga_utils.javatogpu.frontend.model.ParsedGpuConstantData; import net.sixik.ga_utils.javatogpu.frontend.model.GpuConstantDataKind; +import net.sixik.ga_utils.javatogpu.frontend.model.GpuAttributeMetadata; import net.sixik.ga_utils.javatogpu.frontend.model.ParsedGpuStruct; import net.sixik.ga_utils.javatogpu.frontend.model.ParsedGpuStructField; import net.sixik.ga_utils.javatogpu.types.GpuTypeSupport; @@ -95,7 +96,8 @@ public ParsedGpuStruct parseStruct(String structSource, String ownerSimpleName, fields.add(new ParsedGpuStructField( variable.getNameAsString(), variable.getTypeAsString(), - parseOpenClAttributes(fieldDeclaration.getAnnotations()) + parseOpenClAttributes(fieldDeclaration.getAnnotations()), + parseAttributeMetadata(fieldDeclaration.getAnnotations()) )); }); } @@ -106,7 +108,8 @@ public ParsedGpuStruct parseStruct(String structSource, String ownerSimpleName, List.copyOf(fields), List.copyOf(constants), List.copyOf(constantData), - parseOpenClAttributes(classDeclaration.getAnnotations()) + parseOpenClAttributes(classDeclaration.getAnnotations()), + parseAttributeMetadata(classDeclaration.getAnnotations()) ); } @@ -117,9 +120,66 @@ static List parseOpenClAttributes(NodeList annotations) "OpenCLAttributes" )); attributes.addAll(parseGpuAttributesForOpenCl(annotations)); + if (hasAnnotation(annotations, "GPUPacked")) { + attributes.add("packed"); + } + parseAlignedAttribute(annotations).ifPresent(attributes::add); return List.copyOf(attributes); } + static List parseAttributeMetadata(NodeList annotations) { + ArrayList metadata = new ArrayList<>(); + if (hasAnnotation(annotations, "GPUPacked")) { + metadata.add(GpuAttributeMetadata.packed("GPUPacked")); + } + parseAlignedMetadata(annotations).ifPresent(metadata::add); + return List.copyOf(metadata); + } + + private static boolean hasAnnotation(NodeList annotations, String annotationName) { + return annotations.stream().anyMatch(annotation -> annotation.getNameAsString().equals(annotationName)); + } + + private static java.util.Optional parseAlignedAttribute(NodeList annotations) { + return annotations.stream() + .filter(annotation -> annotation.getNameAsString().equals("GPUAligned")) + .findFirst() + .map(annotation -> "aligned(" + parsePositiveIntAnnotationValue(annotation, "value", "GPUAligned.value") + ")"); + } + + private static java.util.Optional parseAlignedMetadata(NodeList annotations) { + return annotations.stream() + .filter(annotation -> annotation.getNameAsString().equals("GPUAligned")) + .findFirst() + .map(annotation -> GpuAttributeMetadata.aligned( + parsePositiveIntAnnotationValue(annotation, "value", "GPUAligned.value"), + "GPUAligned" + )); + } + + static int parsePositiveIntAnnotationValue(AnnotationExpr annotation, String propertyName, String errorLabel) { + Expression value; + if (annotation.isSingleMemberAnnotationExpr()) { + value = annotation.asSingleMemberAnnotationExpr().getMemberValue(); + } else if (annotation.isNormalAnnotationExpr()) { + value = annotation.asNormalAnnotationExpr().getPairs().stream() + .filter(pair -> pair.getNameAsString().equals(propertyName)) + .findFirst() + .map(pair -> pair.getValue()) + .orElseThrow(() -> new IllegalArgumentException(errorLabel + " must be declared")); + } else { + throw new IllegalArgumentException(errorLabel + " must be declared"); + } + if (!value.isIntegerLiteralExpr()) { + throw new IllegalArgumentException(errorLabel + " must be an integer literal: " + value); + } + int intValue = value.asIntegerLiteralExpr().asInt(); + if (intValue <= 0) { + throw new IllegalArgumentException(errorLabel + " must be a positive integer"); + } + return intValue; + } + static List parseGpuAttributesForOpenCl(NodeList annotations) { ArrayList attributes = new ArrayList<>(); for (AnnotationExpr annotation : annotations) { diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendServiceTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendServiceTest.java index 84e6150f..2ebce97a 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendServiceTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendServiceTest.java @@ -353,6 +353,81 @@ void kernel(@GPUGlobal float[] output) { assertTrue(manifest.contains("methodFallbackVariant.0.priority=120")); } + @Test + void persistsPortableAttributeMetadataThroughIrGpuRoundTrip() { + String methodSource = """ + @GPUWorkGroupSize(x = 8, y = 4, z = 2) + @GPUWorkGroupSizeHint(x = 4, y = 2, z = 1) + @GPUVectorTypeHint("float4") + @GPU + void kernel(@GPUGlobal Pair[] pairs, @GPUGlobal float[] output) { + int id = GPU.get_global_id(0); + output[id] = scale(pairs[id].x); + } + """; + String helperSource = """ + @GPUAlwaysInline + @CCode(inline = true) + float scale(float value) { + return value * 2.0f; + } + """; + String structSource = """ + @GPUPacked + @GPUAligned(16) + @GPUStruct + class Pair { + @GPUAligned(8) + float x; + float y; + } + """; + GpuFrontendService service = GpuFrontendService.createDefault(); + ParsedGpuMethod kernelMethod = new net.sixik.ga_utils.javatogpu.frontend.parser.GpuMethodParser() + .parseMethod(methodSource, "Demo", "sample.Demo"); + ParsedGpuMethod helperMethod = new net.sixik.ga_utils.javatogpu.frontend.parser.GpuMethodParser() + .parseMethod(helperSource, "Demo", "sample.Demo"); + ParsedGpuStruct struct = new net.sixik.ga_utils.javatogpu.frontend.parser.GpuStructParser() + .parseStruct(structSource, "Pair", "sample.Demo.Pair"); + + GpuFrontendCompilationResult result = service.compile( + kernelMethod, + List.of(helperMethod), + List.of(struct), + "javatogpu/sample/Demo/kernel.cl" + ); + + assertEquals(3, result.irGpuArtifact().module().entryAttributeMetadata().size()); + assertEquals("required-work-group-size", result.irGpuArtifact().module().entryAttributeMetadata().get(0).kind()); + assertEquals("8,4,2", result.irGpuArtifact().module().entryAttributeMetadata().get(0).value()); + assertEquals("work-group-size-hint", result.irGpuArtifact().module().entryAttributeMetadata().get(1).kind()); + assertEquals("vector-type-hint", result.irGpuArtifact().module().entryAttributeMetadata().get(2).kind()); + assertEquals("always-inline", result.irGpuArtifact().module().helperMethods().get(0).attributeMetadata().get(0).kind()); + assertEquals("packed", result.irGpuArtifact().structMetadata().get(0).attributeMetadata().get(0).kind()); + assertEquals("aligned", result.irGpuArtifact().structMetadata().get(0).attributeMetadata().get(1).kind()); + assertEquals("16", result.irGpuArtifact().structMetadata().get(0).attributeMetadata().get(1).value()); + assertEquals("aligned", result.irGpuArtifact().structMetadata().get(0).fields().get(0).attributeMetadata().get(0).kind()); + assertEquals("8", result.irGpuArtifact().structMetadata().get(0).fields().get(0).attributeMetadata().get(0).value()); + + String manifest = IrGpuArtifactSerializer.serialize(result.irGpuArtifact()); + var reparsed = IrGpuArtifactParser.parse(manifest); + + assertEquals(result.irGpuArtifact().module().entryAttributeMetadata(), reparsed.module().entryAttributeMetadata()); + assertEquals( + result.irGpuArtifact().module().helperMethods().get(0).attributeMetadata(), + reparsed.module().helperMethods().get(0).attributeMetadata() + ); + assertEquals(result.irGpuArtifact().structMetadata().get(0).attributeMetadata(), reparsed.structMetadata().get(0).attributeMetadata()); + assertEquals( + result.irGpuArtifact().structMetadata().get(0).fields().get(0).attributeMetadata(), + reparsed.structMetadata().get(0).fields().get(0).attributeMetadata() + ); + assertTrue(manifest.contains("entry.attributeMetadata.0.kind=required-work-group-size")); + assertTrue(manifest.contains("helper.0.attributeMetadata.0.kind=always-inline")); + assertTrue(manifest.contains("structMetadata.0.attributeMetadata.0.kind=packed")); + assertTrue(manifest.contains("structMetadata.0.field.0.attributeMetadata.0.value=8")); + } + @Test void parsesValidatesLowersAndEmitsKernelWithExternalInlineCCodeHelper() { String methodSource = """ diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/parser/GpuMethodParserTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/parser/GpuMethodParserTest.java index 71373307..5007a939 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/parser/GpuMethodParserTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/parser/GpuMethodParserTest.java @@ -183,6 +183,57 @@ void kernel(@GPUGlobal float[] output) { assertEquals("reqd_work_group_size(8, 4, 2)", method.openClAttributes().get(0)); } + @Test + void parsesPortableWorkGroupSizeHintAsOpenClAttribute() { + String methodSource = """ + @GPUWorkGroupSizeHint(x = 4, y = 2, z = 1) + @GPU + void kernel(@GPUGlobal float[] output) { + output[0] = 1.0f; + } + """; + + GpuMethodParser parser = new GpuMethodParser(); + ParsedGpuMethod method = parser.parseMethod(methodSource); + + assertEquals(1, method.openClAttributes().size()); + assertEquals("work_group_size_hint(4, 2, 1)", method.openClAttributes().get(0)); + } + + @Test + void parsesPortableVectorTypeHintAsOpenClAttribute() { + String methodSource = """ + @GPUVectorTypeHint("float4") + @GPU + void kernel(@GPUGlobal float[] output) { + output[0] = 1.0f; + } + """; + + GpuMethodParser parser = new GpuMethodParser(); + ParsedGpuMethod method = parser.parseMethod(methodSource); + + assertEquals(1, method.openClAttributes().size()); + assertEquals("vec_type_hint(float4)", method.openClAttributes().get(0)); + } + + @Test + void parsesPortableAlwaysInlineAsOpenClAttribute() { + String methodSource = """ + @GPUAlwaysInline + @CCode(inline = true) + float helper(float value) { + return value * value; + } + """; + + GpuMethodParser parser = new GpuMethodParser(); + ParsedGpuMethod method = parser.parseMethod(methodSource); + + assertEquals(1, method.openClAttributes().size()); + assertEquals("always_inline", method.openClAttributes().get(0)); + } + @Test void parsesConstantAndLocalAddressSpaces() { String methodSource = """ diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/parser/GpuStructParserTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/parser/GpuStructParserTest.java new file mode 100644 index 00000000..2782b06c --- /dev/null +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/parser/GpuStructParserTest.java @@ -0,0 +1,45 @@ +package net.sixik.ga_utils.javatogpu.frontend.parser; + +import net.sixik.ga_utils.javatogpu.frontend.model.ParsedGpuStruct; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class GpuStructParserTest { + + @Test + void parsesPortablePackedAndAlignedStructAttributes() { + String structSource = """ + @GPUPacked + @GPUAligned(16) + @GPUStruct + class Pair { + float x; + float y; + } + """; + + GpuStructParser parser = new GpuStructParser(); + ParsedGpuStruct struct = parser.parseStruct(structSource); + + assertEquals(java.util.List.of("packed", "aligned(16)"), struct.openClAttributes()); + } + + @Test + void parsesPortableAlignedStructFieldAttribute() { + String structSource = """ + @GPUStruct + class Pair { + @GPUAligned(8) + float x; + float y; + } + """; + + GpuStructParser parser = new GpuStructParser(); + ParsedGpuStruct struct = parser.parseStruct(structSource); + + assertEquals(java.util.List.of("aligned(8)"), struct.fields().get(0).openClAttributes()); + assertEquals(java.util.List.of(), struct.fields().get(1).openClAttributes()); + } +} diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/processors/GpuCompilerProcessorTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/processors/GpuCompilerProcessorTest.java index 6dcaa436..8169d17c 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/processors/GpuCompilerProcessorTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/processors/GpuCompilerProcessorTest.java @@ -7943,6 +7943,58 @@ static void kernel(@GPUGlobal float[] output) { }""", Files.readString(kernelPath)); } + @Test + void generatesKernelWithPortableGpuWorkGroupSizeHint() throws IOException { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + Path classOutputDir = Files.createTempDirectory("javatogpu-portable-workgroup-hint-classes"); + Path generatedOutputDir = Files.createTempDirectory("javatogpu-portable-workgroup-hint-generated"); + + String source = """ + package sample; + + import net.sixik.ga_utils.javatogpu.api.GPU; + import net.sixik.ga_utils.javatogpu.api.annotations.GPUGlobal; + import net.sixik.ga_utils.javatogpu.api.annotations.GPUWorkGroupSizeHint; + + public class Demo { + @GPUWorkGroupSizeHint(x = 4, y = 2, z = 1) + @net.sixik.ga_utils.javatogpu.api.annotations.GPU + static void kernel(@GPUGlobal float[] output) { + int id = GPU.get_global_id(0); + output[id] = 1.0f; + } + } + """; + + try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null)) { + List options = List.of( + "-classpath", System.getProperty("java.class.path"), + "-d", classOutputDir.toString(), + "-s", generatedOutputDir.toString() + ); + JavaFileObject sourceFile = new StringJavaFileObject("sample.Demo", source); + JavaCompiler.CompilationTask task = compiler.getTask( + null, + fileManager, + null, + options, + null, + List.of(sourceFile) + ); + task.setProcessors(List.of(new GpuCompilerProcessor())); + + assertTrue(task.call()); + } + + Path kernelPath = generatedOutputDir.resolve("javatogpu/sample/Demo/kernel.cl"); + assertTrue(Files.exists(kernelPath)); + assertEquals(""" + __attribute__((work_group_size_hint(4, 2, 1))) __kernel void jtg_kernel(__global float* output) { + int id = get_global_id(0); + output[id] = 1.0F; + }""", Files.readString(kernelPath)); + } + @Test void generatesKernelWithNestedGpuStructsAndStructConstants() throws IOException { JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); From 4d2ddddd5f4fd22aeccdf98e0aa525e0ae2f2c7d Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Sat, 11 Jul 2026 15:56:46 +0300 Subject: [PATCH 02/32] Project portable GPU metadata to OpenCL attributes --- .../opencl/OpenClAttributeProjection.java | 78 +++++++++++++++++++ .../frontend/opencl/OpenClKernelEmitter.java | 22 ++++-- .../opencl/OpenClIrGpuSourceAssembler.java | 32 ++++++-- 3 files changed, 121 insertions(+), 11 deletions(-) create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/opencl/OpenClAttributeProjection.java diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/opencl/OpenClAttributeProjection.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/opencl/OpenClAttributeProjection.java new file mode 100644 index 00000000..3afb1f15 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/opencl/OpenClAttributeProjection.java @@ -0,0 +1,78 @@ +package net.sixik.ga_utils.javatogpu.frontend.opencl; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuAttributeMetadata; +import net.sixik.ga_utils.javatogpu.frontend.model.GpuAttributeMetadata; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; + +/** + * Projects backend-neutral JavaToGpu attribute metadata to OpenCL attribute strings. + */ +public final class OpenClAttributeProjection { + + private OpenClAttributeProjection() { + } + + public static List project( + List legacyOpenClAttributes, + List attributeMetadata + ) { + LinkedHashSet attributes = new LinkedHashSet<>(); + for (GpuAttributeMetadata metadata : safeModelMetadata(attributeMetadata)) { + project(metadata.kind(), metadata.value()).forEach(attributes::add); + } + attributes.addAll(safeStrings(legacyOpenClAttributes)); + return List.copyOf(attributes); + } + + public static List projectIr( + List legacyOpenClAttributes, + List attributeMetadata + ) { + LinkedHashSet attributes = new LinkedHashSet<>(); + for (IrGpuAttributeMetadata metadata : safeIrMetadata(attributeMetadata)) { + project(metadata.kind(), metadata.value()).forEach(attributes::add); + } + attributes.addAll(safeStrings(legacyOpenClAttributes)); + return List.copyOf(attributes); + } + + private static List project(String kind, String value) { + String normalizedKind = kind == null ? "" : kind; + String normalizedValue = value == null ? "" : value; + return switch (normalizedKind) { + case "packed" -> List.of("packed"); + case "aligned" -> List.of("aligned(" + normalizedValue + ")"); + case "required-work-group-size" -> List.of("reqd_work_group_size(" + dimensions(normalizedValue) + ")"); + case "work-group-size-hint" -> List.of("work_group_size_hint(" + dimensions(normalizedValue) + ")"); + case "vector-type-hint" -> normalizedValue.isBlank() ? List.of() : List.of("vec_type_hint(" + normalizedValue + ")"); + case "always-inline" -> List.of("always_inline"); + default -> List.of(); + }; + } + + private static String dimensions(String value) { + String[] parts = value.split(",", -1); + ArrayList normalized = new ArrayList<>(); + for (String part : parts) { + if (!part.isBlank()) { + normalized.add(part.strip()); + } + } + return String.join(", ", normalized); + } + + private static List safeStrings(List values) { + return values == null ? List.of() : values; + } + + private static List safeModelMetadata(List values) { + return values == null ? List.of() : values; + } + + private static List safeIrMetadata(List values) { + return values == null ? List.of() : values; + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/opencl/OpenClKernelEmitter.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/opencl/OpenClKernelEmitter.java index 65891789..557e2e3e 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/opencl/OpenClKernelEmitter.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/opencl/OpenClKernelEmitter.java @@ -248,9 +248,13 @@ private String emitParameterPostfixQualifiers(ParsedGpuParameter parameter) { private void emitStruct(StringBuilder builder, ParsedGpuStruct struct) { builder.append("typedef struct"); - if (!struct.openClAttributes().isEmpty()) { + List structAttributes = OpenClAttributeProjection.project( + struct.openClAttributes(), + struct.attributeMetadata() + ); + if (!structAttributes.isEmpty()) { builder.append(" "); - emitAttributes(builder, struct.openClAttributes(), true); + emitAttributes(builder, structAttributes, true); } builder.append("{\n"); for (ParsedGpuStructField field : struct.fields()) { @@ -258,7 +262,7 @@ private void emitStruct(StringBuilder builder, ParsedGpuStruct struct) { .append(emitType(field.javaType())) .append(" ") .append(field.name()); - emitAttributes(builder, field.openClAttributes(), false); + emitAttributes(builder, OpenClAttributeProjection.project(field.openClAttributes(), field.attributeMetadata()), false); builder.append(";\n"); } builder.append("} ") @@ -612,7 +616,11 @@ private void emitFunction( if (!kernel && parsedMethod.inline()) { builder.append("inline "); } - emitAttributes(builder, parsedMethod.openClAttributes(), true); + emitAttributes( + builder, + OpenClAttributeProjection.project(parsedMethod.openClAttributes(), parsedMethod.attributeMetadata()), + true + ); builder.append(kernel ? "__kernel void " : emitType(parsedMethod.returnType()) + " ") .append(emittedName) .append("(") @@ -798,7 +806,11 @@ private void emitFunctionPrototype(StringBuilder builder, ParsedGpuMethod parsed if (parsedMethod.inline()) { builder.append("inline "); } - emitAttributes(builder, parsedMethod.openClAttributes(), true); + emitAttributes( + builder, + OpenClAttributeProjection.project(parsedMethod.openClAttributes(), parsedMethod.attributeMetadata()), + true + ); builder.append(emitType(parsedMethod.returnType())) .append(" ") .append(emittedName) diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClIrGpuSourceAssembler.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClIrGpuSourceAssembler.java index abbfb59e..92e64f00 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClIrGpuSourceAssembler.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClIrGpuSourceAssembler.java @@ -6,6 +6,7 @@ import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModuleMethod; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuStructFieldMetadata; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuStructMetadata; +import net.sixik.ga_utils.javatogpu.frontend.opencl.OpenClAttributeProjection; import net.sixik.ga_utils.javatogpu.types.GpuTypeSupport; import java.util.ArrayList; @@ -94,7 +95,14 @@ public OpenClIrGpuSourceAssemblyResult assemble( for (String helperSource : helperSources) { source.append(helperSource); } - emitAttributes(source, artifact.module().entryOpenClAttributes(), false); + emitAttributes( + source, + OpenClAttributeProjection.projectIr( + artifact.module().entryOpenClAttributes(), + artifact.module().entryAttributeMetadata() + ), + false + ); source.append("__kernel void ") .append(emittedName) .append("(") @@ -112,9 +120,13 @@ public OpenClIrGpuSourceAssemblyResult assemble( private static String emitStruct(IrGpuStructMetadata struct) { StringBuilder builder = new StringBuilder(); builder.append("typedef struct"); - if (!struct.openClAttributes().isEmpty()) { + List structAttributes = OpenClAttributeProjection.projectIr( + struct.openClAttributes(), + struct.attributeMetadata() + ); + if (!structAttributes.isEmpty()) { builder.append(' '); - emitAttributes(builder, struct.openClAttributes(), true); + emitAttributes(builder, structAttributes, true); } builder.append("{\n"); for (IrGpuStructFieldMetadata field : struct.fields()) { @@ -122,7 +134,7 @@ private static String emitStruct(IrGpuStructMetadata struct) { .append(emitType(field.javaType())) .append(' ') .append(field.name()); - emitAttributes(builder, field.openClAttributes(), false); + emitAttributes(builder, OpenClAttributeProjection.projectIr(field.openClAttributes(), field.attributeMetadata()), false); builder.append(";\n"); } builder.append("} ") @@ -166,7 +178,11 @@ private static String emitHelper(IrGpuModuleMethod helperMethod, String emittedB } StringBuilder builder = new StringBuilder(); appendInlinePrefix(builder, helperMethod); - emitAttributes(builder, helperMethod.openClAttributes(), false); + emitAttributes( + builder, + OpenClAttributeProjection.projectIr(helperMethod.openClAttributes(), helperMethod.attributeMetadata()), + false + ); builder.append(emitType(helperMethod.returnType())) .append(" ") .append(emittedName) @@ -196,7 +212,11 @@ private static String emitHelperPrototype(IrGpuModuleMethod helperMethod, List Date: Sat, 11 Jul 2026 16:15:31 +0300 Subject: [PATCH 03/32] Validate portable GPU metadata via OpenCL projection --- .../validation/GpuSubsetValidator.java | 7 +- .../validation/GpuSubsetValidatorTest.java | 67 +++++++++++++++++++ 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/validation/GpuSubsetValidator.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/validation/GpuSubsetValidator.java index 98fccb36..7babbe58 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/validation/GpuSubsetValidator.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/validation/GpuSubsetValidator.java @@ -44,6 +44,7 @@ import net.sixik.ga_utils.javatogpu.frontend.model.ParsedGpuParameter; import net.sixik.ga_utils.javatogpu.frontend.model.ParsedGpuStruct; import net.sixik.ga_utils.javatogpu.frontend.model.ParsedGpuStructField; +import net.sixik.ga_utils.javatogpu.frontend.opencl.OpenClAttributeProjection; import net.sixik.ga_utils.javatogpu.frontend.opencl.OpenClKernelNaming; import net.sixik.ga_utils.javatogpu.types.GpuTypeSupport; @@ -380,7 +381,7 @@ private void validateOpenClAttributes( private void validateMethodAttributes(ParsedGpuMethod method, boolean kernelEntry, List issues) { Set seenUniqueAttributes = new HashSet<>(); - for (String rawAttribute : method.openClAttributes()) { + for (String rawAttribute : OpenClAttributeProjection.project(method.openClAttributes(), method.attributeMetadata())) { AttributeSpec attribute = parseAttribute(rawAttribute); if (!kernelEntry) { if (!HELPER_METHOD_ATTRIBUTES.contains(attribute.name())) { @@ -413,7 +414,7 @@ private void validateMethodAttributes(ParsedGpuMethod method, boolean kernelEntr private void validateStructAttributes(ParsedGpuStruct struct, List issues) { Set seenUniqueAttributes = new HashSet<>(); - for (String rawAttribute : struct.openClAttributes()) { + for (String rawAttribute : OpenClAttributeProjection.project(struct.openClAttributes(), struct.attributeMetadata())) { AttributeSpec attribute = parseAttribute(rawAttribute); if (!STRUCT_ATTRIBUTES.contains(attribute.name())) { if (KERNEL_METHOD_ATTRIBUTES.contains(attribute.name())) { @@ -429,7 +430,7 @@ private void validateStructAttributes(ParsedGpuStruct struct, List seenFieldAttributes = new HashSet<>(); - for (String rawAttribute : field.openClAttributes()) { + for (String rawAttribute : OpenClAttributeProjection.project(field.openClAttributes(), field.attributeMetadata())) { AttributeSpec attribute = parseAttribute(rawAttribute); if (!FIELD_ATTRIBUTES.contains(attribute.name())) { issues.add(new GpuValidationIssue( diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/validation/GpuSubsetValidatorTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/validation/GpuSubsetValidatorTest.java index d8383041..0b5ac84b 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/validation/GpuSubsetValidatorTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/validation/GpuSubsetValidatorTest.java @@ -727,6 +727,30 @@ float helper(float value) { )); } + @Test + void acceptsPortableAlwaysInlineAttributeOnHelperMethod() { + String kernelSource = """ + @GPU + void kernel(@GPUGlobal float[] input, @GPUGlobal float[] output) { + int id = GPU.get_global_id(0); + output[id] = helper(input[id]); + } + """; + String helperSource = """ + @GPUAlwaysInline + @CCode + float helper(float value) { + return value * 2.0f; + } + """; + + assertDoesNotThrow(() -> validator.validateKernel( + parser.parseMethod(kernelSource, "Demo", "sample.Demo"), + java.util.List.of(parser.parseMethod(helperSource, "Helpers", "sample.Helpers")), + java.util.List.of() + )); + } + @Test void acceptsConstOpenClQualifierOnHelperPointerParameter() { String kernelSource = """ @@ -1084,6 +1108,49 @@ void kernel(@GPUGlobal float[] output) { ); } + @Test + void rejectsDuplicateKernelMethodAttributesAcrossPortableMetadataAndRawOpenCl() { + String methodSource = """ + @GPUVectorTypeHint("float4") + @OpenCLAttributes({"vec_type_hint(int4)"}) + @GPU + void kernel(@GPUGlobal float[] output) { + output[0] = 1.0f; + } + """; + + GpuValidationException exception = assertThrows( + GpuValidationException.class, + () -> validator.validate(parser.parseMethod(methodSource)) + ); + + assertEquals( + "Duplicate OpenCL attribute: vec_type_hint", + exception.getMessage() + ); + } + + @Test + void rejectsPortableStructAttributeOnKernelMethodThroughMetadataProjection() { + String methodSource = """ + @GPUPacked + @GPU + void kernel(@GPUGlobal float[] output) { + output[0] = 1.0f; + } + """; + + GpuValidationException exception = assertThrows( + GpuValidationException.class, + () -> validator.validate(parser.parseMethod(methodSource)) + ); + + assertEquals( + "OpenCL attribute 'packed' is not valid on @GPU methods", + exception.getMessage() + ); + } + @Test void acceptsVectorArrayKernelParameters() { String methodSource = """ From 1d03ae677f4697266aa6f039cee10e72a46ceb43 Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Sat, 11 Jul 2026 16:39:36 +0300 Subject: [PATCH 04/32] Update OpenCL promotion manifest for RTX 5070 --- promotion/opencl/backend-source-promotion-manifest.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/promotion/opencl/backend-source-promotion-manifest.properties b/promotion/opencl/backend-source-promotion-manifest.properties index 6a6ad942..3ce9a6ed 100644 --- a/promotion/opencl/backend-source-promotion-manifest.properties +++ b/promotion/opencl/backend-source-promotion-manifest.properties @@ -1,10 +1,10 @@ formatVersion=1 status=approved scope=real-workload-production-candidate-manual-approval -approval.id=approval:rtx5070-f9e17016-20260711 +approval.id=approval:rtx5070-75081921-20260711 approval.approvedBy=manual-workflow-operator approval.approvedAtUtc=2026-07-11T11:02:16Z -binding.gitSha=f9e1701637ec4e4cff5223733bf5d784c940c742 +binding.gitSha=75081921bd9f3a85337172e93ce7b4629437e89f binding.candidateArtifact.sha256=36d3929bdee378c947d299a31337cee40a9cf7e6d3e260fb921133c01801330d binding.backendTarget=OPENCL binding.deviceVendor=NVIDIA Corporation From f6f9deed04e8e2a19615ce8fb1eb821fa76b1d35 Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Sat, 11 Jul 2026 17:03:20 +0300 Subject: [PATCH 05/32] Document RTX 5070 production promotion checkpoint --- ROADMAP.md | 6 ++++-- docs/Public-API-And-Extension-Contract.md | 8 +++++--- docs/Validation-and-Operations.md | 3 ++- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 32bd22ad..029b3e40 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -18,6 +18,8 @@ Older files remain useful as detailed references, but this file is the primary e Deferred until Intel OpenCL hardware is available locally or through a stable runner. - [x] Run the existing validation suite repeatedly on real NVIDIA OpenCL hardware. Current recorded lanes include `NVIDIA CUDA / NVIDIA GeForce RTX 3060` and `NVIDIA CUDA / NVIDIA GeForce RTX 5070`; both pass the full `:processor:openClOperationalRoutine` evidence flow with vendor validation, integration smoke, workload-equivalence, long-running stability, benchmark, bucket-status, and validation-history artifacts all passing. +- [x] Record a production-ready RTX 5070 promotion checkpoint. + The RTX 5070 activation run for commit `1d03ae677f4697266aa6f039cee10e72a46ceb43` validates manifest approval `approval:rtx5070-75081921-20260711` bound to candidate SHA `75081921bd9f3a85337172e93ce7b4629437e89f`. It records `production-promotion-explainability.status=production-ready`, `contract.status=valid`, `decision.mode=production-enabled`, source parity/runtime equivalence passed, activation-token smoke passed, activation-token negative controls passed, `readinessChecklist.ready.count=11`, `readinessChecklist.blocked.count=0`, and `blocker.count=0` on `NVIDIA CUDA / NVIDIA GeForce RTX 5070` driver `595.97`. - [x] Run the existing validation suite repeatedly on real AMD OpenCL hardware. Current recorded lane: `AMD Accelerated Parallel Processing / gfx1101` on AMD RX 7800 XT, platform `OpenCL 2.1 AMD-APP (3679.0)`, device version `OpenCL 2.0 AMD-APP (3679.0)`, with the full OpenCL operational evidence flow passing. - [x] Establish NVIDIA/AMD interim operational validation mode. @@ -482,8 +484,8 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i Public source-level APIs such as `net.sixik.ga_utils.javatogpu.api.GPU` should intentionally keep OpenCL-style syntax and naming for intrinsics, work-item queries, vector helpers, memory concepts, and math functions. Future CUDA, Vulkan/SPIR-V, and Metal support should be implemented as backend lowerers and runtime planners behind the same source dialect, not as parallel CUDA-style, Vulkan-style, or Metal-style Java APIs. This keeps user code, docs, examples, diagnostics, and IR metadata consistent while still allowing backend-specific lowering and optimization internally. - [ ] Keep backend-specific attributes and qualifiers as explicit escape hatches. Existing low-level surfaces such as `@OpenCLAttributes` and `@OpenCLQualifiers` should remain available for expert OpenCL-only code, but they must be modeled as backend-specific metadata rather than portable intent. The compiler should reject or ignore them fail-closed when compiling for CUDA/Vulkan/Metal unless the method is explicitly constrained to OpenCL, and diagnostics should suggest the backend-neutral replacement when one exists, for example a neutral work-group-size annotation instead of raw `reqd_work_group_size(...)` or a neutral memory-space annotation instead of backend-only pointer qualifiers. -- [ ] Persist backend-neutral annotation intent in `IrGpu` storage. - `kernel.irgpu.properties` should carry normalized annotation metadata separately from backend-specific raw attributes: kernel entry kind, address-space intent, work-group size hints/requirements, unroll policy, fast-math policy, backend/device compatibility, and any raw backend escape hatches. This prevents OpenCL-generated source from becoming the only source of truth and lets future CUDA/Vulkan/Metal lowerers consume the same `IrGpu` artifact without parsing OpenCL-specific strings. +- [x] Persist backend-neutral annotation intent in `IrGpu` storage for portable attribute metadata. + `kernel.irgpu.properties` now carries normalized `attributeMetadata.*` records for modeled portable concepts such as required/preferred work-group size, vector type hints, packed/aligned layout metadata, and always-inline helper intent. Java source parsing, frontend OpenCL emission, runtime `IrGpu` source assembly, and validator checks all project this metadata through `OpenClAttributeProjection`, while legacy `openClAttribute.*`, `@OpenCLAttributes`, and `@GPUAttribute` remain compatibility/escape-hatch surfaces. Remaining future work is to extend the same normalized model to any additional annotation intent such as unroll policy or backend-specific raw selector metadata. - [x] Persist method device constraints in `IrGpu` storage. `kernel.irgpu.properties` now carries `methodDeviceConstraint.*` records with method/emitted names, supported backends, supported vendors, supported device classes, required features, and metadata source. Serializer/parser round trips preserve the contract, and Java plus ASM paths use the same normalized model. - [x] Persist fallback groups and native-code compatibility notes in `IrGpu` storage. diff --git a/docs/Public-API-And-Extension-Contract.md b/docs/Public-API-And-Extension-Contract.md index 1b996080..dabcd9cd 100644 --- a/docs/Public-API-And-Extension-Contract.md +++ b/docs/Public-API-And-Extension-Contract.md @@ -16,8 +16,10 @@ static void kernel(@GPUGlobal float[] output) { ``` Common backend metadata should also prefer portable annotations before raw strings: `@GPUWorkGroupSizeHint`, -`@GPUVectorTypeHint`, `@GPUPacked`, `@GPUAligned`, and `@GPUAlwaysInline` lower to the current OpenCL metadata path -today while preserving source intent for future CUDA, Vulkan/SPIR-V, and Metal lowerers. +`@GPUVectorTypeHint`, `@GPUPacked`, `@GPUAligned`, and `@GPUAlwaysInline` lower through backend-neutral +`attributeMetadata` in `kernel.irgpu.properties`. OpenCL currently projects that metadata back to OpenCL attributes +for validation and source emission, while future CUDA, Vulkan/SPIR-V, and Metal lowerers should consume the same +metadata directly instead of parsing OpenCL-specific strings. Use `@GPUAttribute` only when no portable annotation exists yet. It supports repeated usage on the same method, field, type, or parameter: @@ -41,7 +43,7 @@ For vendor or device-specific metadata, narrow the selector explicitly: ) ``` -`@GPUAttribute` is not portable by itself. A backend lowerer may consume it only when the selected backend, vendor, and device class match the annotation selectors; otherwise it should reject or ignore it fail-closed with a diagnostic. Existing `@OpenCLAttributes` and `@OpenCLQualifiers` remain compatibility surfaces for OpenCL-only code, but new backend-specific metadata should prefer `@GPUAttribute`. Generic OpenCL emission should not apply vendor/device-specific raw attributes until device-aware lowering is available. +`@GPUAttribute` is not portable by itself. A backend lowerer may consume it only when the selected backend, vendor, and device class match the annotation selectors; otherwise it should reject or ignore it fail-closed with a diagnostic. Existing `@OpenCLAttributes` and `@OpenCLQualifiers` remain compatibility surfaces for OpenCL-only code, but new backend-specific metadata should prefer `@GPUAttribute`. Generic OpenCL emission should not apply vendor/device-specific raw attributes until device-aware lowering is available. Portable `attributeMetadata` is the source-of-truth for modeled concepts; backend-specific string attributes are compatibility projections or explicit escape hatches. ## Runtime selection diff --git a/docs/Validation-and-Operations.md b/docs/Validation-and-Operations.md index e131aee6..285ea56f 100644 --- a/docs/Validation-and-Operations.md +++ b/docs/Validation-and-Operations.md @@ -34,6 +34,7 @@ This means: - AMD RX 7800 XT validation is green. - Intel should still be validated on real hardware before making broad cross-vendor claims. - A green local validation run proves the tested commit, machine, driver, and backend, not universal OpenCL behavior. +- The RTX 5070 lane has a production-ready OpenCL promotion checkpoint for approval `approval:rtx5070-75081921-20260711`, bound to candidate SHA `75081921bd9f3a85337172e93ce7b4629437e89f`, with manifest validation, activation gate, activation-token smoke, activation-token negative controls, source parity, runtime equivalence, and the production readiness checklist all passing. ## Main Validation Buckets @@ -95,7 +96,7 @@ Manual `workflow_dispatch` runs expose `production_promotion_manifest_mode=skip| Use `activate` only after manifest validation succeeds. The workflow then writes and validates the controlled activation gate plus its SHA-256 sidecar, loads the exact artifact into a `GpuProductionActivationToken`, and runs every approved real workload kernel on the selected device. The positive hardware result is written to `production-activation-token-smoke.properties` with per-kernel status and workload coverage. A required negative lane then verifies that a mismatched SHA-256 is rejected and that an unapproved kernel is blocked before output mutation; its result is written to `production-activation-token-negative.properties`. The `activate` lane fails if either check fails. This mode does not enable default runtime activation, default production source switching, or production mutation. -`openClValidationReport` folds both activation-token artifacts into production-promotion explainability. A successful controlled activation records token loading, approved-kernel execution, full real-workload coverage, digest-mismatch rejection, unapproved-kernel rejection, unchanged rejected output, and safe defaults as separate readiness evidence. The overall production status remains blocked while default production source switching or production mutation is disabled. +`openClValidationReport` folds both activation-token artifacts into production-promotion explainability. A successful controlled activation records token loading, approved-kernel execution, full real-workload coverage, digest-mismatch rejection, unapproved-kernel rejection, unchanged rejected output, and safe defaults as separate readiness evidence. The RTX 5070 promotion checkpoint now demonstrates the complete production-ready path: `production-promotion-explainability.properties` records `status=production-ready`, `contract.status=valid`, `decision.mode=production-enabled`, `readinessChecklist.ready.count=11`, and `blocker.count=0` while still requiring explicit manifest-bound activation evidence. The vendor workflow also runs `openClOptimizerFamilyPayloadFixtureTest` after the main validation bucket. It must produce `fixture-summary.properties` with two complete families and fourteen durable files. This fixture proves the nested artifact contract is uploadable and path-safe; it does not alter real-workload optimizer-family counts or production readiness. From 5a5a154a99b709b560765eaa78061df28ccedd5b Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Sat, 11 Jul 2026 17:07:11 +0300 Subject: [PATCH 06/32] Suggest portable replacements for raw OpenCL attributes --- .../validation/GpuSubsetValidator.java | 25 ++++++++++-- .../validation/GpuSubsetValidatorTest.java | 39 +++++++++++++++++-- 2 files changed, 56 insertions(+), 8 deletions(-) diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/validation/GpuSubsetValidator.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/validation/GpuSubsetValidator.java index 7babbe58..fde01975 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/validation/GpuSubsetValidator.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/validation/GpuSubsetValidator.java @@ -387,7 +387,7 @@ private void validateMethodAttributes(ParsedGpuMethod method, boolean kernelEntr if (!HELPER_METHOD_ATTRIBUTES.contains(attribute.name())) { issues.add(issue( method.declaration(), - "OpenCL attribute '" + attribute.name() + "' is not valid on @CCode helpers" + openClAttributeInvalidHereMessage(attribute, "@CCode helpers") )); continue; } @@ -397,7 +397,7 @@ private void validateMethodAttributes(ParsedGpuMethod method, boolean kernelEntr } if (!KERNEL_METHOD_ATTRIBUTES.contains(attribute.name())) { if (STRUCT_ATTRIBUTES.contains(attribute.name()) || FIELD_ATTRIBUTES.contains(attribute.name())) { - issues.add(issue(method.declaration(), "OpenCL attribute '" + attribute.name() + "' is not valid on @GPU methods")); + issues.add(issue(method.declaration(), openClAttributeInvalidHereMessage(attribute, "@GPU methods"))); } continue; } @@ -418,7 +418,7 @@ private void validateStructAttributes(ParsedGpuStruct struct, List "; use @GPUWorkGroupSize for portable required work-group size metadata"; + case "work_group_size_hint" -> "; use @GPUWorkGroupSizeHint for portable preferred work-group size metadata"; + case "vec_type_hint" -> "; use @GPUVectorTypeHint for portable vector type hint metadata"; + case "packed" -> "; use @GPUPacked on @GPUStruct types for portable packed layout metadata"; + case "aligned" -> "; use @GPUAligned on @GPUStruct types or fields for portable alignment metadata"; + case "always_inline" -> "; use @GPUAlwaysInline on @CCode helpers for portable helper inline metadata"; + default -> ""; + }; + } + private void validateStructFieldTypes( List structs, GpuStructAliasRegistry structRegistry, diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/validation/GpuSubsetValidatorTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/validation/GpuSubsetValidatorTest.java index 0b5ac84b..4a6dc86b 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/validation/GpuSubsetValidatorTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/validation/GpuSubsetValidatorTest.java @@ -534,7 +534,7 @@ void kernel(@GPUGlobal float[] output) { ); assertEquals( - "OpenCL attribute 'packed' is not valid on @GPUStruct fields", + "OpenCL attribute 'packed' is not valid on @GPUStruct fields; use @GPUPacked on @GPUStruct types for portable packed layout metadata", exception.getMessage() ); } @@ -698,7 +698,7 @@ void helper(float[] output) { ); assertEquals( - "OpenCL attribute 'reqd_work_group_size' is not valid on @CCode helpers", + "OpenCL attribute 'reqd_work_group_size' is not valid on @CCode helpers; use @GPUWorkGroupSize for portable required work-group size metadata", exception.getMessage() ); } @@ -1061,7 +1061,7 @@ void kernel(@GPUGlobal float[] output) { ); assertEquals( - "OpenCL attribute 'packed' is not valid on @GPU methods", + "OpenCL attribute 'packed' is not valid on @GPU methods; use @GPUPacked on @GPUStruct types for portable packed layout metadata", exception.getMessage() ); } @@ -1146,7 +1146,38 @@ void kernel(@GPUGlobal float[] output) { ); assertEquals( - "OpenCL attribute 'packed' is not valid on @GPU methods", + "OpenCL attribute 'packed' is not valid on @GPU methods; use @GPUPacked on @GPUStruct types for portable packed layout metadata", + exception.getMessage() + ); + } + + @Test + void suggestsPortableReplacementForRawVectorTypeHintOnStruct() { + String structSource = """ + @OpenCLAttributes({"vec_type_hint(float4)"}) + @GPUStruct + class Sample { + float x; + } + """; + String methodSource = """ + @GPU + void kernel(@GPUGlobal float[] output) { + output[0] = 1.0f; + } + """; + + GpuValidationException exception = assertThrows( + GpuValidationException.class, + () -> validator.validateKernel( + parser.parseMethod(methodSource, "Demo", "sample.Demo"), + java.util.List.of(), + java.util.List.of(structParser.parseStruct(structSource, "Sample", "sample.Sample")) + ) + ); + + assertEquals( + "OpenCL attribute 'vec_type_hint' is not valid on @GPUStruct types; use @GPUVectorTypeHint for portable vector type hint metadata", exception.getMessage() ); } From 6d528c4803974681efc6054fb5315420453cafc9 Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Sat, 11 Jul 2026 17:15:08 +0300 Subject: [PATCH 07/32] Document OpenCL escape hatch guidance --- ROADMAP.md | 2 ++ docs/API-Overview.md | 4 +++- docs/Public-API-And-Extension-Contract.md | 5 ++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 029b3e40..a982150c 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -146,6 +146,8 @@ Older files remain useful as detailed references, but this file is the primary e The current repo-level slice now covers method/struct/field attribute validation, duplicate/malformed attribute diagnostics, helper-only and kernel-only attribute rules, and practical qualifier validation for `const` / `restrict` / `volatile` including duplicate, unsupported, and non-pointer misuse cases. - [x] Decide whether `restrict` should become a user-facing source concept, a low-level concept only, or remain unsupported. Current decision: keep `restrict` as an explicitly low-level user-facing concept via `@OpenCLQualifiers`, limited to pointer-like GPU parameters rather than elevating it into the normal high-level Java surface. +- [x] Document portable-first metadata guidance and raw OpenCL escape hatches. + Modeled attribute concepts now have portable annotations and `IrGpu` `attributeMetadata` as the source of truth. Raw `@OpenCLAttributes`, `@OpenCLQualifiers`, and `@GPUAttribute` remain available for OpenCL-only or backend-specific expert code, while diagnostics steer common raw OpenCL attributes toward portable replacements. Future CUDA/Vulkan/Metal work should consume the portable metadata directly rather than treating OpenCL strings as the cross-backend contract. ## D. Helper / Reuse Architecture diff --git a/docs/API-Overview.md b/docs/API-Overview.md index 9771b9f4..d5a6f6f4 100644 --- a/docs/API-Overview.md +++ b/docs/API-Overview.md @@ -28,7 +28,7 @@ Use these in source code that should compile to GPU code. - `@GPUAttribute` is the backend-aware raw escape hatch for metadata JavaToGpu does not model portably yet. - `@OpenCLAttributes` and `@OpenCLQualifiers` remain OpenCL-only compatibility annotations for existing code. -Prefer portable annotations first. Use raw attributes only for backend-specific code that cannot be expressed through the normal API. +Prefer portable annotations first. Use raw attributes only for backend-specific code that cannot be expressed through the normal API. When a raw OpenCL attribute maps to a modeled concept but is placed in an invalid context, validator diagnostics point to the portable annotation replacement instead of making users decode backend-specific attribute rules. ```java @GPU @@ -134,6 +134,8 @@ static void openClOnlyKernel(@GPUGlobal float[] output) { } ``` +For OpenCL-only legacy code, `@OpenCLAttributes` and `@OpenCLQualifiers` remain valid compatibility annotations. For new code, prefer portable annotations for modeled concepts and `@GPUAttribute` for deliberate backend-specific metadata. + ## `GPU.*` Builtins Use `GPU.*` inside kernels for operations the compiler knows how to lower. diff --git a/docs/Public-API-And-Extension-Contract.md b/docs/Public-API-And-Extension-Contract.md index dabcd9cd..56c46e42 100644 --- a/docs/Public-API-And-Extension-Contract.md +++ b/docs/Public-API-And-Extension-Contract.md @@ -20,6 +20,9 @@ Common backend metadata should also prefer portable annotations before raw strin `attributeMetadata` in `kernel.irgpu.properties`. OpenCL currently projects that metadata back to OpenCL attributes for validation and source emission, while future CUDA, Vulkan/SPIR-V, and Metal lowerers should consume the same metadata directly instead of parsing OpenCL-specific strings. +When users provide a raw OpenCL attribute for a concept JavaToGpu already models, diagnostics should steer them to +the portable annotation replacement such as `@GPUWorkGroupSize`, `@GPUWorkGroupSizeHint`, `@GPUVectorTypeHint`, +`@GPUPacked`, `@GPUAligned`, or `@GPUAlwaysInline`. Use `@GPUAttribute` only when no portable annotation exists yet. It supports repeated usage on the same method, field, type, or parameter: @@ -43,7 +46,7 @@ For vendor or device-specific metadata, narrow the selector explicitly: ) ``` -`@GPUAttribute` is not portable by itself. A backend lowerer may consume it only when the selected backend, vendor, and device class match the annotation selectors; otherwise it should reject or ignore it fail-closed with a diagnostic. Existing `@OpenCLAttributes` and `@OpenCLQualifiers` remain compatibility surfaces for OpenCL-only code, but new backend-specific metadata should prefer `@GPUAttribute`. Generic OpenCL emission should not apply vendor/device-specific raw attributes until device-aware lowering is available. Portable `attributeMetadata` is the source-of-truth for modeled concepts; backend-specific string attributes are compatibility projections or explicit escape hatches. +`@GPUAttribute` is not portable by itself. A backend lowerer may consume it only when the selected backend, vendor, and device class match the annotation selectors; otherwise it should reject or ignore it fail-closed with a diagnostic. Existing `@OpenCLAttributes` and `@OpenCLQualifiers` remain compatibility surfaces for OpenCL-only expert code, but new backend-specific metadata should prefer `@GPUAttribute`. Generic OpenCL emission should not apply vendor/device-specific raw attributes until device-aware lowering is available. Portable `attributeMetadata` is the source-of-truth for modeled concepts; backend-specific string attributes are compatibility projections or explicit escape hatches. ## Runtime selection From c171304e875d2134d0608e77c1a3b6a3e9320778 Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Sat, 11 Jul 2026 17:30:15 +0300 Subject: [PATCH 08/32] Add runtime extension participation artifact --- ROADMAP.md | 2 +- .../GpuRuntimeCompileArtifactDumper.java | 6 + ...RuntimeExtensionParticipationArtifact.java | 121 ++++++++++++++++++ .../GpuRuntimeCompileArtifactDumperTest.java | 99 ++++++++++++++ 4 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeExtensionParticipationArtifact.java diff --git a/ROADMAP.md b/ROADMAP.md index a982150c..17da0c1e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -613,7 +613,7 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i - [x] Persist OpenCL device selection and policy participation in runtime compile artifacts. `GpuRuntimeCompileArtifactSnapshot` now carries an optional backend-neutral `GpuRuntimeDeviceSelection`. OpenCL attaches the live selection without forcing context creation in mock/custom backends, and `GpuRuntimeCompileArtifactDumper` emits `runtime-device-selection.properties`. The artifact records selected device identity/class/vendor/driver, deterministic candidate scores and rejection state, policy ids/versions, score adjustments, capability facts, quirks, compile-option diagnostics, extension execution outcomes/failure policies, blockers, and final diagnostics. - [ ] Persist remaining extension participation in `IrGpu` and cross-pipeline artifacts. - `kernel.irgpu.properties`, optimizer drift artifacts, promotion gates, validation history, and future backend artifacts should record the relevant extension ids, versions, phases, decisions, diagnostics, and whether each extension was advisory or production-affecting. Device-selection runtime participation is now persisted; validator, optimizer, backend-lowerer, runtime-equivalence, promotion-gate, and artifact-writer participation still need one normalized durable metadata model. This keeps generated artifacts auditable when a project depends on company-specific plugins or third-party GPU libraries. + `kernel.irgpu.properties`, optimizer drift artifacts, promotion gates, validation history, and future backend artifacts should record the relevant extension ids, versions, phases, decisions, diagnostics, and whether each extension was advisory or production-affecting. Runtime compile dumps now include a first normalized `runtime-extension-participation.properties` aggregate for device-selection, runtime optimizer, and backend compiler-feedback execution reports. Validator, backend-lowerer, runtime-equivalence, promotion-gate, artifact-writer, and durable `IrGpu` participation still need the same normalized model. This keeps generated artifacts auditable when a project depends on company-specific plugins or third-party GPU libraries. #### I3.8 Storage and cache model diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java index 6b9199f9..6b97a8d6 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java @@ -19,6 +19,8 @@ public final class GpuRuntimeCompileArtifactDumper { public static final String RUNTIME_DEVICE_SELECTION_ARTIFACT = "runtime-device-selection.properties"; public static final String BACKEND_COMPILER_FEEDBACK_ARTIFACT = "backend-compiler-feedback.properties"; + public static final String RUNTIME_EXTENSION_PARTICIPATION_ARTIFACT = + "runtime-extension-participation.properties"; public static final String RUNTIME_OPTIMIZER_FAMILY_EQUIVALENCE_PAYLOAD_DIRECTORY = "runtime-optimizer-family-equivalence-payload"; @@ -77,6 +79,10 @@ public static GpuRuntimeCompileArtifactDump dump( artifacts.put("backend-module.properties", formatBackendModule(snapshot.backendModuleArtifact())); artifacts.put("backend-diagnostics.properties", formatBackendDiagnostics(snapshot)); artifacts.put(BACKEND_COMPILER_FEEDBACK_ARTIFACT, compilerFeedbackReport.toPropertiesText()); + artifacts.put( + RUNTIME_EXTENSION_PARTICIPATION_ARTIFACT, + GpuRuntimeExtensionParticipationArtifact.from(snapshot, compilerFeedbackReport).toPropertiesText() + ); artifacts.put("opencl-irgpu-reconstruction-preview.properties", formatOpenClIrGpuReconstructionPreview(snapshot)); artifacts.put("backend-source-reconstruction.properties", formatBackendSourceReconstruction(snapshot)); artifacts.put(GpuPromotionArtifactRegistry.BACKEND_SOURCE_PROMOTION_GATE, formatBackendSourcePromotionGate(snapshot)); diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeExtensionParticipationArtifact.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeExtensionParticipationArtifact.java new file mode 100644 index 00000000..3d2bb89f --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeExtensionParticipationArtifact.java @@ -0,0 +1,121 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionExecutionOutcome; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionExecutionReport; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Durable aggregate of extension participation across one runtime compile snapshot. + */ +public record GpuRuntimeExtensionParticipationArtifact( + List entries +) { + + public GpuRuntimeExtensionParticipationArtifact { + entries = entries == null ? List.of() : List.copyOf(entries); + } + + public static GpuRuntimeExtensionParticipationArtifact from( + GpuRuntimeCompileArtifactSnapshot snapshot, + GpuBackendCompilerFeedbackReport compilerFeedbackReport + ) { + if (snapshot == null) { + return new GpuRuntimeExtensionParticipationArtifact(List.of()); + } + ArrayList entries = new ArrayList<>(); + snapshot.deviceSelection().ifPresent(selection -> append( + entries, + "device-selection", + selection.executionReports() + )); + append( + entries, + "runtime-ir-optimization", + snapshot.optimizationReport().extensionExecutionReports() + ); + append( + entries, + "backend-compiler-feedback", + compilerFeedbackReport == null ? List.of() : compilerFeedbackReport.executions() + ); + return new GpuRuntimeExtensionParticipationArtifact(entries); + } + + public Map artifactFields() { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("status", entries.isEmpty() ? "not-recorded" : "recorded"); + fields.put("entry.count", Integer.toString(entries.size())); + fields.put("succeeded.count", Long.toString(count(GpuExtensionExecutionOutcome.SUCCEEDED))); + fields.put("skipped.count", Long.toString(count(GpuExtensionExecutionOutcome.SKIPPED))); + fields.put("failedContinued.count", Long.toString(count(GpuExtensionExecutionOutcome.FAILED_CONTINUED))); + fields.put("failedClosed.count", Long.toString(count(GpuExtensionExecutionOutcome.FAILED_CLOSED))); + fields.put("pipelineContinued.all", Boolean.toString(entries.stream().allMatch(entry -> entry.report().pipelineContinued()))); + fields.put("firstFailure", firstFailure()); + for (int index = 0; index < entries.size(); index++) { + Entry entry = entries.get(index); + String prefix = "entry." + index; + fields.put(prefix + ".source", entry.source()); + fields.putAll(entry.report().artifactFields(prefix)); + } + return Collections.unmodifiableMap(fields); + } + + public String toPropertiesText() { + StringBuilder builder = new StringBuilder(); + artifactFields().forEach((key, value) -> builder + .append(key) + .append('=') + .append(safeValue(value)) + .append('\n')); + return builder.toString(); + } + + private static void append( + ArrayList entries, + String source, + List reports + ) { + if (reports == null || reports.isEmpty()) { + return; + } + for (GpuExtensionExecutionReport report : reports) { + if (report != null) { + entries.add(new Entry(source, report)); + } + } + } + + private long count(GpuExtensionExecutionOutcome outcome) { + return entries.stream().filter(entry -> entry.report().outcome() == outcome).count(); + } + + private String firstFailure() { + return entries.stream() + .map(Entry::report) + .filter(report -> report.outcome() == GpuExtensionExecutionOutcome.FAILED_CONTINUED + || report.outcome() == GpuExtensionExecutionOutcome.FAILED_CLOSED) + .map(report -> report.extensionId() + ":" + report.outcome().name()) + .findFirst() + .orElse("none"); + } + + private static String safeValue(String value) { + return value == null ? "" : value.replace("\r", " ").replace("\n", " "); + } + + public record Entry( + String source, + GpuExtensionExecutionReport report + ) { + + public Entry { + source = source == null || source.isBlank() ? "unknown" : source; + report = java.util.Objects.requireNonNull(report, "report"); + } + } +} diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java index b361d5fe..a55a9e78 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java @@ -2,6 +2,11 @@ import net.sixik.ga_utils.javatogpu.api.GpuBackendTarget; import net.sixik.ga_utils.javatogpu.api.GpuDeviceClassTarget; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionExecutionOutcome; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionExecutionReport; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionFailurePolicy; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionPermission; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionPhase; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; @@ -26,6 +31,65 @@ class GpuRuntimeCompileArtifactDumperTest { private static final String SIMPLE_IRGPU_SOURCE_RESOURCE = "javatogpu/runtime/opencl/integration/simple-irgpu-source-kernel.irgpu.properties"; + @Test + void dumpsRuntimeExtensionParticipationArtifact() { + GpuExtensionExecutionReport optimizerExecution = execution( + "optimizer:cse", + GpuExtensionPhase.RUNTIME_IR_OPTIMIZATION, + GpuExtensionPermission.MUTATION_PROPOSAL, + GpuExtensionExecutionOutcome.SUCCEEDED, + true + ); + GpuExtensionExecutionReport deviceSelectionExecution = execution( + "device-policy:test", + GpuExtensionPhase.DEVICE_SELECTION, + GpuExtensionPermission.READ_ONLY, + GpuExtensionExecutionOutcome.SUCCEEDED, + true + ); + GpuRuntimeCompileArtifactSnapshot snapshot = GpuRuntimeCompileArtifactSnapshot.legacy(descriptor()) + .withDeviceSelection(syntheticDeviceSelection(deviceSelectionExecution)) + .withOptimizationReport(new GpuRuntimeIrOptimizationReport( + Optional.empty(), + List.of(), + GpuOptimizationStrategyDecision.none(null), + List.of(optimizerExecution) + )) + .withCompileLog("mock compiler resource log"); + GpuBackendCompilerFeedbackRegistry compilerFeedbackRegistry = GpuBackendCompilerFeedbackRegistry.of(List.of( + new GpuBackendCompilerFeedbackProvider() { + @Override + public String extensionId() { + return "compiler-feedback:mock"; + } + + @Override + public Optional inspect(GpuBackendCompilerFeedbackRequest request) { + throw new IllegalStateException("mock feedback unavailable"); + } + } + )); + + GpuRuntimeCompileArtifactDump dump = GpuRuntimeCompileArtifactDumper.dump(snapshot, compilerFeedbackRegistry); + + assertTrue(dump.hasArtifact(GpuRuntimeCompileArtifactDumper.RUNTIME_EXTENSION_PARTICIPATION_ARTIFACT)); + String participation = dump.artifact(GpuRuntimeCompileArtifactDumper.RUNTIME_EXTENSION_PARTICIPATION_ARTIFACT); + assertTrue(participation.contains("status=recorded")); + assertTrue(participation.contains("entry.count=3")); + assertTrue(participation.contains("succeeded.count=2")); + assertTrue(participation.contains("failedContinued.count=1")); + assertTrue(participation.contains("failedClosed.count=0")); + assertTrue(participation.contains("pipelineContinued.all=true")); + assertTrue(participation.contains("firstFailure=compiler-feedback:mock:FAILED_CONTINUED")); + assertTrue(participation.contains("entry.0.source=device-selection")); + assertTrue(participation.contains("entry.0.extensionId=device-policy:test")); + assertTrue(participation.contains("entry.1.source=runtime-ir-optimization")); + assertTrue(participation.contains("entry.1.extensionId=optimizer:cse")); + assertTrue(participation.contains("entry.2.source=backend-compiler-feedback")); + assertTrue(participation.contains("entry.2.extensionId=compiler-feedback:mock")); + assertTrue(participation.contains("entry.2.outcome=FAILED_CONTINUED")); + } + @Test void dumpsStructuredRuntimeEquivalenceComparisonCases() { GpuRuntimeEquivalenceCaseEvidence caseEvidence = new GpuRuntimeEquivalenceCaseEvidence( @@ -1776,6 +1840,41 @@ private static GpuKernelDescriptor descriptor() { ); } + private static GpuExtensionExecutionReport execution( + String extensionId, + GpuExtensionPhase phase, + GpuExtensionPermission permission, + GpuExtensionExecutionOutcome outcome, + boolean pipelineContinued + ) { + return new GpuExtensionExecutionReport( + extensionId, + "test-version", + phase, + permission, + "test operation", + outcome, + GpuExtensionFailurePolicy.CONTINUE, + pipelineContinued, + "none", + "test execution", + List.of() + ); + } + + private static GpuRuntimeDeviceSelection syntheticDeviceSelection(GpuExtensionExecutionReport execution) { + return new GpuRuntimeDeviceSelection( + Optional.empty(), + List.of(), + List.of(), + List.of(execution), + true, + false, + "none", + List.of("synthetic device-selection execution for participation artifact test") + ); + } + private static GpuRuntimeDeviceSelection deviceSelection() { GpuRuntimeDeviceProfile profile = GpuRuntimeDeviceProfile.openCl( "OpenCL", From 899981f715b22d917afb69c3728a50871867dc00 Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Sat, 11 Jul 2026 17:43:58 +0300 Subject: [PATCH 09/32] Surface extension participation in OpenCL validation --- ROADMAP.md | 2 +- ...RuntimeExtensionParticipationArtifact.java | 20 +- .../OpenClExtensionParticipationSummary.java | 298 ++++++++++++++++++ .../opencl/OpenClValidationHistoryEntry.java | 47 ++- .../opencl/OpenClValidationHistoryIO.java | 12 +- .../opencl/OpenClValidationReporter.java | 33 +- .../GpuRuntimeCompileArtifactDumperTest.java | 3 + ...enClExtensionParticipationSummaryTest.java | 73 +++++ 8 files changed, 476 insertions(+), 12 deletions(-) create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClExtensionParticipationSummary.java create mode 100644 processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClExtensionParticipationSummaryTest.java diff --git a/ROADMAP.md b/ROADMAP.md index 17da0c1e..ea724843 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -613,7 +613,7 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i - [x] Persist OpenCL device selection and policy participation in runtime compile artifacts. `GpuRuntimeCompileArtifactSnapshot` now carries an optional backend-neutral `GpuRuntimeDeviceSelection`. OpenCL attaches the live selection without forcing context creation in mock/custom backends, and `GpuRuntimeCompileArtifactDumper` emits `runtime-device-selection.properties`. The artifact records selected device identity/class/vendor/driver, deterministic candidate scores and rejection state, policy ids/versions, score adjustments, capability facts, quirks, compile-option diagnostics, extension execution outcomes/failure policies, blockers, and final diagnostics. - [ ] Persist remaining extension participation in `IrGpu` and cross-pipeline artifacts. - `kernel.irgpu.properties`, optimizer drift artifacts, promotion gates, validation history, and future backend artifacts should record the relevant extension ids, versions, phases, decisions, diagnostics, and whether each extension was advisory or production-affecting. Runtime compile dumps now include a first normalized `runtime-extension-participation.properties` aggregate for device-selection, runtime optimizer, and backend compiler-feedback execution reports. Validator, backend-lowerer, runtime-equivalence, promotion-gate, artifact-writer, and durable `IrGpu` participation still need the same normalized model. This keeps generated artifacts auditable when a project depends on company-specific plugins or third-party GPU libraries. + `kernel.irgpu.properties`, optimizer drift artifacts, promotion gates, validation history, and future backend artifacts should record the relevant extension ids, versions, phases, decisions, diagnostics, and whether each extension was advisory or production-affecting. Runtime compile dumps now include a first normalized `runtime-extension-participation.properties` aggregate for device-selection, runtime optimizer, and backend compiler-feedback execution reports, and OpenCL validation reports/history now aggregate those per-kernel artifacts into `Runtime Extension Participation` evidence. Validator, backend-lowerer, runtime-equivalence, promotion-gate, artifact-writer, and durable `IrGpu` participation still need the same normalized model. This keeps generated artifacts auditable when a project depends on company-specific plugins or third-party GPU libraries. #### I3.8 Storage and cache model diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeExtensionParticipationArtifact.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeExtensionParticipationArtifact.java index 3d2bb89f..1f341851 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeExtensionParticipationArtifact.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeExtensionParticipationArtifact.java @@ -13,13 +13,23 @@ * Durable aggregate of extension participation across one runtime compile snapshot. */ public record GpuRuntimeExtensionParticipationArtifact( + String backendTarget, + String backendFormat, + String backendResource, List entries ) { public GpuRuntimeExtensionParticipationArtifact { + backendTarget = backendTarget == null || backendTarget.isBlank() ? "UNKNOWN" : backendTarget; + backendFormat = backendFormat == null || backendFormat.isBlank() ? "unknown" : backendFormat; + backendResource = backendResource == null || backendResource.isBlank() ? "unknown" : backendResource; entries = entries == null ? List.of() : List.copyOf(entries); } + public GpuRuntimeExtensionParticipationArtifact(List entries) { + this("UNKNOWN", "unknown", "unknown", entries); + } + public static GpuRuntimeExtensionParticipationArtifact from( GpuRuntimeCompileArtifactSnapshot snapshot, GpuBackendCompilerFeedbackReport compilerFeedbackReport @@ -43,12 +53,20 @@ public static GpuRuntimeExtensionParticipationArtifact from( "backend-compiler-feedback", compilerFeedbackReport == null ? List.of() : compilerFeedbackReport.executions() ); - return new GpuRuntimeExtensionParticipationArtifact(entries); + return new GpuRuntimeExtensionParticipationArtifact( + snapshot.backendModuleArtifact().backendTarget().name(), + snapshot.backendModuleArtifact().format(), + snapshot.backendModuleArtifact().resource(), + entries + ); } public Map artifactFields() { LinkedHashMap fields = new LinkedHashMap<>(); fields.put("status", entries.isEmpty() ? "not-recorded" : "recorded"); + fields.put("backendTarget", backendTarget); + fields.put("backendFormat", backendFormat); + fields.put("backendResource", backendResource); fields.put("entry.count", Integer.toString(entries.size())); fields.put("succeeded.count", Long.toString(count(GpuExtensionExecutionOutcome.SUCCEEDED))); fields.put("skipped.count", Long.toString(count(GpuExtensionExecutionOutcome.SKIPPED))); diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClExtensionParticipationSummary.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClExtensionParticipationSummary.java new file mode 100644 index 00000000..31353c64 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClExtensionParticipationSummary.java @@ -0,0 +1,298 @@ +package net.sixik.ga_utils.javatogpu.runtime.opencl; + +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileArtifactDumper; + +import java.io.IOException; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Base64; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; + +/** + * Aggregates runtime extension participation artifacts into validation and history summaries. + */ +record OpenClExtensionParticipationSummary(String status, List entries, String diagnostic) { + + private static final String ARTIFACT_FILE_NAME = + GpuRuntimeCompileArtifactDumper.RUNTIME_EXTENSION_PARTICIPATION_ARTIFACT; + private static final String HISTORY_SNAPSHOT_MARKER = "; snapshot=v1:"; + + OpenClExtensionParticipationSummary { + status = status == null || status.isBlank() ? "unknown" : status; + entries = entries == null ? List.of() : List.copyOf(entries); + diagnostic = diagnostic == null ? "" : diagnostic; + } + + static OpenClExtensionParticipationSummary notRecorded() { + return new OpenClExtensionParticipationSummary("not-recorded", List.of(), ""); + } + + static OpenClExtensionParticipationSummary failed(Throwable failure) { + return new OpenClExtensionParticipationSummary( + "failed", + List.of(), + failure == null ? "unknown failure" : failure.toString() + ); + } + + static OpenClExtensionParticipationSummary read(Path workloadGateFile) throws IOException { + if (workloadGateFile == null || !Files.isRegularFile(workloadGateFile)) { + return notRecorded(); + } + Properties gate = loadProperties(workloadGateFile); + int kernelCount = parseInt(gate.getProperty("kernel.count"), 0); + if (kernelCount <= 0) { + return notRecorded(); + } + + Path reportDirectory = workloadGateFile.getParent(); + Path artifactRoot = reportDirectory == null + ? null + : reportDirectory.resolve("runtime-compile-artifacts"); + Map participationByResource = loadParticipationArtifacts(artifactRoot); + ArrayList entries = new ArrayList<>(); + for (int index = 0; index < kernelCount; index++) { + String resource = gate.getProperty("kernel." + index + ".sourceKernelResource", "unknown"); + entries.add(Entry.from(resource, participationByResource.get(resource))); + } + return new OpenClExtensionParticipationSummary("recorded", entries, ""); + } + + int totalExecutions() { + return entries.stream().mapToInt(Entry::executionCount).sum(); + } + + int totalFailedContinued() { + return entries.stream().mapToInt(Entry::failedContinuedCount).sum(); + } + + int totalFailedClosed() { + return entries.stream().mapToInt(Entry::failedClosedCount).sum(); + } + + int missingCount() { + int count = 0; + for (Entry entry : entries) { + if ("missing".equals(entry.status())) { + count++; + } + } + return count; + } + + String toHistorySummary() { + if (!"recorded".equals(status)) { + return status; + } + String aggregate = "recorded (kernels=" + entries.size() + + ", executions=" + totalExecutions() + + ", failedContinued=" + totalFailedContinued() + + ", failedClosed=" + totalFailedClosed() + + ", missing=" + missingCount() + + ")"; + StringBuilder snapshot = new StringBuilder(aggregate).append(HISTORY_SNAPSHOT_MARKER); + for (int index = 0; index < entries.size(); index++) { + if (index > 0) { + snapshot.append(';'); + } + snapshot.append(entries.get(index).toHistoryToken()); + } + return snapshot.toString(); + } + + static String aggregateHistorySummary(String summary) { + if (summary == null) { + return "unknown"; + } + int markerIndex = summary.indexOf(HISTORY_SNAPSHOT_MARKER); + return markerIndex < 0 ? summary : summary.substring(0, markerIndex); + } + + static Optional> parseHistoryEntries(String summary) { + if (summary == null) { + return Optional.empty(); + } + int markerIndex = summary.indexOf(HISTORY_SNAPSHOT_MARKER); + if (markerIndex < 0) { + return Optional.empty(); + } + String payload = summary.substring(markerIndex + HISTORY_SNAPSHOT_MARKER.length()); + if (payload.isBlank()) { + return Optional.of(List.of()); + } + ArrayList parsed = new ArrayList<>(); + for (String token : payload.split(";", -1)) { + Optional entry = Entry.fromHistoryToken(token); + if (entry.isEmpty()) { + return Optional.empty(); + } + parsed.add(entry.get()); + } + return Optional.of(List.copyOf(parsed)); + } + + String toMarkdown() { + StringBuilder markdown = new StringBuilder(); + markdown.append("## Runtime Extension Participation\n\n"); + markdown.append("- Status: `").append(inline(status)).append("`\n"); + if (!diagnostic.isBlank()) { + markdown.append("- Diagnostic: `").append(inline(diagnostic)).append("`\n\n"); + return markdown.toString(); + } + markdown.append("- Kernel count: `").append(entries.size()).append("`\n"); + markdown.append("- Execution count: `").append(totalExecutions()).append("`\n"); + markdown.append("- Failed continued: `").append(totalFailedContinued()).append("`\n"); + markdown.append("- Failed closed: `").append(totalFailedClosed()).append("`\n"); + markdown.append("- Missing artifacts: `").append(missingCount()).append("`\n\n"); + if (entries.isEmpty()) { + return markdown.toString(); + } + markdown.append("| Kernel resource | Status | Executions | Failed continued | Failed closed | Pipeline continued | First failure |\n"); + markdown.append("| --- | --- | ---: | ---: | ---: | --- | --- |\n"); + for (Entry entry : entries) { + markdown.append("| `").append(table(entry.kernelResource())).append("` | `") + .append(table(entry.status())).append("` | `") + .append(entry.executionCount()).append("` | `") + .append(entry.failedContinuedCount()).append("` | `") + .append(entry.failedClosedCount()).append("` | `") + .append(table(entry.pipelineContinuedAll())).append("` | `") + .append(table(entry.firstFailure())).append("` |\n"); + } + markdown.append('\n'); + return markdown.toString(); + } + + private static Map loadParticipationArtifacts(Path artifactRoot) throws IOException { + LinkedHashMap artifacts = new LinkedHashMap<>(); + if (artifactRoot == null || !Files.isDirectory(artifactRoot)) { + return artifacts; + } + try (java.util.stream.Stream paths = Files.walk(artifactRoot)) { + for (Path path : paths + .filter(Files::isRegularFile) + .filter(candidate -> ARTIFACT_FILE_NAME.equals(candidate.getFileName().toString())) + .toList()) { + Properties properties = loadProperties(path); + String resource = properties.getProperty("backendResource", ""); + if (!resource.isBlank() && !"unknown".equals(resource)) { + artifacts.put(resource, properties); + } + } + } + return artifacts; + } + + private static Properties loadProperties(Path path) throws IOException { + Properties properties = new Properties(); + try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { + properties.load(reader); + } + return properties; + } + + private static int parseInt(String value, int fallback) { + try { + return Integer.parseInt(value); + } catch (NumberFormatException ignored) { + return fallback; + } + } + + private static String inline(String value) { + return value == null ? "" : value.replace('\r', ' ').replace('\n', ' ').replace('`', '\''); + } + + private static String table(String value) { + return inline(value).replace("|", "\\|"); + } + + record Entry( + String kernelResource, + String status, + int executionCount, + int failedContinuedCount, + int failedClosedCount, + String pipelineContinuedAll, + String firstFailure + ) { + + private static final Base64.Encoder HISTORY_ENCODER = Base64.getUrlEncoder().withoutPadding(); + private static final Base64.Decoder HISTORY_DECODER = Base64.getUrlDecoder(); + + Entry { + kernelResource = normalize(kernelResource, "unknown"); + status = normalize(status, "unknown"); + executionCount = Math.max(0, executionCount); + failedContinuedCount = Math.max(0, failedContinuedCount); + failedClosedCount = Math.max(0, failedClosedCount); + pipelineContinuedAll = normalize(pipelineContinuedAll, "unknown"); + firstFailure = normalize(firstFailure, "none"); + } + + static Entry from(String kernelResource, Properties properties) { + if (properties == null || properties.isEmpty()) { + return new Entry(kernelResource, "missing", 0, 0, 0, "unknown", "none"); + } + return new Entry( + kernelResource, + properties.getProperty("status", "unknown"), + parseInt(properties.getProperty("entry.count"), 0), + parseInt(properties.getProperty("failedContinued.count"), 0), + parseInt(properties.getProperty("failedClosed.count"), 0), + properties.getProperty("pipelineContinued.all", "unknown"), + properties.getProperty("firstFailure", "none") + ); + } + + String toHistoryToken() { + return String.join(".", + encode(kernelResource), + encode(status), + encode(Integer.toString(executionCount)), + encode(Integer.toString(failedContinuedCount)), + encode(Integer.toString(failedClosedCount)), + encode(pipelineContinuedAll), + encode(firstFailure) + ); + } + + static Optional fromHistoryToken(String token) { + String[] fields = token.split("\\.", -1); + if (fields.length != 7) { + return Optional.empty(); + } + try { + return Optional.of(new Entry( + decode(fields[0]), + decode(fields[1]), + Integer.parseInt(decode(fields[2])), + Integer.parseInt(decode(fields[3])), + Integer.parseInt(decode(fields[4])), + decode(fields[5]), + decode(fields[6]) + )); + } catch (IllegalArgumentException failure) { + return Optional.empty(); + } + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } + + private static String encode(String value) { + return HISTORY_ENCODER.encodeToString(inline(value).getBytes(StandardCharsets.UTF_8)); + } + + private static String decode(String value) { + return new String(HISTORY_DECODER.decode(value), StandardCharsets.UTF_8); + } + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationHistoryEntry.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationHistoryEntry.java index 6c20bbbb..c139aa9b 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationHistoryEntry.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationHistoryEntry.java @@ -20,9 +20,51 @@ record OpenClValidationHistoryEntry( String backendSourcePromotionWorkloadStatus, String productionPromotionExplainabilityStatus, String kernelLaunchAdvisoryStatus, - String compilerResourceStatus + String compilerResourceStatus, + String extensionParticipationStatus ) { + OpenClValidationHistoryEntry( + Instant generatedAtUtc, + String requestedVendorLane, + String backendName, + String deviceLabel, + String vendor, + String driverVersion, + String deviceVersion, + String bucketSummary, + String longRunningStatus, + String workloadStatus, + String irGpuSourceReviewStatus, + String productionSourceSwitchingValidationStatus, + String backendSourcePromotionContractStatus, + String backendSourcePromotionWorkloadStatus, + String productionPromotionExplainabilityStatus, + String kernelLaunchAdvisoryStatus, + String compilerResourceStatus + ) { + this( + generatedAtUtc, + requestedVendorLane, + backendName, + deviceLabel, + vendor, + driverVersion, + deviceVersion, + bucketSummary, + longRunningStatus, + workloadStatus, + irGpuSourceReviewStatus, + productionSourceSwitchingValidationStatus, + backendSourcePromotionContractStatus, + backendSourcePromotionWorkloadStatus, + productionPromotionExplainabilityStatus, + kernelLaunchAdvisoryStatus, + compilerResourceStatus, + "not recorded" + ); + } + OpenClValidationHistoryEntry( Instant generatedAtUtc, String requestedVendorLane, @@ -58,6 +100,7 @@ record OpenClValidationHistoryEntry( backendSourcePromotionWorkloadStatus, productionPromotionExplainabilityStatus, kernelLaunchAdvisoryStatus, + "not recorded", "not recorded" ); } @@ -96,6 +139,7 @@ record OpenClValidationHistoryEntry( backendSourcePromotionWorkloadStatus, productionPromotionExplainabilityStatus, "not recorded", + "not recorded", "not recorded" ); } @@ -118,6 +162,7 @@ record OpenClValidationHistoryEntry( productionPromotionExplainabilityStatus = normalize(productionPromotionExplainabilityStatus); kernelLaunchAdvisoryStatus = normalize(kernelLaunchAdvisoryStatus); compilerResourceStatus = normalize(compilerResourceStatus); + extensionParticipationStatus = normalize(extensionParticipationStatus); } private static String normalize(String value) { diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationHistoryIO.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationHistoryIO.java index 88bb2fbb..ae05f698 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationHistoryIO.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationHistoryIO.java @@ -53,7 +53,8 @@ static List readAll(Path path) throws IOException properties.getProperty(prefix + ".backendSourcePromotionWorkloadStatus", "not-promoted"), properties.getProperty(prefix + ".productionPromotionExplainabilityStatus", "not recorded"), properties.getProperty(prefix + ".kernelLaunchAdvisoryStatus", "not recorded"), - properties.getProperty(prefix + ".compilerResourceStatus", "not recorded") + properties.getProperty(prefix + ".compilerResourceStatus", "not recorded"), + properties.getProperty(prefix + ".extensionParticipationStatus", "not recorded") )); } entries.sort(Comparator.comparing(OpenClValidationHistoryEntry::generatedAtUtc).reversed()); @@ -82,6 +83,7 @@ static void writeAll(Path path, List entries) thro properties.setProperty(prefix + ".productionPromotionExplainabilityStatus", entry.productionPromotionExplainabilityStatus()); properties.setProperty(prefix + ".kernelLaunchAdvisoryStatus", entry.kernelLaunchAdvisoryStatus()); properties.setProperty(prefix + ".compilerResourceStatus", entry.compilerResourceStatus()); + properties.setProperty(prefix + ".extensionParticipationStatus", entry.extensionParticipationStatus()); } Path parent = path.getParent(); if (parent != null) { @@ -95,8 +97,8 @@ static void writeAll(Path path, List entries) thro static void writeMarkdown(Path path, List entries) throws IOException { StringBuilder markdown = new StringBuilder(); markdown.append("# OpenCL Validation History\n\n"); - markdown.append("| Generated (UTC) | Lane | Backend | Device | Vendor | Driver | Device Version | Buckets | Long-Running | Workloads | IrGpu Source Review | Controlled Production Source Switching | Backend Source Contract Fixture | Backend Source Workload Gate | Production Promotion Explainability | Kernel Launch Advisories | Compiler Resources |\n"); - markdown.append("| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n"); + markdown.append("| Generated (UTC) | Lane | Backend | Device | Vendor | Driver | Device Version | Buckets | Long-Running | Workloads | IrGpu Source Review | Controlled Production Source Switching | Backend Source Contract Fixture | Backend Source Workload Gate | Production Promotion Explainability | Kernel Launch Advisories | Compiler Resources | Extension Participation |\n"); + markdown.append("| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n"); for (OpenClValidationHistoryEntry entry : entries) { markdown.append("| ") .append(entry.generatedAtUtc()) @@ -136,6 +138,10 @@ static void writeMarkdown(Path path, List entries) .append(escapeTable(OpenClCompilerResourceSummary.aggregateHistorySummary( entry.compilerResourceStatus() ))) + .append(" | ") + .append(escapeTable(OpenClExtensionParticipationSummary.aggregateHistorySummary( + entry.extensionParticipationStatus() + ))) .append(" |\n"); } Path parent = path.getParent(); diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReporter.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReporter.java index 8318c302..202605a7 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReporter.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReporter.java @@ -61,9 +61,11 @@ public static void main(String[] args) throws IOException { ensureProductionPromotionExplainabilityArtifact(); OpenClKernelLaunchAdvisorySummary launchAdvisorySummary = loadKernelLaunchAdvisorySummary(); OpenClCompilerResourceSummary compilerResourceSummary = loadCompilerResourceSummary(); + OpenClExtensionParticipationSummary extensionParticipationSummary = loadExtensionParticipationSummary(); OpenClValidationHistoryEntry currentHistoryEntry = buildHistoryEntry( launchAdvisorySummary, - compilerResourceSummary + compilerResourceSummary, + extensionParticipationSummary ); OpenClKernelLaunchAdvisoryDrift launchAdvisoryDrift = loadKernelLaunchAdvisoryDrift(currentHistoryEntry); OpenClCompilerResourceDrift compilerResourceDrift = loadCompilerResourceDrift(currentHistoryEntry); @@ -71,7 +73,8 @@ public static void main(String[] args) throws IOException { launchAdvisorySummary, launchAdvisoryDrift, compilerResourceSummary, - compilerResourceDrift + compilerResourceDrift, + extensionParticipationSummary ); String outputPath = System.getProperty(REPORT_FILE_PROPERTY); if (outputPath != null && !outputPath.isBlank()) { @@ -94,7 +97,8 @@ private static String buildReport( OpenClKernelLaunchAdvisorySummary launchAdvisorySummary, OpenClKernelLaunchAdvisoryDrift launchAdvisoryDrift, OpenClCompilerResourceSummary compilerResourceSummary, - OpenClCompilerResourceDrift compilerResourceDrift + OpenClCompilerResourceDrift compilerResourceDrift, + OpenClExtensionParticipationSummary extensionParticipationSummary ) { StringBuilder markdown = new StringBuilder(); String requestedVendor = env("JTG_VALIDATION_VENDOR"); @@ -132,6 +136,7 @@ private static String buildReport( markdown.append(launchAdvisoryDrift.toMarkdown()); markdown.append(compilerResourceSummary.toMarkdown()); markdown.append(compilerResourceDrift.toMarkdown()); + markdown.append(extensionParticipationSummary.toMarkdown()); appendIrGpuSourceReviewSummary(markdown); appendProductionSourceSwitchingValidationSummary(markdown); appendLongRunningSummary(markdown); @@ -175,6 +180,18 @@ private static OpenClCompilerResourceSummary loadCompilerResourceSummary() { } } + private static OpenClExtensionParticipationSummary loadExtensionParticipationSummary() { + String gatePath = System.getProperty(BACKEND_SOURCE_PROMOTION_WORKLOAD_GATE_FILE_PROPERTY); + if (gatePath == null || gatePath.isBlank()) { + return OpenClExtensionParticipationSummary.notRecorded(); + } + try { + return OpenClExtensionParticipationSummary.read(Paths.get(gatePath)); + } catch (Throwable failure) { + return OpenClExtensionParticipationSummary.failed(failure); + } + } + private static OpenClKernelLaunchAdvisoryDrift loadKernelLaunchAdvisoryDrift( OpenClValidationHistoryEntry currentEntry ) { @@ -1475,7 +1492,8 @@ private static void updateHistoryArtifacts( private static OpenClValidationHistoryEntry buildHistoryEntry( OpenClKernelLaunchAdvisorySummary launchAdvisorySummary, - OpenClCompilerResourceSummary compilerResourceSummary + OpenClCompilerResourceSummary compilerResourceSummary, + OpenClExtensionParticipationSummary extensionParticipationSummary ) { String requestedVendor = env("JTG_VALIDATION_VENDOR"); String bucketSummary = summarizeBuckets(); @@ -1488,6 +1506,7 @@ private static OpenClValidationHistoryEntry buildHistoryEntry( String productionPromotionExplainabilityStatus = summarizeProductionPromotionExplainabilityStatus(); String kernelLaunchAdvisoryStatus = launchAdvisorySummary.toHistorySummary(); String compilerResourceStatus = compilerResourceSummary.toHistorySummary(); + String extensionParticipationStatus = extensionParticipationSummary.toHistorySummary(); try (OpenClGpuRuntimeBackend backend = new OpenClGpuRuntimeBackend()) { OpenClValidationReport report = backend.validationReport(); @@ -1508,7 +1527,8 @@ private static OpenClValidationHistoryEntry buildHistoryEntry( backendSourcePromotionWorkloadStatus, productionPromotionExplainabilityStatus, kernelLaunchAdvisoryStatus, - compilerResourceStatus + compilerResourceStatus, + extensionParticipationStatus ); } catch (Throwable failure) { return new OpenClValidationHistoryEntry( @@ -1528,7 +1548,8 @@ private static OpenClValidationHistoryEntry buildHistoryEntry( backendSourcePromotionWorkloadStatus, productionPromotionExplainabilityStatus, kernelLaunchAdvisoryStatus, - compilerResourceStatus + compilerResourceStatus, + extensionParticipationStatus ); } } diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java index a55a9e78..825835a1 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java @@ -75,6 +75,9 @@ public Optional inspect(GpuBackendCompilerFeedbackRe assertTrue(dump.hasArtifact(GpuRuntimeCompileArtifactDumper.RUNTIME_EXTENSION_PARTICIPATION_ARTIFACT)); String participation = dump.artifact(GpuRuntimeCompileArtifactDumper.RUNTIME_EXTENSION_PARTICIPATION_ARTIFACT); assertTrue(participation.contains("status=recorded")); + assertTrue(participation.contains("backendTarget=OPENCL")); + assertTrue(participation.contains("backendFormat=opencl-c")); + assertTrue(participation.contains("backendResource=javatogpu/sample/Demo/kernel.cl")); assertTrue(participation.contains("entry.count=3")); assertTrue(participation.contains("succeeded.count=2")); assertTrue(participation.contains("failedContinued.count=1")); diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClExtensionParticipationSummaryTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClExtensionParticipationSummaryTest.java new file mode 100644 index 00000000..9afe11d9 --- /dev/null +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClExtensionParticipationSummaryTest.java @@ -0,0 +1,73 @@ +package net.sixik.ga_utils.javatogpu.runtime.opencl; + +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileArtifactDumper; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class OpenClExtensionParticipationSummaryTest { + + @TempDir + Path temporaryDirectory; + + @Test + void readsRuntimeExtensionParticipationArtifactsAndRoundTripsHistorySnapshot() throws Exception { + Path reportDirectory = temporaryDirectory.resolve("reports").resolve("opencl"); + Files.createDirectories(reportDirectory); + Path workloadGate = reportDirectory.resolve("backend-source-promotion-workload-gate.properties"); + Files.writeString(workloadGate, """ + kernel.count=2 + kernel.0.sourceKernelResource=kernel-a.cl + kernel.1.sourceKernelResource=kernel-b.cl + """); + Path artifact = reportDirectory + .resolve("runtime-compile-artifacts") + .resolve("kernel-a") + .resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_EXTENSION_PARTICIPATION_ARTIFACT); + Files.createDirectories(artifact.getParent()); + Files.writeString(artifact, """ + status=recorded + backendTarget=OPENCL + backendFormat=opencl-c + backendResource=kernel-a.cl + entry.count=3 + succeeded.count=2 + skipped.count=0 + failedContinued.count=1 + failedClosed.count=0 + pipelineContinued.all=true + firstFailure=compiler-feedback:mock:FAILED_CONTINUED + """); + + OpenClExtensionParticipationSummary summary = OpenClExtensionParticipationSummary.read(workloadGate); + + assertEquals("recorded", summary.status()); + assertEquals(2, summary.entries().size()); + assertEquals("recorded", summary.entries().get(0).status()); + assertEquals(3, summary.entries().get(0).executionCount()); + assertEquals(1, summary.entries().get(0).failedContinuedCount()); + assertEquals("compiler-feedback:mock:FAILED_CONTINUED", summary.entries().get(0).firstFailure()); + assertEquals("missing", summary.entries().get(1).status()); + assertEquals(3, summary.totalExecutions()); + assertEquals(1, summary.totalFailedContinued()); + assertEquals(0, summary.totalFailedClosed()); + assertEquals(1, summary.missingCount()); + assertTrue(summary.toMarkdown().contains("## Runtime Extension Participation")); + assertTrue(summary.toMarkdown().contains("| `kernel-a.cl` | `recorded` | `3` | `1` | `0` | `true` | `compiler-feedback:mock:FAILED_CONTINUED` |")); + + String history = summary.toHistorySummary(); + List parsed = + OpenClExtensionParticipationSummary.parseHistoryEntries(history).orElseThrow(); + assertEquals(summary.entries(), parsed); + assertEquals( + "recorded (kernels=2, executions=3, failedContinued=1, failedClosed=0, missing=1)", + OpenClExtensionParticipationSummary.aggregateHistorySummary(history) + ); + } +} From 5c62ad70e7d76f0425da1874493461a116627114 Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Sat, 11 Jul 2026 21:49:55 +0300 Subject: [PATCH 10/32] wip --- ROADMAP.md | 8 +- .../frontend/GpuFrontendService.java | 18 ++++- .../frontend/ir/artifact/IrGpuArtifact.java | 76 ++++++++++++++++++- .../ir/artifact/IrGpuArtifactParser.java | 30 +++++++- .../ir/artifact/IrGpuArtifactSerializer.java | 27 +++++++ .../IrGpuExtensionParticipationMetadata.java | 67 ++++++++++++++++ .../frontend/GpuFrontendServiceTest.java | 75 ++++++++++++++++++ 7 files changed, 295 insertions(+), 6 deletions(-) create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuExtensionParticipationMetadata.java diff --git a/ROADMAP.md b/ROADMAP.md index ea724843..f208a35c 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -613,7 +613,13 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i - [x] Persist OpenCL device selection and policy participation in runtime compile artifacts. `GpuRuntimeCompileArtifactSnapshot` now carries an optional backend-neutral `GpuRuntimeDeviceSelection`. OpenCL attaches the live selection without forcing context creation in mock/custom backends, and `GpuRuntimeCompileArtifactDumper` emits `runtime-device-selection.properties`. The artifact records selected device identity/class/vendor/driver, deterministic candidate scores and rejection state, policy ids/versions, score adjustments, capability facts, quirks, compile-option diagnostics, extension execution outcomes/failure policies, blockers, and final diagnostics. - [ ] Persist remaining extension participation in `IrGpu` and cross-pipeline artifacts. - `kernel.irgpu.properties`, optimizer drift artifacts, promotion gates, validation history, and future backend artifacts should record the relevant extension ids, versions, phases, decisions, diagnostics, and whether each extension was advisory or production-affecting. Runtime compile dumps now include a first normalized `runtime-extension-participation.properties` aggregate for device-selection, runtime optimizer, and backend compiler-feedback execution reports, and OpenCL validation reports/history now aggregate those per-kernel artifacts into `Runtime Extension Participation` evidence. Validator, backend-lowerer, runtime-equivalence, promotion-gate, artifact-writer, and durable `IrGpu` participation still need the same normalized model. This keeps generated artifacts auditable when a project depends on company-specific plugins or third-party GPU libraries. + `kernel.irgpu.properties`, optimizer drift artifacts, promotion gates, validation history, and future backend artifacts should record the relevant extension ids, versions, phases, decisions, diagnostics, and whether each extension was advisory or production-affecting. Runtime compile dumps now include a first normalized `runtime-extension-participation.properties` aggregate for device-selection, runtime optimizer, and backend compiler-feedback execution reports, and OpenCL validation reports/history now aggregate those per-kernel artifacts into `Runtime Extension Participation` evidence. `IrGpu` artifacts now persist read-only IR Validator execution participation through `extensionParticipation.*` fields. Backend-lowerer, runtime-equivalence, promotion-gate, artifact-writer, and broader non-validator `IrGpu` participation still need the same normalized model. This keeps generated artifacts auditable when a project depends on company-specific plugins or third-party GPU libraries. + + +#### Future IR optimization passes + +- [ ] Evaluate private-array scalarization as an opt-in IR optimization pass. + The proposed `GpuIrPrivateArrayScalarizer` idea is valid for fixed-size private arrays accessed only by constant indices, but it should stay out of the default frontend path until it has pass-pipeline integration, explicit opt-in/gating, control-flow safety coverage, and tests for dynamic, negative, and out-of-bounds indices. The expected rewrite is `arr[N]` into scalar locals such as `arr_0`, `arr_1`, etc., preserving every assignment and return use without changing IR semantics. #### I3.8 Storage and cache model diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendService.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendService.java index 39d7d0fe..a26ffdfc 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendService.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendService.java @@ -7,6 +7,7 @@ import net.sixik.ga_utils.javatogpu.api.GpuBackendTarget; import net.sixik.ga_utils.javatogpu.api.GpuDeviceClassTarget; import net.sixik.ga_utils.javatogpu.api.GpuVendorTarget; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionExecutionReport; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuAttributeMetadata; @@ -15,6 +16,7 @@ import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuConstantDataMetadata; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuConstantMetadata; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuEntryParameter; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuExtensionParticipationMetadata; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuFeatureMetadata; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuLaunchMetadata; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; @@ -235,10 +237,12 @@ public GpuFrontendCompilationResult compile( validator.validateKernel(kernelMethod, helperMethods, relevantStructs); List compiledMethods = lowerer.lower(kernelMethod, helperMethods, relevantStructs); + List compiledHelpers = compiledMethods.subList(0, helperMethods.size()); GpuIrCompiledMethod compiledKernel = compiledMethods.get(compiledMethods.size() - 1); passRunner.run(compiledKernel, compiledHelpers, relevantStructs); - validationRunner.run(compiledKernel, compiledHelpers, relevantStructs); + List validationExecutions = + validationRunner.runWithReport(compiledKernel, compiledHelpers, relevantStructs); List reachableHelpers = GpuProgramAssemblySupport.selectReachableHelpers( compiledKernel, compiledHelpers, @@ -253,9 +257,21 @@ public GpuFrontendCompilationResult compile( return new GpuFrontendCompilationResult( openClSource, buildIrGpuArtifact(compiledKernel, reachableHelpers, relevantStructs, derivedOpenClResource, "java-source") + .withExtensionParticipationMetadata(buildIrValidationParticipationMetadata(validationExecutions)) ); } + private static List buildIrValidationParticipationMetadata( + List executions + ) { + if (executions == null || executions.isEmpty()) { + return List.of(); + } + return executions.stream() + .map(report -> IrGpuExtensionParticipationMetadata.fromExecutionReport("ir-validation", report)) + .toList(); + } + static IrGpuArtifact buildIrGpuArtifact( GpuIrCompiledMethod compiledKernel, List helperMethods, diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifact.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifact.java index 294bec51..432ea3e6 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifact.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifact.java @@ -18,9 +18,49 @@ public record IrGpuArtifact( String runtimeDefaultBackend, String runtimeOptimizationProfile, List methodDeviceConstraints, - List methodFallbackVariants + List methodFallbackVariants, + List extensionParticipationMetadata ) { + public IrGpuArtifact( + IrGpuArtifactHeader header, + IrGpuModule module, + List entryParameters, + IrGpuLaunchMetadata launchMetadata, + IrGpuValidationMetadata validationMetadata, + IrGpuFeatureMetadata featureMetadata, + IrGpuOptimizerPolicyMetadata optimizerPolicyMetadata, + IrGpuRegenerationMetadata regenerationMetadata, + List structMetadata, + List constants, + List constantData, + List backendOutputs, + String runtimeDefaultBackend, + String runtimeOptimizationProfile, + List methodDeviceConstraints, + List methodFallbackVariants + ) { + this( + header, + module, + entryParameters, + launchMetadata, + validationMetadata, + featureMetadata, + optimizerPolicyMetadata, + regenerationMetadata, + structMetadata, + constants, + constantData, + backendOutputs, + runtimeDefaultBackend, + runtimeOptimizationProfile, + methodDeviceConstraints, + methodFallbackVariants, + List.of() + ); + } + public IrGpuArtifact( IrGpuArtifactHeader header, IrGpuModule module, @@ -54,6 +94,7 @@ public IrGpuArtifact( runtimeDefaultBackend, runtimeOptimizationProfile, methodDeviceConstraints, + List.of(), List.of() ); } @@ -258,6 +299,9 @@ public IrGpuArtifact( : runtimeOptimizationProfile; methodDeviceConstraints = methodDeviceConstraints == null ? List.of() : List.copyOf(methodDeviceConstraints); methodFallbackVariants = methodFallbackVariants == null ? List.of() : List.copyOf(methodFallbackVariants); + extensionParticipationMetadata = extensionParticipationMetadata == null + ? List.of() + : List.copyOf(extensionParticipationMetadata); } public String derivedOpenClResource() { @@ -300,7 +344,8 @@ public IrGpuArtifact withMethodDeviceConstraints(List runtimeDefaultBackend, runtimeOptimizationProfile, methodDeviceConstraints, - variants + variants, + extensionParticipationMetadata + ); + } + + public IrGpuArtifact withExtensionParticipationMetadata( + List participationMetadata + ) { + return new IrGpuArtifact( + header, + module, + entryParameters, + launchMetadata, + validationMetadata, + featureMetadata, + optimizerPolicyMetadata, + regenerationMetadata, + structMetadata, + constants, + constantData, + backendOutputs, + runtimeDefaultBackend, + runtimeOptimizationProfile, + methodDeviceConstraints, + methodFallbackVariants, + participationMetadata ); } } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifactParser.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifactParser.java index c1b95cd5..613abc8e 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifactParser.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifactParser.java @@ -3,6 +3,10 @@ import net.sixik.ga_utils.javatogpu.api.GpuBackendTarget; import net.sixik.ga_utils.javatogpu.api.GpuDeviceClassTarget; import net.sixik.ga_utils.javatogpu.api.GpuVendorTarget; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionExecutionOutcome; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionFailurePolicy; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionPermission; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionPhase; import java.io.IOException; import java.io.StringReader; @@ -59,10 +63,34 @@ public static IrGpuArtifact parse(String manifest) { properties.getProperty("runtime.defaultBackend", "opencl"), properties.getProperty("runtime.optimizationProfile", "off"), parseMethodDeviceConstraints(properties), - parseMethodFallbackVariants(properties) + parseMethodFallbackVariants(properties), + parseExtensionParticipationMetadata(properties) ); } + private static List parseExtensionParticipationMetadata(Properties properties) { + int count = parseInt(properties, "extensionParticipation.count", 0); + ArrayList metadata = new ArrayList<>(); + for (int index = 0; index < count; index++) { + String prefix = "extensionParticipation." + index + "."; + metadata.add(new IrGpuExtensionParticipationMetadata( + properties.getProperty(prefix + "source", "unknown"), + properties.getProperty(prefix + "extensionId", "extension:unknown"), + properties.getProperty(prefix + "extensionVersion", "unknown"), + GpuExtensionPhase.valueOf(properties.getProperty(prefix + "phase", "IR_VALIDATION")), + GpuExtensionPermission.valueOf(properties.getProperty(prefix + "permission", "READ_ONLY")), + properties.getProperty(prefix + "operation", "extension invocation"), + GpuExtensionExecutionOutcome.valueOf(properties.getProperty(prefix + "outcome", "SKIPPED")), + GpuExtensionFailurePolicy.valueOf(properties.getProperty(prefix + "failurePolicy", "CONTINUE")), + Boolean.parseBoolean(properties.getProperty(prefix + "pipelineContinued", "true")), + properties.getProperty(prefix + "failureType", "none"), + properties.getProperty(prefix + "message", ""), + parseIndexedValues(properties, prefix + "diagnostic") + )); + } + return List.copyOf(metadata); + } + private static List parseMethodFallbackVariants(Properties properties) { int count = parseInt(properties, "methodFallbackVariant.count", 0); ArrayList variants = new ArrayList<>(); diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifactSerializer.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifactSerializer.java index 74c6b7c1..80fc7227 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifactSerializer.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifactSerializer.java @@ -19,6 +19,7 @@ public static String serialize(IrGpuArtifact artifact) { writeOptimizerPolicyMetadata(properties, artifact.optimizerPolicyMetadata()); writeMethodDeviceConstraints(properties, artifact.methodDeviceConstraints()); writeMethodFallbackVariants(properties, artifact.methodFallbackVariants()); + writeExtensionParticipationMetadata(properties, artifact.extensionParticipationMetadata()); writeRegenerationMetadata(properties, artifact.regenerationMetadata()); writeStructMetadata(properties, artifact.structMetadata()); writeConstants(properties, artifact.constants()); @@ -138,6 +139,32 @@ private static void writeMethodFallbackVariants( } } + private static void writeExtensionParticipationMetadata( + TreeMap properties, + java.util.List metadata + ) { + java.util.List values = metadata == null + ? java.util.List.of() + : metadata; + properties.put("extensionParticipation.count", Integer.toString(values.size())); + for (int index = 0; index < values.size(); index++) { + IrGpuExtensionParticipationMetadata value = values.get(index); + String prefix = "extensionParticipation." + index + "."; + properties.put(prefix + "source", value.source()); + properties.put(prefix + "extensionId", value.extensionId()); + properties.put(prefix + "extensionVersion", value.extensionVersion()); + properties.put(prefix + "phase", value.phase().name()); + properties.put(prefix + "permission", value.permission().name()); + properties.put(prefix + "operation", value.operation()); + properties.put(prefix + "outcome", value.outcome().name()); + properties.put(prefix + "failurePolicy", value.failurePolicy().name()); + properties.put(prefix + "pipelineContinued", Boolean.toString(value.pipelineContinued())); + properties.put(prefix + "failureType", value.failureType()); + properties.put(prefix + "message", value.message()); + writeStringList(properties, prefix + "diagnostic", value.diagnostics()); + } + } + private static void writeTypedBody( TreeMap properties, String prefix, diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuExtensionParticipationMetadata.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuExtensionParticipationMetadata.java new file mode 100644 index 00000000..5fcbe50f --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuExtensionParticipationMetadata.java @@ -0,0 +1,67 @@ +package net.sixik.ga_utils.javatogpu.frontend.ir.artifact; + +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionExecutionOutcome; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionExecutionReport; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionFailurePolicy; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionPermission; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionPhase; + +import java.util.List; + +/** + * Durable extension execution metadata persisted inside an IrGpu artifact. + */ +public record IrGpuExtensionParticipationMetadata( + String source, + String extensionId, + String extensionVersion, + GpuExtensionPhase phase, + GpuExtensionPermission permission, + String operation, + GpuExtensionExecutionOutcome outcome, + GpuExtensionFailurePolicy failurePolicy, + boolean pipelineContinued, + String failureType, + String message, + List diagnostics +) { + + public IrGpuExtensionParticipationMetadata { + source = normalize(source, "unknown"); + extensionId = normalize(extensionId, "extension:unknown"); + extensionVersion = normalize(extensionVersion, "unknown"); + phase = phase == null ? GpuExtensionPhase.IR_VALIDATION : phase; + permission = permission == null ? GpuExtensionPermission.READ_ONLY : permission; + operation = normalize(operation, "extension invocation"); + outcome = outcome == null ? GpuExtensionExecutionOutcome.SKIPPED : outcome; + failurePolicy = failurePolicy == null ? GpuExtensionFailurePolicy.CONTINUE : failurePolicy; + failureType = normalize(failureType, "none"); + message = normalize(message, "extension invocation metadata"); + diagnostics = diagnostics == null ? List.of() : List.copyOf(diagnostics); + } + + public static IrGpuExtensionParticipationMetadata fromExecutionReport( + String source, + GpuExtensionExecutionReport report + ) { + GpuExtensionExecutionReport value = java.util.Objects.requireNonNull(report, "report"); + return new IrGpuExtensionParticipationMetadata( + source, + value.extensionId(), + value.extensionVersion(), + value.phase(), + value.permission(), + value.operation(), + value.outcome(), + value.failurePolicy(), + value.pipelineContinued(), + value.failureType(), + value.message(), + value.diagnostics() + ); + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } +} diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendServiceTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendServiceTest.java index 2ebce97a..7d142fba 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendServiceTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendServiceTest.java @@ -5,15 +5,24 @@ import net.sixik.ga_utils.javatogpu.api.GpuVendorTarget; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactParser; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactSerializer; +import net.sixik.ga_utils.javatogpu.frontend.ir.validation.GpuIrValidationMode; +import net.sixik.ga_utils.javatogpu.frontend.ir.validation.GpuIrValidationProvider; +import net.sixik.ga_utils.javatogpu.frontend.ir.validation.GpuIrValidationRunner; import net.sixik.ga_utils.javatogpu.frontend.ir.model.GpuIrMethod; import net.sixik.ga_utils.javatogpu.frontend.model.ParsedGpuConstant; import net.sixik.ga_utils.javatogpu.frontend.model.ParsedGpuConstantData; import net.sixik.ga_utils.javatogpu.frontend.model.GpuConstantDataKind; import net.sixik.ga_utils.javatogpu.frontend.model.ParsedGpuMethod; import net.sixik.ga_utils.javatogpu.frontend.model.ParsedGpuStruct; +import net.sixik.ga_utils.javatogpu.frontend.intrinsics.GpuIntrinsicDatabase; +import net.sixik.ga_utils.javatogpu.frontend.ir.passes.GpuIrPassRunner; +import net.sixik.ga_utils.javatogpu.frontend.lowering.GpuIrLowerer; +import net.sixik.ga_utils.javatogpu.frontend.opencl.OpenClKernelEmitter; +import net.sixik.ga_utils.javatogpu.frontend.parser.GpuMethodParser; import net.sixik.ga_utils.javatogpu.runtime.GpuKernelDescriptor; import net.sixik.ga_utils.javatogpu.runtime.GpuKernelParameterAccess; import net.sixik.ga_utils.javatogpu.frontend.validation.GpuValidationException; +import net.sixik.ga_utils.javatogpu.frontend.validation.GpuSubsetValidator; import org.junit.jupiter.api.Test; import java.util.List; @@ -353,6 +362,72 @@ void kernel(@GPUGlobal float[] output) { assertTrue(manifest.contains("methodFallbackVariant.0.priority=120")); } + @Test + void persistsIrValidationParticipationThroughIrGpuRoundTrip() { + String methodSource = """ + @GPU + void kernel(@GPUGlobal float[] output) { + output[0] = 1.0f; + } + """; + GpuIrValidationProvider provider = new GpuIrValidationProvider() { + @Override + public void validate(net.sixik.ga_utils.javatogpu.frontend.ir.validation.GpuIrValidationRequest request) { + request.reportDiagnostic("validator observed " + request.method().irMethod().name()); + } + + @Override + public String extensionId() { + return "test.ir-validator"; + } + + @Override + public String extensionVersion() { + return "7"; + } + }; + GpuIntrinsicDatabase intrinsicDatabase = GpuIntrinsicDatabase.createDefault(); + GpuFrontendService service = new GpuFrontendService( + new GpuMethodParser(), + new GpuSubsetValidator(intrinsicDatabase), + new GpuIrLowerer(intrinsicDatabase), + new OpenClKernelEmitter(), + GpuIrPassRunner.loadFromServiceLoader(), + new GpuIrValidationRunner(List.of(provider), GpuIrValidationMode.DIAGNOSTIC) + ); + ParsedGpuMethod kernelMethod = new GpuMethodParser() + .parseMethod(methodSource, "Demo", "sample.Demo"); + + GpuFrontendCompilationResult result = service.compile( + kernelMethod, + List.of(), + List.of(), + "javatogpu/sample/Demo/kernel.cl" + ); + + assertEquals(1, result.irGpuArtifact().extensionParticipationMetadata().size()); + var metadata = result.irGpuArtifact().extensionParticipationMetadata().get(0); + assertEquals("ir-validation", metadata.source()); + assertEquals("test.ir-validator", metadata.extensionId()); + assertEquals("7", metadata.extensionVersion()); + assertEquals("IR_VALIDATION", metadata.phase().name()); + assertEquals("READ_ONLY", metadata.permission().name()); + assertEquals("SUCCEEDED", metadata.outcome().name()); + assertEquals("CONTINUE", metadata.failurePolicy().name()); + assertTrue(metadata.pipelineContinued()); + + String manifest = IrGpuArtifactSerializer.serialize(result.irGpuArtifact()); + var reparsed = IrGpuArtifactParser.parse(manifest); + + assertEquals(result.irGpuArtifact().extensionParticipationMetadata(), reparsed.extensionParticipationMetadata()); + assertTrue(manifest.contains("extensionParticipation.count=1")); + assertTrue(manifest.contains("extensionParticipation.0.source=ir-validation")); + assertTrue(manifest.contains("extensionParticipation.0.extensionId=test.ir-validator")); + assertTrue(manifest.contains("extensionParticipation.0.extensionVersion=7")); + assertTrue(manifest.contains("extensionParticipation.0.phase=IR_VALIDATION")); + assertTrue(manifest.contains("extensionParticipation.0.outcome=SUCCEEDED")); + } + @Test void persistsPortableAttributeMetadataThroughIrGpuRoundTrip() { String methodSource = """ From 9d3fe51f003cd155394540921ef58f1d61888c00 Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Sat, 11 Jul 2026 22:22:56 +0300 Subject: [PATCH 11/32] Surface extension participation across runtime promotion evidence --- ROADMAP.md | 2 +- ...dSourcePromotionWorkloadGateFormatter.java | 128 +++++++++++++++++ ...BackendSourcePromotionWorkloadSummary.java | 109 +++++++++++++++ ...kendSourcePromotionWorkloadSummaryCli.java | 20 +++ ...RuntimeExtensionParticipationArtifact.java | 50 +++++++ .../OpenClExtensionParticipationSummary.java | 112 ++++++++++++++- .../opencl/OpenClGpuRuntimeBackend.java | 3 +- ...rcePromotionWorkloadGateFormatterTest.java | 59 ++++++++ ...endSourcePromotionWorkloadSummaryTest.java | 37 +++++ .../GpuRuntimeCompileArtifactDumperTest.java | 130 ++++++++++++++++++ ...enClExtensionParticipationSummaryTest.java | 8 ++ 11 files changed, 651 insertions(+), 7 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index f208a35c..eb2ed23b 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -613,7 +613,7 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i - [x] Persist OpenCL device selection and policy participation in runtime compile artifacts. `GpuRuntimeCompileArtifactSnapshot` now carries an optional backend-neutral `GpuRuntimeDeviceSelection`. OpenCL attaches the live selection without forcing context creation in mock/custom backends, and `GpuRuntimeCompileArtifactDumper` emits `runtime-device-selection.properties`. The artifact records selected device identity/class/vendor/driver, deterministic candidate scores and rejection state, policy ids/versions, score adjustments, capability facts, quirks, compile-option diagnostics, extension execution outcomes/failure policies, blockers, and final diagnostics. - [ ] Persist remaining extension participation in `IrGpu` and cross-pipeline artifacts. - `kernel.irgpu.properties`, optimizer drift artifacts, promotion gates, validation history, and future backend artifacts should record the relevant extension ids, versions, phases, decisions, diagnostics, and whether each extension was advisory or production-affecting. Runtime compile dumps now include a first normalized `runtime-extension-participation.properties` aggregate for device-selection, runtime optimizer, and backend compiler-feedback execution reports, and OpenCL validation reports/history now aggregate those per-kernel artifacts into `Runtime Extension Participation` evidence. `IrGpu` artifacts now persist read-only IR Validator execution participation through `extensionParticipation.*` fields. Backend-lowerer, runtime-equivalence, promotion-gate, artifact-writer, and broader non-validator `IrGpu` participation still need the same normalized model. This keeps generated artifacts auditable when a project depends on company-specific plugins or third-party GPU libraries. + `kernel.irgpu.properties`, optimizer drift artifacts, promotion gates, validation history, and future backend artifacts should record the relevant extension ids, versions, phases, decisions, diagnostics, and whether each extension was advisory or production-affecting. Runtime compile dumps now include a normalized `runtime-extension-participation.properties` aggregate for build-time `IrGpu` validator metadata, device-selection, runtime optimizer, and backend compiler-feedback execution reports. OpenCL workload gates, workload summaries, workload-summary CLI output, validation reports, and validation history now surface per-kernel and aggregate `Runtime Extension Participation` evidence, including source breakdowns such as `original-irgpu:ir-validation`, `runtime-ir-optimization`, and `backend-compiler-feedback`. Backend-lowerer, runtime-equivalence, artifact-writer, and future backend-specific artifacts still need the same normalized participation model. This keeps generated artifacts auditable when a project depends on company-specific plugins or third-party GPU libraries. #### Future IR optimization passes diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatter.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatter.java index 41d4278b..39cbc91c 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatter.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatter.java @@ -123,6 +123,32 @@ public static String merge( String latestI3ReadinessSummaryProperties, String latestRuntimeOptimizerDriftProperties, String latestOptimizerFamilyEquivalencePayloadProperties + ) throws IOException { + return merge( + path, + sourceKernelResource, + latestGateProperties, + latestSourceSwitchingDecisionProperties, + latestRuntimeIrHandoffProperties, + latestRuntimeProductionMutationSafetyProperties, + latestI3ReadinessSummaryProperties, + latestRuntimeOptimizerDriftProperties, + latestOptimizerFamilyEquivalencePayloadProperties, + "" + ); + } + + public static String merge( + Path path, + String sourceKernelResource, + String latestGateProperties, + String latestSourceSwitchingDecisionProperties, + String latestRuntimeIrHandoffProperties, + String latestRuntimeProductionMutationSafetyProperties, + String latestI3ReadinessSummaryProperties, + String latestRuntimeOptimizerDriftProperties, + String latestOptimizerFamilyEquivalencePayloadProperties, + String latestRuntimeExtensionParticipationProperties ) throws IOException { Properties latest = loadProperties(latestGateProperties); Properties latestSourceSwitchingDecision = loadProperties(latestSourceSwitchingDecisionProperties); @@ -131,6 +157,7 @@ public static String merge( Properties latestI3ReadinessSummary = loadProperties(latestI3ReadinessSummaryProperties); Properties latestRuntimeOptimizerDrift = loadProperties(latestRuntimeOptimizerDriftProperties); Properties latestOptimizerFamilyEquivalencePayload = loadProperties(latestOptimizerFamilyEquivalencePayloadProperties); + Properties latestRuntimeExtensionParticipation = loadProperties(latestRuntimeExtensionParticipationProperties); Properties existing = new Properties(); if (Files.exists(path)) { try (InputStream inputStream = Files.newInputStream(path)) { @@ -172,6 +199,7 @@ public static String merge( copyI3ReadinessSummaryProperties(latestI3ReadinessSummary, latestEntry); copyRuntimeOptimizerDriftProperties(latestRuntimeOptimizerDrift, latestEntry); copyOptimizerFamilyEquivalencePayloadProperties(latestOptimizerFamilyEquivalencePayload, latestEntry); + copyRuntimeExtensionParticipationProperties(latestRuntimeExtensionParticipation, latestEntry); copyIndexedProperties(latest, latestEntry, "runtimeEquivalence.diagnostic"); copyIndexedProperties(latest, latestEntry, "reconstruction.blocker"); copyIndexedProperties(latest, latestEntry, "reconstruction.diagnostic"); @@ -556,6 +584,7 @@ private static String format(LinkedHashMap kernels) { builder.append("productionPromotionOperatorAccepted.all=").append(allProductionPromotionOperatorAccepted).append('\n'); builder.append("sourceSwitching.productionDecision.count=").append(productionSourceDecisionCount).append('\n'); builder.append("sourceSwitching.productionDecision.all=").append(allProductionSourceDecisions).append('\n'); + appendAggregateRuntimeExtensionParticipation(builder, kernels); builder.append("sourceSwitching.count=").append(kernels.size()).append('\n'); builder.append("sourceSwitching.sourcePromotionFirstBlocker.count=").append(aggregateSourcePromotionFirstBlockers.size()).append('\n'); int sourcePromotionBlockerIndex = 0; @@ -620,6 +649,7 @@ private static void appendKernelEntry(StringBuilder builder, int index, Properti appendRuntimeProductionMutationSafety(builder, prefix, entry); appendI3ReadinessSummary(builder, prefix, entry); appendRuntimeOptimizerDrift(builder, prefix, entry); + appendRuntimeExtensionParticipation(builder, prefix, entry); appendIndexedProperties(builder, prefix, entry, "runtimeEquivalence.diagnostic"); appendIndexedProperties(builder, prefix, entry, "reconstruction.blocker"); appendIndexedProperties(builder, prefix, entry, "reconstruction.diagnostic"); @@ -759,6 +789,104 @@ private static void appendRuntimeOptimizerDrift(StringBuilder builder, String pr builder.append(prefix).append("optimizerFamilyPayload.family.complete.all=").append(entry.getProperty("optimizerFamilyPayload.family.complete.all", "false")).append('\n'); } + private static void copyRuntimeExtensionParticipationProperties(Properties source, Properties target) { + if (source == null || source.isEmpty()) { + target.setProperty("runtimeExtensionParticipation.status", "not-recorded"); + target.setProperty("runtimeExtensionParticipation.entry.count", "0"); + target.setProperty("runtimeExtensionParticipation.succeeded.count", "0"); + target.setProperty("runtimeExtensionParticipation.skipped.count", "0"); + target.setProperty("runtimeExtensionParticipation.failedContinued.count", "0"); + target.setProperty("runtimeExtensionParticipation.failedClosed.count", "0"); + target.setProperty("runtimeExtensionParticipation.pipelineContinued.all", "unknown"); + target.setProperty("runtimeExtensionParticipation.firstFailure", "none"); + target.setProperty("runtimeExtensionParticipation.source.count", "0"); + return; + } + target.setProperty("runtimeExtensionParticipation.status", source.getProperty("status", "unknown")); + target.setProperty("runtimeExtensionParticipation.entry.count", source.getProperty("entry.count", "0")); + target.setProperty("runtimeExtensionParticipation.succeeded.count", source.getProperty("succeeded.count", "0")); + target.setProperty("runtimeExtensionParticipation.skipped.count", source.getProperty("skipped.count", "0")); + target.setProperty("runtimeExtensionParticipation.failedContinued.count", source.getProperty("failedContinued.count", "0")); + target.setProperty("runtimeExtensionParticipation.failedClosed.count", source.getProperty("failedClosed.count", "0")); + target.setProperty("runtimeExtensionParticipation.pipelineContinued.all", source.getProperty("pipelineContinued.all", "unknown")); + target.setProperty("runtimeExtensionParticipation.firstFailure", source.getProperty("firstFailure", "none")); + LinkedHashMap sourceCounts = runtimeExtensionParticipationSourceCounts(source); + target.setProperty("runtimeExtensionParticipation.source.count", Integer.toString(sourceCounts.size())); + int sourceIndex = 0; + for (Map.Entry sourceCount : sourceCounts.entrySet()) { + target.setProperty("runtimeExtensionParticipation.source." + sourceIndex + ".name", sourceCount.getKey()); + target.setProperty("runtimeExtensionParticipation.source." + sourceIndex + ".count", Integer.toString(sourceCount.getValue())); + sourceIndex++; + } + } + + private static LinkedHashMap runtimeExtensionParticipationSourceCounts(Properties properties) { + LinkedHashMap counts = new LinkedHashMap<>(); + int entryCount = parsePositiveInt(properties.getProperty("entry.count", "0")); + for (int index = 0; index < entryCount; index++) { + String source = properties.getProperty("entry." + index + ".source", ""); + if (!source.isBlank()) { + counts.merge(source, 1, Integer::sum); + } + } + return counts; + } + + private static void appendAggregateRuntimeExtensionParticipation( + StringBuilder builder, + LinkedHashMap kernels + ) { + int recordedKernelCount = 0; + int entryCount = 0; + int failedContinuedCount = 0; + int failedClosedCount = 0; + LinkedHashMap sourceCounts = new LinkedHashMap<>(); + for (Properties entry : kernels.values()) { + if ("recorded".equals(entry.getProperty("runtimeExtensionParticipation.status"))) { + recordedKernelCount++; + } + entryCount += parsePositiveInt(entry.getProperty("runtimeExtensionParticipation.entry.count", "0")); + failedContinuedCount += parsePositiveInt(entry.getProperty("runtimeExtensionParticipation.failedContinued.count", "0")); + failedClosedCount += parsePositiveInt(entry.getProperty("runtimeExtensionParticipation.failedClosed.count", "0")); + int sourceCount = parsePositiveInt(entry.getProperty("runtimeExtensionParticipation.source.count", "0")); + for (int index = 0; index < sourceCount; index++) { + String source = entry.getProperty("runtimeExtensionParticipation.source." + index + ".name", ""); + int count = parsePositiveInt(entry.getProperty("runtimeExtensionParticipation.source." + index + ".count", "0")); + if (!source.isBlank()) { + sourceCounts.merge(source, count, Integer::sum); + } + } + } + builder.append("runtimeExtensionParticipation.recordedKernel.count=").append(recordedKernelCount).append('\n'); + builder.append("runtimeExtensionParticipation.entry.count=").append(entryCount).append('\n'); + builder.append("runtimeExtensionParticipation.failedContinued.count=").append(failedContinuedCount).append('\n'); + builder.append("runtimeExtensionParticipation.failedClosed.count=").append(failedClosedCount).append('\n'); + builder.append("runtimeExtensionParticipation.source.count=").append(sourceCounts.size()).append('\n'); + int sourceIndex = 0; + for (Map.Entry source : sourceCounts.entrySet()) { + builder.append("runtimeExtensionParticipation.source.").append(sourceIndex).append(".name=").append(source.getKey()).append('\n'); + builder.append("runtimeExtensionParticipation.source.").append(sourceIndex).append(".count=").append(source.getValue()).append('\n'); + sourceIndex++; + } + } + + private static void appendRuntimeExtensionParticipation(StringBuilder builder, String prefix, Properties entry) { + builder.append(prefix).append("runtimeExtensionParticipation.status=").append(entry.getProperty("runtimeExtensionParticipation.status", "not-recorded")).append('\n'); + builder.append(prefix).append("runtimeExtensionParticipation.entry.count=").append(entry.getProperty("runtimeExtensionParticipation.entry.count", "0")).append('\n'); + builder.append(prefix).append("runtimeExtensionParticipation.succeeded.count=").append(entry.getProperty("runtimeExtensionParticipation.succeeded.count", "0")).append('\n'); + builder.append(prefix).append("runtimeExtensionParticipation.skipped.count=").append(entry.getProperty("runtimeExtensionParticipation.skipped.count", "0")).append('\n'); + builder.append(prefix).append("runtimeExtensionParticipation.failedContinued.count=").append(entry.getProperty("runtimeExtensionParticipation.failedContinued.count", "0")).append('\n'); + builder.append(prefix).append("runtimeExtensionParticipation.failedClosed.count=").append(entry.getProperty("runtimeExtensionParticipation.failedClosed.count", "0")).append('\n'); + builder.append(prefix).append("runtimeExtensionParticipation.pipelineContinued.all=").append(entry.getProperty("runtimeExtensionParticipation.pipelineContinued.all", "unknown")).append('\n'); + builder.append(prefix).append("runtimeExtensionParticipation.firstFailure=").append(entry.getProperty("runtimeExtensionParticipation.firstFailure", "none")).append('\n'); + int sourceCount = parsePositiveInt(entry.getProperty("runtimeExtensionParticipation.source.count", "0")); + builder.append(prefix).append("runtimeExtensionParticipation.source.count=").append(sourceCount).append('\n'); + for (int sourceIndex = 0; sourceIndex < sourceCount; sourceIndex++) { + builder.append(prefix).append("runtimeExtensionParticipation.source.").append(sourceIndex).append(".name=").append(entry.getProperty("runtimeExtensionParticipation.source." + sourceIndex + ".name", "unknown")).append('\n'); + builder.append(prefix).append("runtimeExtensionParticipation.source.").append(sourceIndex).append(".count=").append(entry.getProperty("runtimeExtensionParticipation.source." + sourceIndex + ".count", "0")).append('\n'); + } + } + private static void appendIndexedProperties( StringBuilder builder, String kernelPrefix, diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummary.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummary.java index f80ef7fa..5f3c7c6e 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummary.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummary.java @@ -29,6 +29,11 @@ public record GpuBackendSourcePromotionWorkloadSummary( String optimizerFamilySummary, int optimizerFamilyPayloadCompleteCount, String optimizerFamilyPayloadCompleteAll, + int runtimeExtensionParticipationRecordedKernelCount, + int runtimeExtensionParticipationEntryCount, + int runtimeExtensionParticipationFailedContinuedCount, + int runtimeExtensionParticipationFailedClosedCount, + String runtimeExtensionParticipationSources, String sourceSwitchingDecisions, String sourcePromotionFirstBlockers, String sourcePromotionFirstBlockerFamilies, @@ -55,6 +60,11 @@ public static GpuBackendSourcePromotionWorkloadSummary notRecorded() { "none", 0, "false", + 0, + 0, + 0, + 0, + "", "", "", "", @@ -103,6 +113,11 @@ public static GpuBackendSourcePromotionWorkloadSummary fromProperties(Properties "optimizerFamilyPayload.family.complete.count", "optimizerFamilyPayload.family.complete.all" ), + runtimeExtensionParticipationRecordedKernelCount(properties, kernelCount), + runtimeExtensionParticipationEntryCount(properties, kernelCount), + runtimeExtensionParticipationFailedContinuedCount(properties, kernelCount), + runtimeExtensionParticipationFailedClosedCount(properties, kernelCount), + summarizeRuntimeExtensionParticipationSources(properties, kernelCount), summarizeSourceSwitchingDecisions(properties, kernelCount), summarizeSourcePromotionFirstBlockers(properties, kernelCount), summarizeSourcePromotionFirstBlockerFamilies(properties, kernelCount), @@ -135,6 +150,7 @@ public String historyStatus() { + ", optimizerPayloadCompleteAll=" + optimizerFamilyPayloadCompleteAll + optimizerFamilySummaryText() + + runtimeExtensionParticipationEvidenceText() + sourceSwitchingEvidenceText() + kernelEvidence + sourceKernelResourceText() @@ -161,6 +177,7 @@ public String historyStatus() { + ", optimizerPayloadCompleteAll=" + optimizerFamilyPayloadCompleteAll + optimizerFamilySummaryText() + + runtimeExtensionParticipationEvidenceText() + sourceSwitchingEvidenceText() + kernelEvidence + sourceKernelResourceText() @@ -181,6 +198,26 @@ public String sourceSwitchingEvidenceText() { return builder.toString(); } + public String runtimeExtensionParticipationEvidenceText() { + if (runtimeExtensionParticipationEntryCount == 0 + && runtimeExtensionParticipationRecordedKernelCount == 0 + && runtimeExtensionParticipationSources.isBlank()) { + return ""; + } + StringBuilder builder = new StringBuilder(", runtimeExtensionParticipation=recordedKernels=") + .append(runtimeExtensionParticipationRecordedKernelCount) + .append("/executions=") + .append(runtimeExtensionParticipationEntryCount) + .append("/failedContinued=") + .append(runtimeExtensionParticipationFailedContinuedCount) + .append("/failedClosed=") + .append(runtimeExtensionParticipationFailedClosedCount); + if (!runtimeExtensionParticipationSources.isBlank()) { + builder.append("/sources=").append(runtimeExtensionParticipationSources); + } + return builder.toString(); + } + private String sourceKernelResourceText() { return sourceKernelResource.isBlank() ? "" : ", sourceKernelResource=" + sourceKernelResource; } @@ -313,6 +350,69 @@ private static String summarizeSourceSwitchingDecisions(Properties properties, i return summarizeCounts(decisionCounts); } + private static int runtimeExtensionParticipationRecordedKernelCount(Properties properties, int kernelCount) { + String aggregate = properties.getProperty("runtimeExtensionParticipation.recordedKernel.count"); + if (aggregate != null) { + return parsePositiveInt(aggregate); + } + int count = 0; + for (int index = 0; index < kernelCount; index++) { + if ("recorded".equals(properties.getProperty("kernel." + index + ".runtimeExtensionParticipation.status"))) { + count++; + } + } + return count; + } + + private static int runtimeExtensionParticipationEntryCount(Properties properties, int kernelCount) { + String aggregate = properties.getProperty("runtimeExtensionParticipation.entry.count"); + return aggregate == null + ? sumKernelProperty(properties, kernelCount, "runtimeExtensionParticipation.entry.count") + : parsePositiveInt(aggregate); + } + + private static int runtimeExtensionParticipationFailedContinuedCount(Properties properties, int kernelCount) { + String aggregate = properties.getProperty("runtimeExtensionParticipation.failedContinued.count"); + return aggregate == null + ? sumKernelProperty(properties, kernelCount, "runtimeExtensionParticipation.failedContinued.count") + : parsePositiveInt(aggregate); + } + + private static int runtimeExtensionParticipationFailedClosedCount(Properties properties, int kernelCount) { + String aggregate = properties.getProperty("runtimeExtensionParticipation.failedClosed.count"); + return aggregate == null + ? sumKernelProperty(properties, kernelCount, "runtimeExtensionParticipation.failedClosed.count") + : parsePositiveInt(aggregate); + } + + private static String summarizeRuntimeExtensionParticipationSources(Properties properties, int kernelCount) { + int aggregateSourceCount = parsePositiveInt(properties.getProperty("runtimeExtensionParticipation.source.count", "0")); + if (aggregateSourceCount > 0) { + return summarizeIndexed(properties, "runtimeExtensionParticipation.source", aggregateSourceCount); + } + Map sourceCounts = new LinkedHashMap<>(); + for (int kernelIndex = 0; kernelIndex < kernelCount; kernelIndex++) { + int sourceCount = parsePositiveInt(properties.getProperty( + "kernel." + kernelIndex + ".runtimeExtensionParticipation.source.count", + "0" + )); + for (int sourceIndex = 0; sourceIndex < sourceCount; sourceIndex++) { + String source = properties.getProperty( + "kernel." + kernelIndex + ".runtimeExtensionParticipation.source." + sourceIndex + ".name", + "" + ); + int count = parsePositiveInt(properties.getProperty( + "kernel." + kernelIndex + ".runtimeExtensionParticipation.source." + sourceIndex + ".count", + "0" + )); + if (!source.isBlank()) { + sourceCounts.merge(source, count, Integer::sum); + } + } + } + return summarizeCounts(sourceCounts); + } + private static String summarizeKernelEvidence(Properties properties, int kernelCount) { if (kernelCount == 0) { return ""; @@ -360,6 +460,15 @@ private static String summarizeKernelEvidence(Properties properties, int kernelC .append(properties.getProperty("kernel." + index + ".optimizerFamilyPayload.family.complete.count", "0")) .append("/payloadCompleteAll=") .append(properties.getProperty("kernel." + index + ".optimizerFamilyPayload.family.complete.all", "false")) + .append(", extensionParticipation=") + .append(properties.getProperty("kernel." + index + ".runtimeExtensionParticipation.status", "not-recorded")) + .append('/') + .append(properties.getProperty("kernel." + index + ".runtimeExtensionParticipation.entry.count", "0")) + .append("executions") + .append("/failedContinued=") + .append(properties.getProperty("kernel." + index + ".runtimeExtensionParticipation.failedContinued.count", "0")) + .append("/failedClosed=") + .append(properties.getProperty("kernel." + index + ".runtimeExtensionParticipation.failedClosed.count", "0")) .append("/fallback=") .append(properties.getProperty("kernel." + index + ".runtimeOptimizerDrift.fallbackDecision", "unknown")) .append(", productionMutation=") diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummaryCli.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummaryCli.java index 07a1fc18..4006792c 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummaryCli.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummaryCli.java @@ -80,6 +80,26 @@ public static String format(GpuBackendSourcePromotionWorkloadSummary summary) { builder.append("optimizerFamilyPayload.complete.all=") .append(summary.optimizerFamilyPayloadCompleteAll()) .append('\n'); + builder.append("runtimeExtensionParticipation.recordedKernel.count=") + .append(summary.runtimeExtensionParticipationRecordedKernelCount()) + .append('\n'); + builder.append("runtimeExtensionParticipation.entry.count=") + .append(summary.runtimeExtensionParticipationEntryCount()) + .append('\n'); + builder.append("runtimeExtensionParticipation.failedContinued.count=") + .append(summary.runtimeExtensionParticipationFailedContinuedCount()) + .append('\n'); + builder.append("runtimeExtensionParticipation.failedClosed.count=") + .append(summary.runtimeExtensionParticipationFailedClosedCount()) + .append('\n'); + builder.append("runtimeExtensionParticipation.sources=") + .append(summary.runtimeExtensionParticipationSources()) + .append('\n'); + appendFirstCount( + builder, + "runtimeExtensionParticipation.source", + summary.runtimeExtensionParticipationSources() + ); builder.append("historyStatus=").append(summary.historyStatus()).append('\n'); return builder.toString(); } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeExtensionParticipationArtifact.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeExtensionParticipationArtifact.java index 1f341851..38ccaf41 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeExtensionParticipationArtifact.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeExtensionParticipationArtifact.java @@ -2,6 +2,8 @@ import net.sixik.ga_utils.javatogpu.extension.GpuExtensionExecutionOutcome; import net.sixik.ga_utils.javatogpu.extension.GpuExtensionExecutionReport; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuExtensionParticipationMetadata; import java.util.ArrayList; import java.util.Collections; @@ -38,6 +40,20 @@ public static GpuRuntimeExtensionParticipationArtifact from( return new GpuRuntimeExtensionParticipationArtifact(List.of()); } ArrayList entries = new ArrayList<>(); + snapshot.originalIrGpuArtifact().ifPresent(artifact -> appendIrGpuMetadata( + entries, + "original-irgpu", + artifact + )); + snapshot.optimizedIrGpuArtifact() + .filter(optimized -> snapshot.originalIrGpuArtifact() + .map(original -> !original.equals(optimized)) + .orElse(true)) + .ifPresent(artifact -> appendIrGpuMetadata( + entries, + "optimized-irgpu", + artifact + )); snapshot.deviceSelection().ifPresent(selection -> append( entries, "device-selection", @@ -108,6 +124,40 @@ private static void append( } } + private static void appendIrGpuMetadata( + ArrayList entries, + String artifactStage, + IrGpuArtifact artifact + ) { + if (artifact == null || artifact.extensionParticipationMetadata().isEmpty()) { + return; + } + for (IrGpuExtensionParticipationMetadata metadata : artifact.extensionParticipationMetadata()) { + if (metadata != null) { + entries.add(new Entry( + artifactStage + ":" + metadata.source(), + toExecutionReport(metadata) + )); + } + } + } + + private static GpuExtensionExecutionReport toExecutionReport(IrGpuExtensionParticipationMetadata metadata) { + return new GpuExtensionExecutionReport( + metadata.extensionId(), + metadata.extensionVersion(), + metadata.phase(), + metadata.permission(), + metadata.operation(), + metadata.outcome(), + metadata.failurePolicy(), + metadata.pipelineContinued(), + metadata.failureType(), + metadata.message(), + metadata.diagnostics() + ); + } + private long count(GpuExtensionExecutionOutcome outcome) { return entries.stream().filter(entry -> entry.report().outcome() == outcome).count(); } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClExtensionParticipationSummary.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClExtensionParticipationSummary.java index 31353c64..d3af8f79 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClExtensionParticipationSummary.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClExtensionParticipationSummary.java @@ -151,6 +151,7 @@ String toMarkdown() { markdown.append("- Failed continued: `").append(totalFailedContinued()).append("`\n"); markdown.append("- Failed closed: `").append(totalFailedClosed()).append("`\n"); markdown.append("- Missing artifacts: `").append(missingCount()).append("`\n\n"); + markdown.append("- Participation sources: `").append(inline(sourceSummary())).append("`\n\n"); if (entries.isEmpty()) { return markdown.toString(); } @@ -169,6 +170,26 @@ String toMarkdown() { return markdown.toString(); } + String sourceSummary() { + LinkedHashMap counts = new LinkedHashMap<>(); + for (Entry entry : entries) { + for (Map.Entry source : entry.sourceCounts().entrySet()) { + counts.merge(source.getKey(), source.getValue(), Integer::sum); + } + } + if (counts.isEmpty()) { + return "none"; + } + StringBuilder summary = new StringBuilder(); + counts.forEach((source, count) -> { + if (!summary.isEmpty()) { + summary.append(", "); + } + summary.append(source).append('=').append(count); + }); + return summary.toString(); + } + private static Map loadParticipationArtifacts(Path artifactRoot) throws IOException { LinkedHashMap artifacts = new LinkedHashMap<>(); if (artifactRoot == null || !Files.isDirectory(artifactRoot)) { @@ -220,7 +241,8 @@ record Entry( int failedContinuedCount, int failedClosedCount, String pipelineContinuedAll, - String firstFailure + String firstFailure, + Map sourceCounts ) { private static final Base64.Encoder HISTORY_ENCODER = Base64.getUrlEncoder().withoutPadding(); @@ -234,6 +256,28 @@ record Entry( failedClosedCount = Math.max(0, failedClosedCount); pipelineContinuedAll = normalize(pipelineContinuedAll, "unknown"); firstFailure = normalize(firstFailure, "none"); + sourceCounts = normalizeSourceCounts(sourceCounts); + } + + Entry( + String kernelResource, + String status, + int executionCount, + int failedContinuedCount, + int failedClosedCount, + String pipelineContinuedAll, + String firstFailure + ) { + this( + kernelResource, + status, + executionCount, + failedContinuedCount, + failedClosedCount, + pipelineContinuedAll, + firstFailure, + Map.of() + ); } static Entry from(String kernelResource, Properties properties) { @@ -247,7 +291,8 @@ static Entry from(String kernelResource, Properties properties) { parseInt(properties.getProperty("failedContinued.count"), 0), parseInt(properties.getProperty("failedClosed.count"), 0), properties.getProperty("pipelineContinued.all", "unknown"), - properties.getProperty("firstFailure", "none") + properties.getProperty("firstFailure", "none"), + parseSourceCounts(properties) ); } @@ -259,13 +304,14 @@ String toHistoryToken() { encode(Integer.toString(failedContinuedCount)), encode(Integer.toString(failedClosedCount)), encode(pipelineContinuedAll), - encode(firstFailure) + encode(firstFailure), + encode(formatSourceCounts(sourceCounts)) ); } static Optional fromHistoryToken(String token) { String[] fields = token.split("\\.", -1); - if (fields.length != 7) { + if (fields.length != 7 && fields.length != 8) { return Optional.empty(); } try { @@ -276,7 +322,8 @@ static Optional fromHistoryToken(String token) { Integer.parseInt(decode(fields[3])), Integer.parseInt(decode(fields[4])), decode(fields[5]), - decode(fields[6]) + decode(fields[6]), + fields.length == 8 ? parseSourceCounts(decode(fields[7])) : Map.of() )); } catch (IllegalArgumentException failure) { return Optional.empty(); @@ -294,5 +341,60 @@ private static String encode(String value) { private static String decode(String value) { return new String(HISTORY_DECODER.decode(value), StandardCharsets.UTF_8); } + + private static Map parseSourceCounts(Properties properties) { + LinkedHashMap counts = new LinkedHashMap<>(); + int count = parseInt(properties.getProperty("entry.count"), 0); + for (int index = 0; index < count; index++) { + String source = properties.getProperty("entry." + index + ".source", ""); + if (!source.isBlank()) { + counts.merge(source, 1, Integer::sum); + } + } + return counts; + } + + private static Map parseSourceCounts(String summary) { + LinkedHashMap counts = new LinkedHashMap<>(); + if (summary == null || summary.isBlank() || "none".equals(summary)) { + return counts; + } + for (String token : summary.split(",", -1)) { + String trimmed = token.trim(); + int separator = trimmed.lastIndexOf('='); + if (separator <= 0 || separator == trimmed.length() - 1) { + return Map.of(); + } + counts.put(trimmed.substring(0, separator), parseInt(trimmed.substring(separator + 1), 0)); + } + return counts; + } + + private static Map normalizeSourceCounts(Map values) { + if (values == null || values.isEmpty()) { + return Map.of(); + } + LinkedHashMap normalized = new LinkedHashMap<>(); + values.forEach((source, count) -> { + if (source != null && !source.isBlank() && count != null && count > 0) { + normalized.put(source, count); + } + }); + return java.util.Collections.unmodifiableMap(normalized); + } + + private static String formatSourceCounts(Map counts) { + if (counts == null || counts.isEmpty()) { + return "none"; + } + StringBuilder summary = new StringBuilder(); + counts.forEach((source, count) -> { + if (!summary.isEmpty()) { + summary.append(", "); + } + summary.append(source).append('=').append(count); + }); + return summary.toString(); + } } } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackend.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackend.java index b4d1852b..b5bf6f2c 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackend.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackend.java @@ -3152,7 +3152,8 @@ private void dumpBackendSourcePromotionWorkloadGate(GpuRuntimeCompileArtifactSna dump.artifact(GpuPromotionArtifactRegistry.RUNTIME_PRODUCTION_MUTATION_SAFETY), dump.artifact(GpuPromotionArtifactRegistry.I3_READINESS_SUMMARY), dump.artifact(GpuPromotionArtifactRegistry.RUNTIME_OPTIMIZER_DRIFT), - dump.artifact(GpuPromotionArtifactRegistry.RUNTIME_OPTIMIZER_FAMILY_EQUIVALENCE_PAYLOAD) + dump.artifact(GpuPromotionArtifactRegistry.RUNTIME_OPTIMIZER_FAMILY_EQUIVALENCE_PAYLOAD), + dump.artifact(GpuRuntimeCompileArtifactDumper.RUNTIME_EXTENSION_PARTICIPATION_ARTIFACT) ); java.nio.file.Files.writeString(path, gateProperties, java.nio.charset.StandardCharsets.UTF_8); } catch (RuntimeException | java.io.IOException exception) { diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatterTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatterTest.java index 82d8942b..5c55621d 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatterTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatterTest.java @@ -119,6 +119,42 @@ void mergesKernelEntriesAndAggregatesBlockerFamilies() throws IOException { assertEquals("source-parity", gate.getProperty("kernel.1.blockerFamily.0.name")); } + @Test + void carriesRuntimeExtensionParticipationIntoWorkloadGateEvidence() throws IOException { + Path gateFile = tempDir.resolve("backend-source-promotion-workload-gate.properties"); + + Properties gate = loadProperties(GpuBackendSourcePromotionWorkloadGateFormatter.merge( + gateFile, + "kernel-extension-participation.cl", + blockedGateProperties("backend source promotion remains blocked for fixture"), + "", + "", + "", + "", + "", + "", + runtimeExtensionParticipationProperties() + )); + + assertEquals("1", gate.getProperty("runtimeExtensionParticipation.recordedKernel.count")); + assertEquals("3", gate.getProperty("runtimeExtensionParticipation.entry.count")); + assertEquals("1", gate.getProperty("runtimeExtensionParticipation.failedContinued.count")); + assertEquals("0", gate.getProperty("runtimeExtensionParticipation.failedClosed.count")); + assertEquals("2", gate.getProperty("runtimeExtensionParticipation.source.count")); + assertEquals("original-irgpu:ir-validation", gate.getProperty("runtimeExtensionParticipation.source.0.name")); + assertEquals("1", gate.getProperty("runtimeExtensionParticipation.source.0.count")); + assertEquals("backend-compiler-feedback", gate.getProperty("runtimeExtensionParticipation.source.1.name")); + assertEquals("2", gate.getProperty("runtimeExtensionParticipation.source.1.count")); + assertEquals("recorded", gate.getProperty("kernel.0.runtimeExtensionParticipation.status")); + assertEquals("3", gate.getProperty("kernel.0.runtimeExtensionParticipation.entry.count")); + assertEquals("2", gate.getProperty("kernel.0.runtimeExtensionParticipation.succeeded.count")); + assertEquals("1", gate.getProperty("kernel.0.runtimeExtensionParticipation.failedContinued.count")); + assertEquals("true", gate.getProperty("kernel.0.runtimeExtensionParticipation.pipelineContinued.all")); + assertEquals("compiler-feedback:mock:FAILED_CONTINUED", gate.getProperty("kernel.0.runtimeExtensionParticipation.firstFailure")); + assertEquals("original-irgpu:ir-validation", gate.getProperty("kernel.0.runtimeExtensionParticipation.source.0.name")); + assertEquals("backend-compiler-feedback", gate.getProperty("kernel.0.runtimeExtensionParticipation.source.1.name")); + } + @Test void updatesExistingKernelResourceInsteadOfDuplicatingIt() throws IOException { Path gateFile = tempDir.resolve("backend-source-promotion-workload-gate.properties"); @@ -800,4 +836,27 @@ private static String runtimeOptimizerDriftProperties( "" ); } + + private static String runtimeExtensionParticipationProperties() { + return String.join("\n", + "status=recorded", + "backendTarget=OPENCL", + "backendFormat=opencl-c", + "backendResource=kernel-extension-participation.cl", + "entry.count=3", + "succeeded.count=2", + "skipped.count=0", + "failedContinued.count=1", + "failedClosed.count=0", + "pipelineContinued.all=true", + "firstFailure=compiler-feedback:mock:FAILED_CONTINUED", + "entry.0.source=original-irgpu:ir-validation", + "entry.0.extensionId=validator:shape-contract", + "entry.1.source=backend-compiler-feedback", + "entry.1.extensionId=compiler-feedback:mock", + "entry.2.source=backend-compiler-feedback", + "entry.2.extensionId=compiler-feedback:fallback", + "" + ); + } } diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummaryTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummaryTest.java index c38bedc9..0eb8fc2b 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummaryTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummaryTest.java @@ -58,6 +58,19 @@ void summarizesIndexedSourceSwitchingEvidenceForHistoryAndReports() { properties.setProperty("sourceSwitching.sourcePromotionFirstBlockerFamily.count", "1"); properties.setProperty("sourceSwitching.sourcePromotionFirstBlockerFamily.0.name", "runtime-equivalence"); properties.setProperty("sourceSwitching.sourcePromotionFirstBlockerFamily.0.count", "1"); + properties.setProperty("runtimeExtensionParticipation.recordedKernel.count", "1"); + properties.setProperty("runtimeExtensionParticipation.entry.count", "3"); + properties.setProperty("runtimeExtensionParticipation.failedContinued.count", "1"); + properties.setProperty("runtimeExtensionParticipation.failedClosed.count", "0"); + properties.setProperty("runtimeExtensionParticipation.source.count", "2"); + properties.setProperty("runtimeExtensionParticipation.source.0.name", "original-irgpu:ir-validation"); + properties.setProperty("runtimeExtensionParticipation.source.0.count", "1"); + properties.setProperty("runtimeExtensionParticipation.source.1.name", "backend-compiler-feedback"); + properties.setProperty("runtimeExtensionParticipation.source.1.count", "2"); + properties.setProperty("kernel.0.runtimeExtensionParticipation.status", "recorded"); + properties.setProperty("kernel.0.runtimeExtensionParticipation.entry.count", "3"); + properties.setProperty("kernel.0.runtimeExtensionParticipation.failedContinued.count", "1"); + properties.setProperty("kernel.0.runtimeExtensionParticipation.failedClosed.count", "0"); GpuBackendSourcePromotionWorkloadSummary summary = GpuBackendSourcePromotionWorkloadSummary.fromProperties(properties); @@ -79,14 +92,24 @@ void summarizesIndexedSourceSwitchingEvidenceForHistoryAndReports() { ); assertEquals(1, summary.productionPromotionOperatorAcceptedCount()); assertEquals("true", summary.productionPromotionOperatorAcceptedAll()); + assertEquals(1, summary.runtimeExtensionParticipationRecordedKernelCount()); + assertEquals(3, summary.runtimeExtensionParticipationEntryCount()); + assertEquals(1, summary.runtimeExtensionParticipationFailedContinuedCount()); + assertEquals(0, summary.runtimeExtensionParticipationFailedClosedCount()); + assertEquals( + "original-irgpu:ir-validation=1, backend-compiler-feedback=2", + summary.runtimeExtensionParticipationSources() + ); assertTrue(summary.historyStatus().contains("gateStatus=blocked")); assertTrue(summary.historyStatus().contains("realWorkloadEvidence=runtime-snapshot")); assertTrue(summary.historyStatus().contains("productionPromotionOperatorAccepted=1/1")); assertTrue(summary.historyStatus().contains("productionPromotionOperatorAcceptedAll=true")); + assertTrue(summary.historyStatus().contains("runtimeExtensionParticipation=recordedKernels=1/executions=3/failedContinued=1/failedClosed=0/sources=original-irgpu:ir-validation=1, backend-compiler-feedback=2")); assertTrue(summary.historyStatus().contains("optimizerFamilies=2")); assertTrue(summary.historyStatus().contains("optimizerPromotionReadyFamilies=1")); assertTrue(summary.historyStatus().contains("optimizerFamilySummary=cse[passes=1")); assertTrue(summary.historyStatus().contains("kernelCount=1")); + assertTrue(summary.historyStatus().contains("extensionParticipation=recorded/3executions/failedContinued=1/failedClosed=0")); assertTrue(summary.historyStatus().contains("sourceSwitching=reject-production-irgpu-source/operatorAccepted=true")); assertTrue(summary.historyStatus().contains("proof=2/acceptedProof=1/blockingProof=1/optimizerFamilies=2/promotionReadyFamilies=1")); assertTrue(summary.historyStatus().contains("families=source-parity=1")); @@ -235,6 +258,13 @@ void cliFormatKeepsCiFriendlyScalarFields() { properties.setProperty("kernel.0.runtimeOptimizerDrift.optimizerFamily.count", "0"); properties.setProperty("kernel.0.runtimeOptimizerDrift.optimizerFamily.promotionReady.count", "0"); properties.setProperty("kernel.0.runtimeOptimizerDrift.optimizerFamily.summary", "none"); + properties.setProperty("runtimeExtensionParticipation.recordedKernel.count", "1"); + properties.setProperty("runtimeExtensionParticipation.entry.count", "2"); + properties.setProperty("runtimeExtensionParticipation.failedContinued.count", "0"); + properties.setProperty("runtimeExtensionParticipation.failedClosed.count", "0"); + properties.setProperty("runtimeExtensionParticipation.source.count", "1"); + properties.setProperty("runtimeExtensionParticipation.source.0.name", "original-irgpu:ir-validation"); + properties.setProperty("runtimeExtensionParticipation.source.0.count", "2"); properties.setProperty( "kernel.0.sourceSwitching.sourcePromotionFirstBlocker", "backend source must be reconstructed from IrGpu before promotion review" @@ -260,6 +290,13 @@ void cliFormatKeepsCiFriendlyScalarFields() { assertTrue(formatted.contains("optimizerFamily.count=0\n")); assertTrue(formatted.contains("optimizerFamily.promotionReady.count=0\n")); assertTrue(formatted.contains("optimizerFamily.summary=none\n")); + assertTrue(formatted.contains("runtimeExtensionParticipation.recordedKernel.count=1\n")); + assertTrue(formatted.contains("runtimeExtensionParticipation.entry.count=2\n")); + assertTrue(formatted.contains("runtimeExtensionParticipation.failedContinued.count=0\n")); + assertTrue(formatted.contains("runtimeExtensionParticipation.failedClosed.count=0\n")); + assertTrue(formatted.contains("runtimeExtensionParticipation.sources=original-irgpu:ir-validation=2\n")); + assertTrue(formatted.contains("runtimeExtensionParticipation.source.0.name=original-irgpu:ir-validation\n")); + assertTrue(formatted.contains("runtimeExtensionParticipation.source.0.count=2\n")); assertTrue(formatted.contains("historyStatus=not-promoted")); } } diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java index 825835a1..1b402c9c 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java @@ -11,6 +11,7 @@ import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuEntryParameter; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuExtensionParticipationMetadata; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuFeatureMetadata; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuLaunchMetadata; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; @@ -93,6 +94,135 @@ public Optional inspect(GpuBackendCompilerFeedbackRe assertTrue(participation.contains("entry.2.outcome=FAILED_CONTINUED")); } + @Test + void dumpsIrGpuExtensionParticipationIntoRuntimeParticipationArtifact() { + IrGpuArtifact irGpuArtifact = artifact("body\n return original\n") + .withExtensionParticipationMetadata(List.of(new IrGpuExtensionParticipationMetadata( + "ir-validation", + "validator:shape-contract", + "3", + GpuExtensionPhase.IR_VALIDATION, + GpuExtensionPermission.READ_ONLY, + "IR validation", + GpuExtensionExecutionOutcome.SUCCEEDED, + GpuExtensionFailurePolicy.CONTINUE, + true, + "none", + "validator accepted IR shape", + List.of("validator diagnostic") + ))); + GpuRuntimeCompileRequest request = new GpuRuntimeCompileRequest( + descriptor(), + GpuRuntimeCompileOptions.defaults(GpuBackendTarget.OPENCL), + GpuRuntimeDeviceProfile.generic(GpuBackendTarget.OPENCL, "OpenCL"), + Optional.of(irGpuArtifact) + ); + GpuBackendModuleArtifact backendArtifact = GpuBackendModuleArtifact.openClSource( + "__kernel void kernel(__global int* output) { output[0] = 1; }", + "javatogpu/sample/Demo/kernel.cl", + "test-lowerer-v1" + ); + GpuRuntimeCompileArtifactSnapshot snapshot = GpuRuntimeCompileArtifactSnapshot.from( + request, + request, + backendArtifact, + GpuRuntimeCompileInvalidationStamp.from(request, backendArtifact, "optimizer:test-v1"), + GpuRuntimeCompileProvenance.from(request), + GpuRuntimeIrOptimizationReport.empty(Optional.of(irGpuArtifact)) + ); + + GpuRuntimeCompileArtifactDump dump = GpuRuntimeCompileArtifactDumper.dump( + snapshot, + GpuBackendCompilerFeedbackRegistry.of(List.of()) + ); + + String participation = dump.artifact(GpuRuntimeCompileArtifactDumper.RUNTIME_EXTENSION_PARTICIPATION_ARTIFACT); + assertTrue(participation.contains("status=recorded")); + assertTrue(participation.contains("entry.count=1")); + assertTrue(participation.contains("succeeded.count=1")); + assertTrue(participation.contains("entry.0.source=original-irgpu:ir-validation")); + assertTrue(participation.contains("entry.0.extensionId=validator:shape-contract")); + assertTrue(participation.contains("entry.0.extensionVersion=3")); + assertTrue(participation.contains("entry.0.phase=IR_VALIDATION")); + assertTrue(participation.contains("entry.0.permission=READ_ONLY")); + assertTrue(participation.contains("entry.0.outcome=SUCCEEDED")); + } + + @Test + void dumpsOptimizedIrGpuExtensionParticipationWhenArtifactDiffers() { + IrGpuArtifact originalIrGpuArtifact = artifact("body\n return original\n") + .withExtensionParticipationMetadata(List.of(new IrGpuExtensionParticipationMetadata( + "ir-validation", + "validator:shape-contract", + "3", + GpuExtensionPhase.IR_VALIDATION, + GpuExtensionPermission.READ_ONLY, + "IR validation", + GpuExtensionExecutionOutcome.SUCCEEDED, + GpuExtensionFailurePolicy.CONTINUE, + true, + "none", + "validator accepted IR shape", + List.of() + ))); + IrGpuArtifact optimizedIrGpuArtifact = artifact("body\n return optimized\n") + .withExtensionParticipationMetadata(List.of(new IrGpuExtensionParticipationMetadata( + "runtime-ir-optimization", + "optimizer:review-pass", + "2", + GpuExtensionPhase.RUNTIME_IR_OPTIMIZATION, + GpuExtensionPermission.MUTATION_PROPOSAL, + "Runtime IR optimization", + GpuExtensionExecutionOutcome.SKIPPED, + GpuExtensionFailurePolicy.CONTINUE, + true, + "none", + "optimizer proposed no production mutation", + List.of() + ))); + GpuRuntimeCompileRequest originalRequest = new GpuRuntimeCompileRequest( + descriptor(), + GpuRuntimeCompileOptions.defaults(GpuBackendTarget.OPENCL), + GpuRuntimeDeviceProfile.generic(GpuBackendTarget.OPENCL, "OpenCL"), + Optional.of(originalIrGpuArtifact) + ); + GpuRuntimeCompileRequest optimizedRequest = new GpuRuntimeCompileRequest( + descriptor(), + GpuRuntimeCompileOptions.defaults(GpuBackendTarget.OPENCL), + GpuRuntimeDeviceProfile.generic(GpuBackendTarget.OPENCL, "OpenCL"), + Optional.of(optimizedIrGpuArtifact) + ); + GpuBackendModuleArtifact backendArtifact = GpuBackendModuleArtifact.openClSource( + "__kernel void kernel(__global int* output) { output[0] = 1; }", + "javatogpu/sample/Demo/kernel.cl", + "test-lowerer-v1" + ); + GpuRuntimeCompileArtifactSnapshot snapshot = GpuRuntimeCompileArtifactSnapshot.from( + originalRequest, + optimizedRequest, + backendArtifact, + GpuRuntimeCompileInvalidationStamp.from(optimizedRequest, backendArtifact, "optimizer:test-v1"), + GpuRuntimeCompileProvenance.from(optimizedRequest), + GpuRuntimeIrOptimizationReport.empty(Optional.of(optimizedIrGpuArtifact)) + ); + + GpuRuntimeCompileArtifactDump dump = GpuRuntimeCompileArtifactDumper.dump( + snapshot, + GpuBackendCompilerFeedbackRegistry.of(List.of()) + ); + + String participation = dump.artifact(GpuRuntimeCompileArtifactDumper.RUNTIME_EXTENSION_PARTICIPATION_ARTIFACT); + assertTrue(participation.contains("entry.count=2")); + assertTrue(participation.contains("succeeded.count=1")); + assertTrue(participation.contains("skipped.count=1")); + assertTrue(participation.contains("entry.0.source=original-irgpu:ir-validation")); + assertTrue(participation.contains("entry.0.extensionId=validator:shape-contract")); + assertTrue(participation.contains("entry.1.source=optimized-irgpu:runtime-ir-optimization")); + assertTrue(participation.contains("entry.1.extensionId=optimizer:review-pass")); + assertTrue(participation.contains("entry.1.permission=MUTATION_PROPOSAL")); + assertTrue(participation.contains("entry.1.outcome=SKIPPED")); + } + @Test void dumpsStructuredRuntimeEquivalenceComparisonCases() { GpuRuntimeEquivalenceCaseEvidence caseEvidence = new GpuRuntimeEquivalenceCaseEvidence( diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClExtensionParticipationSummaryTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClExtensionParticipationSummaryTest.java index 9afe11d9..3d137b12 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClExtensionParticipationSummaryTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClExtensionParticipationSummaryTest.java @@ -43,6 +43,9 @@ void readsRuntimeExtensionParticipationArtifactsAndRoundTripsHistorySnapshot() t failedClosed.count=0 pipelineContinued.all=true firstFailure=compiler-feedback:mock:FAILED_CONTINUED + entry.0.source=original-irgpu:ir-validation + entry.1.source=device-selection + entry.2.source=backend-compiler-feedback """); OpenClExtensionParticipationSummary summary = OpenClExtensionParticipationSummary.read(workloadGate); @@ -58,7 +61,12 @@ void readsRuntimeExtensionParticipationArtifactsAndRoundTripsHistorySnapshot() t assertEquals(1, summary.totalFailedContinued()); assertEquals(0, summary.totalFailedClosed()); assertEquals(1, summary.missingCount()); + assertEquals( + "original-irgpu:ir-validation=1, device-selection=1, backend-compiler-feedback=1", + summary.sourceSummary() + ); assertTrue(summary.toMarkdown().contains("## Runtime Extension Participation")); + assertTrue(summary.toMarkdown().contains("- Participation sources: `original-irgpu:ir-validation=1, device-selection=1, backend-compiler-feedback=1`")); assertTrue(summary.toMarkdown().contains("| `kernel-a.cl` | `recorded` | `3` | `1` | `0` | `true` | `compiler-feedback:mock:FAILED_CONTINUED` |")); String history = summary.toHistorySummary(); From 7b6d77fb6d4276bbd1408e8ff8bed4cbc1eb8875 Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Sat, 11 Jul 2026 22:46:39 +0300 Subject: [PATCH 12/32] wip --- ROADMAP.md | 2 +- .../frontend/GpuFrontendArtifactWriter.java | 48 ++++++++++- ...RuntimeExtensionParticipationArtifact.java | 82 +++++++++++++++++++ .../GpuFrontendArtifactWriterTest.java | 8 ++ .../GpuRuntimeCompileArtifactDumperTest.java | 45 +++++++--- 5 files changed, 173 insertions(+), 12 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index eb2ed23b..37425ca3 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -613,7 +613,7 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i - [x] Persist OpenCL device selection and policy participation in runtime compile artifacts. `GpuRuntimeCompileArtifactSnapshot` now carries an optional backend-neutral `GpuRuntimeDeviceSelection`. OpenCL attaches the live selection without forcing context creation in mock/custom backends, and `GpuRuntimeCompileArtifactDumper` emits `runtime-device-selection.properties`. The artifact records selected device identity/class/vendor/driver, deterministic candidate scores and rejection state, policy ids/versions, score adjustments, capability facts, quirks, compile-option diagnostics, extension execution outcomes/failure policies, blockers, and final diagnostics. - [ ] Persist remaining extension participation in `IrGpu` and cross-pipeline artifacts. - `kernel.irgpu.properties`, optimizer drift artifacts, promotion gates, validation history, and future backend artifacts should record the relevant extension ids, versions, phases, decisions, diagnostics, and whether each extension was advisory or production-affecting. Runtime compile dumps now include a normalized `runtime-extension-participation.properties` aggregate for build-time `IrGpu` validator metadata, device-selection, runtime optimizer, and backend compiler-feedback execution reports. OpenCL workload gates, workload summaries, workload-summary CLI output, validation reports, and validation history now surface per-kernel and aggregate `Runtime Extension Participation` evidence, including source breakdowns such as `original-irgpu:ir-validation`, `runtime-ir-optimization`, and `backend-compiler-feedback`. Backend-lowerer, runtime-equivalence, artifact-writer, and future backend-specific artifacts still need the same normalized participation model. This keeps generated artifacts auditable when a project depends on company-specific plugins or third-party GPU libraries. + `kernel.irgpu.properties`, optimizer drift artifacts, promotion gates, validation history, and future backend artifacts should record the relevant extension ids, versions, phases, decisions, diagnostics, and whether each extension was advisory or production-affecting. Runtime compile dumps now include a normalized `runtime-extension-participation.properties` aggregate for build-time `IrGpu` validator metadata, device-selection, runtime optimizer, runtime-equivalence evidence, backend-lowerer/module production-affecting participation, and backend compiler-feedback execution reports. The frontend artifact writer now records `ARTIFACT_EMISSION` participation inside `kernel.irgpu.properties`, including paired OpenCL/IrGpu resource diagnostics. OpenCL workload gates, workload summaries, workload-summary CLI output, validation reports, and validation history now surface per-kernel and aggregate `Runtime Extension Participation` evidence, including source breakdowns such as `original-irgpu:ir-validation`, `runtime-ir-optimization`, `runtime-equivalence`, `backend-lowerer`, and `backend-compiler-feedback`. Future backend-specific artifacts still need the same normalized participation model. This keeps generated artifacts auditable when a project depends on company-specific plugins or third-party GPU libraries. #### Future IR optimization passes diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendArtifactWriter.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendArtifactWriter.java index 87819878..a0f3ad49 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendArtifactWriter.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendArtifactWriter.java @@ -1,9 +1,16 @@ package net.sixik.ga_utils.javatogpu.frontend; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionExecutionOutcome; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionFailurePolicy; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionPermission; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionPhase; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactSerializer; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuExtensionParticipationMetadata; import java.io.IOException; import java.io.Writer; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; /** @@ -11,6 +18,9 @@ */ public final class GpuFrontendArtifactWriter { + static final String ARTIFACT_WRITER_EXTENSION_ID = "artifact-writer:frontend"; + static final String ARTIFACT_WRITER_EXTENSION_VERSION = "frontend-artifact-writer-v1"; + private GpuFrontendArtifactWriter() { } @@ -21,7 +31,43 @@ public static void write( Objects.requireNonNull(result, "result"); Objects.requireNonNull(sink, "sink"); writeText(result.openClResource(), result.openClSource(), sink); - writeText(result.irGpuResource(), IrGpuArtifactSerializer.serialize(result.irGpuArtifact()), sink); + writeText( + result.irGpuResource(), + IrGpuArtifactSerializer.serialize(withArtifactWriterParticipation(result)), + sink + ); + } + + private static net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact withArtifactWriterParticipation( + GpuFrontendCompilationResult result + ) { + ArrayList metadata = new ArrayList<>( + result.irGpuArtifact().extensionParticipationMetadata() + ); + metadata.add(new IrGpuExtensionParticipationMetadata( + "artifact-writer", + ARTIFACT_WRITER_EXTENSION_ID, + ARTIFACT_WRITER_EXTENSION_VERSION, + GpuExtensionPhase.ARTIFACT_EMISSION, + GpuExtensionPermission.READ_ONLY, + "frontend artifact emission", + GpuExtensionExecutionOutcome.SUCCEEDED, + GpuExtensionFailurePolicy.CONTINUE, + true, + "none", + "frontend artifact writer emitted paired OpenCL and IrGpu resources", + List.of( + "openClResource=" + result.openClResource(), + "irGpuResource=" + result.irGpuResource(), + "openClSource.bytes=" + byteLength(result.openClSource()), + "irGpuManifest.format=javatogpu.irgpu.v1" + ) + )); + return result.irGpuArtifact().withExtensionParticipationMetadata(metadata); + } + + private static int byteLength(String value) { + return value == null ? 0 : value.getBytes(java.nio.charset.StandardCharsets.UTF_8).length; } private static void writeText(String resourcePath, String text, ArtifactSink sink) throws IOException { diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeExtensionParticipationArtifact.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeExtensionParticipationArtifact.java index 38ccaf41..d0cf22f5 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeExtensionParticipationArtifact.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeExtensionParticipationArtifact.java @@ -2,6 +2,9 @@ import net.sixik.ga_utils.javatogpu.extension.GpuExtensionExecutionOutcome; import net.sixik.ga_utils.javatogpu.extension.GpuExtensionExecutionReport; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionFailurePolicy; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionPermission; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionPhase; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuExtensionParticipationMetadata; @@ -64,6 +67,8 @@ public static GpuRuntimeExtensionParticipationArtifact from( "runtime-ir-optimization", snapshot.optimizationReport().extensionExecutionReports() ); + appendRuntimeEquivalence(entries, snapshot.runtimeEquivalenceEvidence()); + appendBackendLowerer(entries, snapshot.backendModuleArtifact()); append( entries, "backend-compiler-feedback", @@ -142,6 +147,83 @@ private static void appendIrGpuMetadata( } } + private static void appendRuntimeEquivalence( + ArrayList entries, + GpuRuntimeEquivalenceEvidence evidence + ) { + if (evidence == null) { + return; + } + entries.add(new Entry( + "runtime-equivalence", + new GpuExtensionExecutionReport( + "runtime-equivalence:" + evidence.backendTarget().toLowerCase(java.util.Locale.ROOT), + "runtime-equivalence-v1", + GpuExtensionPhase.RUNTIME_EQUIVALENCE, + GpuExtensionPermission.READ_ONLY, + "runtime equivalence evidence capture", + runtimeEquivalenceOutcome(evidence), + GpuExtensionFailurePolicy.CONTINUE, + true, + evidence.executed() && !evidence.equivalent() ? "runtime-equivalence-failed" : "none", + "runtime equivalence " + evidence.status(), + runtimeEquivalenceDiagnostics(evidence) + ) + )); + } + + private static GpuExtensionExecutionOutcome runtimeEquivalenceOutcome(GpuRuntimeEquivalenceEvidence evidence) { + if (!evidence.executed()) { + return GpuExtensionExecutionOutcome.SKIPPED; + } + return evidence.equivalent() + ? GpuExtensionExecutionOutcome.SUCCEEDED + : GpuExtensionExecutionOutcome.FAILED_CONTINUED; + } + + private static List runtimeEquivalenceDiagnostics(GpuRuntimeEquivalenceEvidence evidence) { + ArrayList diagnostics = new ArrayList<>(); + diagnostics.add("status=" + evidence.status()); + diagnostics.add("backendTarget=" + evidence.backendTarget()); + diagnostics.add("vendor=" + evidence.vendor()); + diagnostics.add("deviceLabel=" + evidence.deviceLabel()); + diagnostics.add("optimizationProfile=" + evidence.optimizationProfile()); + diagnostics.add("executed=" + evidence.executed()); + diagnostics.add("equivalent=" + evidence.equivalent()); + diagnostics.add("inputCase.count=" + evidence.inputCaseCount()); + diagnostics.add("comparedOutput.count=" + evidence.comparedOutputCount()); + diagnostics.addAll(evidence.diagnostics()); + return diagnostics; + } + + private static void appendBackendLowerer(ArrayList entries, GpuBackendModuleArtifact artifact) { + if (artifact == null || artifact.backendTarget() == null || artifact.lowererVersion().isBlank()) { + return; + } + entries.add(new Entry( + "backend-lowerer", + new GpuExtensionExecutionReport( + "backend-lowerer:" + artifact.backendTarget().name().toLowerCase(java.util.Locale.ROOT), + artifact.lowererVersion(), + GpuExtensionPhase.BACKEND_LOWERING, + GpuExtensionPermission.PRODUCTION_AFFECTING, + "backend module lowering", + GpuExtensionExecutionOutcome.SUCCEEDED, + GpuExtensionFailurePolicy.STOP_PIPELINE, + true, + "none", + "backend lowerer produced " + artifact.kind() + " artifact " + artifact.format(), + List.of( + "backendTarget=" + artifact.backendTarget().name(), + "backendFormat=" + artifact.format(), + "backendResource=" + artifact.resource(), + "sourceOrigin=" + artifact.sourceOrigin(), + "runtimeLoadMode=" + artifact.runtimeLoadMode() + ) + ) + )); + } + private static GpuExtensionExecutionReport toExecutionReport(IrGpuExtensionParticipationMetadata metadata) { return new GpuExtensionExecutionReport( metadata.extensionId(), diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendArtifactWriterTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendArtifactWriterTest.java index 30116d4b..e47a6e00 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendArtifactWriterTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendArtifactWriterTest.java @@ -64,6 +64,14 @@ void writesOpenClAndIrGpuArtifactsThroughOnePackagingSink() throws Exception { assertTrue(manifest.contains("entryMethod=kernel")); assertTrue(manifest.contains("derived.opencl.resource=javatogpu/sample/Demo/kernel.cl")); assertTrue(manifest.contains("entryParameter.0.name=output")); + assertTrue(manifest.contains("extensionParticipation.count=1")); + assertTrue(manifest.contains("extensionParticipation.0.source=artifact-writer")); + assertTrue(manifest.contains("extensionParticipation.0.extensionId=artifact-writer:frontend")); + assertTrue(manifest.contains("extensionParticipation.0.phase=ARTIFACT_EMISSION")); + assertTrue(manifest.contains("extensionParticipation.0.permission=READ_ONLY")); + assertTrue(manifest.contains("extensionParticipation.0.outcome=SUCCEEDED")); + assertTrue(manifest.contains("extensionParticipation.0.diagnostic.0=openClResource=javatogpu/sample/Demo/kernel.cl")); + assertTrue(manifest.contains("extensionParticipation.0.diagnostic.1=irGpuResource=javatogpu/sample/Demo/kernel.irgpu.properties")); } private Writer nonClosing(StringWriter writer) { diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java index 1b402c9c..b6f73f7e 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java @@ -79,8 +79,9 @@ public Optional inspect(GpuBackendCompilerFeedbackRe assertTrue(participation.contains("backendTarget=OPENCL")); assertTrue(participation.contains("backendFormat=opencl-c")); assertTrue(participation.contains("backendResource=javatogpu/sample/Demo/kernel.cl")); - assertTrue(participation.contains("entry.count=3")); - assertTrue(participation.contains("succeeded.count=2")); + assertTrue(participation.contains("entry.count=5")); + assertTrue(participation.contains("succeeded.count=3")); + assertTrue(participation.contains("skipped.count=1")); assertTrue(participation.contains("failedContinued.count=1")); assertTrue(participation.contains("failedClosed.count=0")); assertTrue(participation.contains("pipelineContinued.all=true")); @@ -89,9 +90,19 @@ public Optional inspect(GpuBackendCompilerFeedbackRe assertTrue(participation.contains("entry.0.extensionId=device-policy:test")); assertTrue(participation.contains("entry.1.source=runtime-ir-optimization")); assertTrue(participation.contains("entry.1.extensionId=optimizer:cse")); - assertTrue(participation.contains("entry.2.source=backend-compiler-feedback")); - assertTrue(participation.contains("entry.2.extensionId=compiler-feedback:mock")); - assertTrue(participation.contains("entry.2.outcome=FAILED_CONTINUED")); + assertTrue(participation.contains("entry.2.source=runtime-equivalence")); + assertTrue(participation.contains("entry.2.extensionId=runtime-equivalence:unknown")); + assertTrue(participation.contains("entry.2.phase=RUNTIME_EQUIVALENCE")); + assertTrue(participation.contains("entry.2.permission=READ_ONLY")); + assertTrue(participation.contains("entry.2.outcome=SKIPPED")); + assertTrue(participation.contains("entry.3.source=backend-lowerer")); + assertTrue(participation.contains("entry.3.extensionId=backend-lowerer:opencl")); + assertTrue(participation.contains("entry.3.phase=BACKEND_LOWERING")); + assertTrue(participation.contains("entry.3.permission=PRODUCTION_AFFECTING")); + assertTrue(participation.contains("entry.3.outcome=SUCCEEDED")); + assertTrue(participation.contains("entry.4.source=backend-compiler-feedback")); + assertTrue(participation.contains("entry.4.extensionId=compiler-feedback:mock")); + assertTrue(participation.contains("entry.4.outcome=FAILED_CONTINUED")); } @Test @@ -138,14 +149,23 @@ void dumpsIrGpuExtensionParticipationIntoRuntimeParticipationArtifact() { String participation = dump.artifact(GpuRuntimeCompileArtifactDumper.RUNTIME_EXTENSION_PARTICIPATION_ARTIFACT); assertTrue(participation.contains("status=recorded")); - assertTrue(participation.contains("entry.count=1")); - assertTrue(participation.contains("succeeded.count=1")); + assertTrue(participation.contains("entry.count=3")); + assertTrue(participation.contains("succeeded.count=2")); + assertTrue(participation.contains("skipped.count=1")); assertTrue(participation.contains("entry.0.source=original-irgpu:ir-validation")); assertTrue(participation.contains("entry.0.extensionId=validator:shape-contract")); assertTrue(participation.contains("entry.0.extensionVersion=3")); assertTrue(participation.contains("entry.0.phase=IR_VALIDATION")); assertTrue(participation.contains("entry.0.permission=READ_ONLY")); assertTrue(participation.contains("entry.0.outcome=SUCCEEDED")); + assertTrue(participation.contains("entry.1.source=runtime-equivalence")); + assertTrue(participation.contains("entry.1.extensionId=runtime-equivalence:opencl")); + assertTrue(participation.contains("entry.1.phase=RUNTIME_EQUIVALENCE")); + assertTrue(participation.contains("entry.1.outcome=SKIPPED")); + assertTrue(participation.contains("entry.2.source=backend-lowerer")); + assertTrue(participation.contains("entry.2.extensionId=backend-lowerer:opencl")); + assertTrue(participation.contains("entry.2.extensionVersion=test-lowerer-v1")); + assertTrue(participation.contains("entry.2.phase=BACKEND_LOWERING")); } @Test @@ -212,15 +232,20 @@ void dumpsOptimizedIrGpuExtensionParticipationWhenArtifactDiffers() { ); String participation = dump.artifact(GpuRuntimeCompileArtifactDumper.RUNTIME_EXTENSION_PARTICIPATION_ARTIFACT); - assertTrue(participation.contains("entry.count=2")); - assertTrue(participation.contains("succeeded.count=1")); - assertTrue(participation.contains("skipped.count=1")); + assertTrue(participation.contains("entry.count=4")); + assertTrue(participation.contains("succeeded.count=2")); + assertTrue(participation.contains("skipped.count=2")); assertTrue(participation.contains("entry.0.source=original-irgpu:ir-validation")); assertTrue(participation.contains("entry.0.extensionId=validator:shape-contract")); assertTrue(participation.contains("entry.1.source=optimized-irgpu:runtime-ir-optimization")); assertTrue(participation.contains("entry.1.extensionId=optimizer:review-pass")); assertTrue(participation.contains("entry.1.permission=MUTATION_PROPOSAL")); assertTrue(participation.contains("entry.1.outcome=SKIPPED")); + assertTrue(participation.contains("entry.2.source=runtime-equivalence")); + assertTrue(participation.contains("entry.2.extensionId=runtime-equivalence:opencl")); + assertTrue(participation.contains("entry.2.outcome=SKIPPED")); + assertTrue(participation.contains("entry.3.source=backend-lowerer")); + assertTrue(participation.contains("entry.3.extensionId=backend-lowerer:opencl")); } @Test From e137c692a191da93c315af4c9dddcec2355f6f7d Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Sun, 12 Jul 2026 01:05:44 +0300 Subject: [PATCH 13/32] wip --- ROADMAP.md | 4 ++-- .../javatogpu/frontend/AsmFrontendServiceTest.java | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 37425ca3..3ea7d72f 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -447,8 +447,8 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i The current typed preflight safely detects the initial `mad/fma` candidate shape without editing nodes. Next, add reusable typed-node traversal/replacement APIs that can prove complete-pattern replacement before editing anything. Partial matches must produce diagnostics only. Rule implementations should remain isolated (`mix`, `mad/fma`, `clamp`, `step`, `dot`) and report applied/skipped/blocked counts per rule so optimizer drift artifacts can detect regressions. - [x] Add Java-source fast-math policy metadata for runtime optimizer decisions. `@GPUOptimize(fastMath = false)` is now the production-safe default policy surface. Java-source frontend artifacts persist the explicit annotation source or `default-strict`, runtime optimization requests expose the resolved policy, and optimizer hooks can declare `requiresFastMath()` so the registry skips them without invocation unless the method explicitly opts in. This does not enable production mutation by itself. -- [ ] Preserve fast-math policy through the ASM frontend. - Structured/general ASM ingestion should write the same `IrGpuOptimizerPolicyMetadata` contract as Java source instead of adding an ASM-only flag. Missing ASM policy must resolve to `default-strict`; future ASM compile options may opt in only through the same shared metadata and evidence gates. +- [x] Preserve fast-math policy through the ASM frontend. + Structured ASM ingestion now writes the same `IrGpuOptimizerPolicyMetadata` contract as Java source. Missing ASM policy resolves to `default-strict`, the serialized `kernel.irgpu.properties` manifest carries `optimizerPolicy.fastMath=false` and `optimizerPolicy.source=default-strict`, and parser round-trip coverage pins the storage/runtime boundary. Future ASM compile options may opt in only through the same shared metadata and evidence gates. - [x] Persist fast-math and optimizer-policy metadata in `IrGpu` storage. `kernel.irgpu.properties` now carries `optimizerPolicy.fastMath` and `optimizerPolicy.source`, with backward-compatible strict defaults in the parser. Runtime optimizer requests read the loaded `IrGpu` policy, and passes marked `requiresFastMath()` are fail-closed/skipped when fast math is false or absent. Future rule-family toggles remain open. - [ ] Add proof, runtime-equivalence, and rollback gates for peephole rewrites. diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/AsmFrontendServiceTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/AsmFrontendServiceTest.java index 4f33e2e0..52de0926 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/AsmFrontendServiceTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/frontend/AsmFrontendServiceTest.java @@ -3,6 +3,8 @@ import net.sixik.ga_utils.javatogpu.frontend.asm.AsmFrontendException; import net.sixik.ga_utils.javatogpu.frontend.asm.AsmFrontendFailureMetadata; import net.sixik.ga_utils.javatogpu.frontend.asm.AsmGpuMethod; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactParser; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactSerializer; import net.sixik.ga_utils.javatogpu.frontend.ir.model.GpuIrMethod; import net.sixik.ga_utils.javatogpu.frontend.ir.statement.GpuIrAssignment; import net.sixik.ga_utils.javatogpu.frontend.ir.statement.GpuIrIf; @@ -204,6 +206,14 @@ void compilesStructuredAsmKernelAndReturnsIrGpuArtifact() { assertEquals("javatogpu/sample/Demo/kernel.cl", result.irGpuArtifact().derivedOpenClResource()); assertEquals(false, result.irGpuArtifact().optimizerPolicyMetadata().fastMath()); assertEquals("default-strict", result.irGpuArtifact().optimizerPolicyMetadata().source()); + String manifest = IrGpuArtifactSerializer.serialize(result.irGpuArtifact()); + assertTrue(manifest.contains("sourceFrontend=asm")); + assertTrue(manifest.contains("optimizerPolicy.fastMath=false")); + assertTrue(manifest.contains("optimizerPolicy.source=default-strict")); + assertEquals( + result.irGpuArtifact().optimizerPolicyMetadata(), + IrGpuArtifactParser.parse(manifest).optimizerPolicyMetadata() + ); assertEquals("javatogpu/sample/Demo/kernel.cl", result.openClResource()); assertEquals("javatogpu/sample/Demo/kernel.irgpu.properties", result.irGpuResource()); From f8b323821ca2b74ff1f5423e996622742fe440f1 Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Sun, 12 Jul 2026 12:36:25 +0300 Subject: [PATCH 14/32] wip: Start work on IR-Optimizator --- README.md | 8 +- ROADMAP.md | 43 ++- docs/Home.md | 1 + docs/IR-Optimizer.md | 105 ++++++ docs/Runtime-Guide.md | 2 + docs/Validation-and-Operations.md | 1 + docs/_Sidebar.md | 1 + ir-optimizer/build.gradle | 185 ++++++++++ ...onstantFoldingPreviewProposalProvider.java | 259 ++++++++++++++ ...pendencyDeduplicationProposalProvider.java | 146 ++++++++ .../GpuIrNoOpOptimizationPass.java | 50 +++ .../GpuIrNoOpProposalProvider.java | 27 ++ .../GpuIrOptimizationApprovalManifest.java | 300 ++++++++++++++++ ...OptimizationApprovalTemplateFormatter.java | 61 ++++ ...uIrOptimizationApprovalTemplateResult.java | 82 +++++ .../iroptimizer/GpuIrOptimizationPolicy.java | 108 ++++++ .../GpuIrOptimizationProposal.java | 124 +++++++ .../GpuIrOptimizationProposalDecision.java | 10 + .../GpuIrOptimizationProposalProvider.java | 32 ++ .../GpuIrOptimizationProposalRegistry.java | 83 +++++ .../GpuIrOptimizationProposalRequest.java | 62 ++++ .../GpuIrOptimizationSandwichReport.java | 45 +++ .../GpuIrOptimizationSandwichRunner.java | 157 +++++++++ .../GpuIrOptimizationSandwichStatus.java | 13 + .../GpuIrOptimizationValidationGate.java | 14 + .../GpuIrOptimizationValidationRequest.java | 22 ++ .../GpuIrOptimizationValidationResult.java | 41 +++ .../GpuIrOptimizationValidationStage.java | 9 + .../iroptimizer/GpuIrOptimizerModule.java | 15 + .../GpuIrProposalRuntimeBridgePass.java | 205 +++++++++++ ...rTextCanonicalizationProposalProvider.java | 152 ++++++++ ...uIrVendorOptimizationProposalProvider.java | 53 +++ ...uIrVendorOptimizationProposalRegistry.java | 104 ++++++ ...puIrVendorOptimizationProposalRequest.java | 101 ++++++ ...ptimizer.GpuIrOptimizationProposalProvider | 4 + ...er.GpuIrVendorOptimizationProposalProvider | 1 + ...togpu.runtime.GpuRuntimeIrOptimizationPass | 1 + ...antFoldingPreviewProposalProviderTest.java | 175 ++++++++++ ...encyDeduplicationProposalProviderTest.java | 118 +++++++ .../GpuIrNoOpOptimizationPassTest.java | 87 +++++ ...GpuIrOptimizationApprovalManifestTest.java | 178 ++++++++++ ...mizationApprovalTemplateFormatterTest.java | 134 +++++++ .../GpuIrOptimizationPolicyTest.java | 106 ++++++ ...GpuIrOptimizationProposalRegistryTest.java | 70 ++++ .../GpuIrOptimizationProposalTest.java | 77 ++++ .../GpuIrOptimizationSandwichRunnerTest.java | 174 ++++++++++ .../GpuIrProposalRuntimeBridgePassTest.java | 198 +++++++++++ ...tCanonicalizationProposalProviderTest.java | 115 ++++++ ...endorOptimizationProposalRegistryTest.java | 183 ++++++++++ ir-vendor-optimizer/build.gradle | 185 ++++++++++ ...oOpVendorOptimizationProposalProvider.java | 54 +++ .../GpuIrVendorOptimizerModule.java | 13 + ...er.GpuIrVendorOptimizationProposalProvider | 1 + .../GpuIrVendorOptimizerModuleTest.java | 139 ++++++++ .../GpuRuntimeCompileArtifactDumper.java | 166 +++++++++ ...enClRuntimeIrOptimizerEvidenceSummary.java | 328 ++++++++++++++++++ .../opencl/OpenClValidationReporter.java | 20 +- .../GpuRuntimeCompileArtifactDumperTest.java | 117 +++++++ ...RuntimeIrOptimizerEvidenceSummaryTest.java | 81 +++++ .../opencl/OpenClValidationReportTest.java | 29 +- settings.gradle | 2 + 61 files changed, 5365 insertions(+), 12 deletions(-) create mode 100644 docs/IR-Optimizer.md create mode 100644 ir-optimizer/build.gradle create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingPreviewProposalProvider.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrHelperDependencyDeduplicationProposalProvider.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrNoOpOptimizationPass.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrNoOpProposalProvider.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalManifest.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalTemplateFormatter.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalTemplateResult.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationPolicy.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposal.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalDecision.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalProvider.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalRegistry.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalRequest.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichReport.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichRunner.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichStatus.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationValidationGate.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationValidationRequest.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationValidationResult.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationValidationStage.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizerModule.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePass.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTextCanonicalizationProposalProvider.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrVendorOptimizationProposalProvider.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrVendorOptimizationProposalRegistry.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrVendorOptimizationProposalRequest.java create mode 100644 ir-optimizer/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrOptimizationProposalProvider create mode 100644 ir-optimizer/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrVendorOptimizationProposalProvider create mode 100644 ir-optimizer/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationPass create mode 100644 ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingPreviewProposalProviderTest.java create mode 100644 ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrHelperDependencyDeduplicationProposalProviderTest.java create mode 100644 ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrNoOpOptimizationPassTest.java create mode 100644 ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalManifestTest.java create mode 100644 ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalTemplateFormatterTest.java create mode 100644 ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationPolicyTest.java create mode 100644 ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalRegistryTest.java create mode 100644 ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalTest.java create mode 100644 ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichRunnerTest.java create mode 100644 ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePassTest.java create mode 100644 ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTextCanonicalizationProposalProviderTest.java create mode 100644 ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrVendorOptimizationProposalRegistryTest.java create mode 100644 ir-vendor-optimizer/build.gradle create mode 100644 ir-vendor-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/irvendoroptimizer/GpuIrNoOpVendorOptimizationProposalProvider.java create mode 100644 ir-vendor-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/irvendoroptimizer/GpuIrVendorOptimizerModule.java create mode 100644 ir-vendor-optimizer/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrVendorOptimizationProposalProvider create mode 100644 ir-vendor-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/irvendoroptimizer/GpuIrVendorOptimizerModuleTest.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java create mode 100644 processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java diff --git a/README.md b/README.md index f0b44bb0..88df0d14 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,8 @@ Advanced topics: - [Language Contract](docs/Language-Contract.md) - [OpenCL Data Model](docs/OpenCL-Data-Model.md) - [IR Validation](docs/IR-Validation.md) +- [IR Optimizer](docs/IR-Optimizer.md) +- IR Vendor Optimizer is documented in the IR Optimizer guide as a separate optional provider artifact. - [Validation and Operations](docs/Validation-and-Operations.md) - [Diagnostics Reference](docs/Diagnostics-Reference.md) - [ASM Contract](docs/ASM-Contract.md) @@ -138,6 +140,8 @@ Start with `validation-report.md` when checking a run. - `processor` - annotation processor, compiler, OpenCL emitter, runtime, launchers, tests, and validation buckets. - `ir-validation` - optional stricter IR validation module. +- `ir-optimizer` - optional backend-neutral IR optimizer skeleton and future transform module. +- `ir-vendor-optimizer` - optional vendor-specific IR optimizer provider skeleton; it plugs into the vendor proposal SPI and is not loaded by the default runtime optimizer bridge. - `examples-app` - example kernels and usage patterns. - `test-app` - consumer-style sample application. - `docs` - public documentation. @@ -149,9 +153,11 @@ Published artifacts: ```text io.github.deussixik:javatogpu io.github.deussixik:javatogpu-ir-validation +io.github.deussixik:javatogpu-ir-optimizer +io.github.deussixik:javatogpu-ir-vendor-optimizer ``` -Publishing is configured for the main processor artifact and the optional IR validation artifact. Keep Maven Central credentials and signing keys outside the repository. See [Publishing Guide](docs/Publishing.md). +Publishing is configured for the main processor artifact, optional IR validation artifact, optional backend-neutral IR optimizer artifact, and optional vendor optimizer provider artifact. Keep Maven Central credentials and signing keys outside the repository. See [Publishing Guide](docs/Publishing.md). ## License diff --git a/ROADMAP.md b/ROADMAP.md index 3ea7d72f..5a0bf075 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -598,8 +598,8 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i `GpuIrValidationProvider` remains a ServiceLoader SPI, while `GpuIrValidationReportEntry` now carries provider alias, extension id/version, rule id, severity, source anchor, method identity, entry-point state, and arbitrary provider fields. `GpuIrValidationRunner` automatically enriches legacy four-field entries with the actual provider metadata and Java/ASM source anchor, so downstream validators do not need to duplicate compiler location logic. `GpuCompilerProcessor` persists the enriched fields in the existing validation properties artifact without breaking the established provider alias contract. The built-in optional `ir-validation` provider now exposes stable id `javatogpu.ir-validation`, version `1`, aggregate rule id, and derived INFO/WARNING/ERROR severity. - [x] Allow third-party optimizer and peephole passes without core forks. External libraries can contribute full `GpuRuntimeIrOptimizationPass` implementations or typed-body `GpuRuntimeIrPeepholeRule` implementations through ServiceLoader. `GpuRuntimeIrPeepholeRuleRegistry` provides deterministic ordering, duplicate rule-id and extension-id rejection, phase/capability/permission validation, advisory failure isolation, and production-profile fail-closed behavior. Rule reports preserve rule id/version, extension id/version, method, candidate/proposal counts, proof status, diagnostics, and custom fields. The built-in MAD/FMA detector now uses the same public rule SPI. The current pass remains diagnostic/proposal-only: rules cannot bypass fast-math policy, proof requirements, production authorization, rollback, runtime-equivalence, or final IR selection gates. -- [ ] Add optional approval-bound IR optimizer modules. - Keep `GpuIrValidationProvider` strictly read-only and introduce future optional IR optimizer modules as separate `MUTATION_PROPOSAL` providers that projects may omit entirely. An optimizer module should emit proposal artifacts plus an approval-template manifest instead of silently changing `IrGpu`; the project or downstream JAR then opts in by packaging an explicit approval manifest under a stable resource path such as `META-INF/javatogpu/ir-optimization-approvals/`. Approval must be identity-bound to rule id/version, method identity, original `IrGpu` SHA-256, proposed optimized `IrGpu` SHA-256, backend target, proof/runtime-equivalence status, and rollback/fallback evidence. Missing approval, `enabled=false`, stale hashes, failed proof, or backend/device mismatch must leave the selected IR as original and record `optimization.status=not-applied`. This gives projects a Git-approval-style review boundary for optimizer adoption without weakening the existing validator contract or forcing optional optimization dependencies on users who do not need them. +- [x] Add optional approval-bound IR optimizer modules. + `ir-optimizer` and `ir-vendor-optimizer` now exist as optional modules that keep `GpuIrValidationProvider` strictly read-only and expose optimization only through separate `MUTATION_PROPOSAL` provider contracts. The backend-neutral runtime bridge records proposal/provider evidence without loading vendor providers by default, while vendor-specific providers must opt into `GpuIrVendorOptimizationProposalProvider` / `GpuIrVendorOptimizationProposalRegistry`. Optimizer proposal evidence now includes approval-template status: real immutable rewrites can emit pending templates under `META-INF/javatogpu/ir-optimization-approvals/`, while preview/no-change proposals record `not-applicable` blockers. Approval artifacts are bound to optimizer id/version, original and optimized `IrGpu` identities, backend target, optimization profile, proof verdict, and rollback requirement; they remain manual-review-only and do not authorize production mutation or selected-IR replacement by themselves. Runtime OpenCL validation now surfaces this evidence through `runtime-ir-optimizer-evidence.properties` and the `Runtime IR Optimizer Evidence` report section, including approval-template pending/not-applicable counts. - [x] Add the third-party backend/device policy foundation. `GpuRuntimeDevicePolicy` extensions can contribute deterministic ranking adjustments, candidate rejection, capability facts, vendor quirks, compile-option validation, and diagnostics without taking ownership of final device selection. `GpuRuntimeDevicePolicyRegistry` loads the built-in backend-compatibility policy plus ServiceLoader extensions, rejects duplicate policy/extension ids, validates the device-selection phase/capability/read-only permission contract, isolates advisory failures, fails closed in production profiles, and keeps final scoring, rejection precedence, tie-breaking, and blocker selection in core. `GpuBackendLowerer` now participates in the shared extension metadata contract through stable backend-lowerer id/version/capability/phase/permission fields. - [x] Add third-party backend compiler feedback providers. @@ -657,6 +657,33 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i - [ ] Phase 6: evidence-backed production optimization. Selected optimizer families can graduate only after A1/A2 evidence, backend-specific baselines, and rollback confidence are stable. +### I4. Optional IR optimizer module split + +Goal: turn the current read-only IR Validator evidence model into a clean optional optimization architecture without making validation mutable, forcing optimizer dependencies on normal users, or coupling backend-neutral rewrites to one vendor stack. + +Current rule: `ir-validation` remains strictly read-only. Optimizer modules may inspect validation evidence, run pre/post validation, and emit proposal/equivalence artifacts, but they must not mutate the original `IrGpu` artifact in place or silently replace the selected runtime IR without explicit opt-in policy and fail-closed evidence. + +- [x] Define the module boundary for optional IR optimization. + Keep `processor` as the owner of the canonical `IrGpu` model, artifact identity, runtime compile request, backend lowering handoff, and no-op default behavior. Keep `ir-validation` as the optional read-only validator module. Add `ir-optimizer` for backend-neutral transform contracts and general optimization passes. Keep vendor/device-specific optimization implementations out of that core module by default. +- [x] Split backend-neutral optimizer contracts from runtime-local prototype APIs. + `ir-optimizer` now exposes backend-neutral proposal contracts through `GpuIrOptimizationProposalProvider`, immutable proposal/request/report types, the validation sandwich, approval-template helpers, vendor-provider SPI, and `GpuIrOptimizationProposalRegistry`. The registry owns ServiceLoader loading, deterministic ordering, duplicate-id rejection, and backend-neutral provider discovery, while `GpuIrProposalRuntimeBridgePass` remains a thin runtime-local adapter to the existing `GpuRuntimeIrOptimizationPass` path. This preserves current runtime behavior when no optimizer module is present and keeps future optimizer authors on the optional proposal API instead of depending on runtime-local prototype registry details. +- [x] Add an immutable IR rewrite contract. + `GpuIrOptimizationProposal` now enforces the immutable rewrite boundary directly: `PROPOSED` decisions must carry a distinct optimized artifact instance with a different stable `IrGpu` identity, while unchanged or preview-only work must use `NO_CHANGE` / proposal evidence instead. Proposal requests carry the original artifact plus policy/context, and proposals preserve optimizer id/version, diagnostics, proof metadata, rollback reason, original identity, and optimized identity. This keeps validation, diffing, cache identity, approval templates, and production gates comparing original versus optimized artifacts instead of trusting in-place mutation. +- [x] Add the validation sandwich as the default optimizer safety model. + `GpuIrOptimizationSandwichRunner` now enforces the default optimizer safety flow: validate the original artifact, run one immutable proposal provider, validate the optimized artifact when present, then either keep the original, emit proposal-only evidence, select the optimized artifact only when mutation is explicitly allowed, or roll back on invalid optimized IR. The report records original/optimized validation results, final status, selected artifact, diagnostics, and rollback requirement. Existing tests cover no-change proposals, proposal-only mode, explicit mutation selection, invalid optimized rollback, invalid original short-circuiting, malformed proposal rejection, and ServiceLoader discovery. +- [x] Define backend-neutral optimizer policy controls. + `GpuIrOptimizationPolicy` now provides the backend-neutral optimizer control surface for optimizer profile, optimization level, proposal-only mode, mutation permission, fast-math permission, vendor adaptation, register-pressure splitting, rollback requirement, and proof requirement. `GpuIrOptimizationProposalRequest` preserves its existing constructors while exposing the normalized policy view and serializing policy fields into context metadata, so proposal providers and runtime adapters can consume one shared policy contract without depending on OpenCL-only flags or runtime-local prototype details. +- [x] Add first backend-neutral optimizer families only after the contract is stable. + `GpuIrTextCanonicalizationProposalProvider` handles semantics-neutral transitional text normalization, and `GpuIrHelperDependencyDeduplicationProposalProvider` now performs the first backend-neutral metadata cleanup by removing duplicate helper dependencies while preserving first-occurrence order and leaving body/typed/backend output data unchanged. More aggressive families such as constant folding, dead-code cleanup, safe local CSE, private-array scalarization, and typed-body peephole rewrites remain future proposal-only candidates until pre/post validation plus runtime-equivalence evidence are available. + `GpuIrConstantFoldingPreviewProposalProvider` now adds the first constant-folding proof surface by scanning typed IR for simple plain-decimal literal binary expressions and recording candidate, operator, numeric-kind, and first-fold evidence without proposing a rewrite. +- [x] Keep vendor optimization as a separate optional provider layer. + `ir-vendor-optimizer` now exists as a separate optional module skeleton. It contributes vendor proposals only through `GpuIrVendorOptimizationProposalProvider` and `GpuIrVendorOptimizationProposalRegistry`, while the default runtime bridge keeps loading only backend-neutral proposal providers. Future vendor modules may consume target device profile, backend compiler feedback, resource metrics, driver quirks, launch advisories, and promotion evidence, but missing modules, missing matching device evidence, or disabled vendor adaptation must leave backend-neutral IR selection unchanged. +- [x] Gate production optimizer selection through approval-bound artifacts. + `GpuIrOptimizationApprovalManifest` now writes and validates a review-only approval template for one immutable optimizer proposal. It is bound to optimizer id/version, original and optimized `IrGpu` identities, backend target, optimization profile, optional device vendor/label context, proof source/verdict, and rollback requirement, and is intended for `META-INF/javatogpu/ir-optimization-approvals/`. A valid manifest remains manual-review-only and does not select optimized IR or grant production mutation by itself; missing, stale, or mismatched approval keeps selection external and fail-closed. + `GpuIrOptimizationApprovalTemplateFormatter` now bridges proposal evidence to pending approval templates and explicitly returns `not-applicable` for preview/no-change proposals, making the approval boundary visible before applied rewrites exist. +- [x] Publish docs for optimizer authors and users before enabling real rewrites. + `docs/IR-Optimizer.md` now documents the validator/optimizer/vendor split, proposal-only runtime bridge behavior, backend-neutral and vendor ServiceLoader packaging, approval-template artifacts, approval manifest resource path, and safe default behavior. Runtime and validation docs now point operators to `runtime-ir-optimizer-evidence.properties` and explain that optimizer evidence is report-only: projects that do not add optimizer modules keep the current no-op runtime path, and approval templates do not enable production mutation or selected-IR replacement by themselves. + ## Recommended Execution Order If the goal is to move forward pragmatically from the current state, the best order is now: @@ -667,13 +694,15 @@ If the goal is to move forward pragmatically from the current state, the best or Treat any repeated NVIDIA/AMD failure, skipped workload, resource leak, or diagnostic gap as the next concrete implementation target. 3. `I3 Production IR pipeline foundation` Start with the non-mutating foundation: canonical `IrGpu` artifact, dual output beside current OpenCL source, OpenCL-from-`IrGpu` parity, runtime compile request/options, backend-lowering boundary, and source/ASM frontends feeding the same IR storage path. Keep runtime optimization profile `off` by default. -4. `A1 Intel runner bring-up when hardware exists` +4. `I4 Optional IR optimizer module split` + Before implementing real optimizer transforms, freeze the module boundary: validator stays read-only, backend-neutral optimizer contracts live behind optional dependencies, vendor optimizers are separate providers, and all mutation remains proposal/opt-in/fail-closed until proof and rollback are present. +5. `A1 Intel runner bring-up when hardware exists` The remaining cross-vendor production gate stays open until an Intel OpenCL stack can run the same bucket set. -5. `Improved ASM parser/frontend` +6. `Improved ASM parser/frontend` Broader ASM ingestion should lower into `IrGpu` first, then reuse the same runtime compile request, validation, backend lowering, and future optimizer path instead of growing a separate pipeline. The first broader-ASM diagnostics layer is already in place through `asmFailure.*` metadata and public preflight APIs. -6. `I3 prototype runtime optimization` +7. `I3/I4 prototype runtime optimization` After `IrGpu` storage and OpenCL parity are stable, add opt-in prototype optimization with rollback, pre/post runtime-equivalence artifacts, and NVIDIA/AMD evidence first. Keep production mutation disabled until A1/A2 confidence and the future Intel gate are satisfied. -7. `H Lower-priority backend/optimization work` +8. `H Lower-priority backend/optimization work` ## Current Working Conclusion @@ -681,7 +710,7 @@ JavaToGpu is already beyond the "toy compiler" stage. The broad repetitive intrinsic-family generation detour is now closed for current priorities, so the active focus returns to production-core/runtime validation rather than expanding optional API symmetry. -After the first serious dogfooding pass, the main repo-local language/runtime gaps for the selected workload classes are no longer the active blocker. Section C and F1 are closed for current practical workload coverage: 3D launch config, union-style packed views, root-blob ergonomics, launch-sensitive attributes, and the focused packed/root-blob dogfooding slice are all covered. Operational confidence now includes NVIDIA and AMD lanes; Intel remains the remaining hardware validation gap. The next major architecture frontier is the I3 Production IR pipeline: `IrGpu` should become the stored backend-neutral artifact, while OpenCL/CUDA/Vulkan/Metal become backend lowerers selected at runtime from compile options and device profile. Broader ASM ingestion should feed that same `IrGpu` path instead of creating a second compiler pipeline. +After the first serious dogfooding pass, the main repo-local language/runtime gaps for the selected workload classes are no longer the active blocker. Section C and F1 are closed for current practical workload coverage: 3D launch config, union-style packed views, root-blob ergonomics, launch-sensitive attributes, and the focused packed/root-blob dogfooding slice are all covered. Operational confidence now includes NVIDIA and AMD lanes; Intel remains the remaining hardware validation gap. The next major architecture frontier is the I3 Production IR pipeline plus the I4 optional optimizer split: `IrGpu` should become the stored backend-neutral artifact, OpenCL/CUDA/Vulkan/Metal should become backend lowerers selected at runtime from compile options and device profile, `ir-validation` must remain read-only, and future `ir-optimizer` / `ir-vendor-optimizer` modules should plug in as optional proposal/opt-in/fail-closed providers. Broader ASM ingestion should feed that same `IrGpu` path instead of creating a second compiler pipeline. Latest I2/L2 reconciliation: the current CI contract layer is closed for present priorities. Direct contract coverage now pins current readiness CI summaries, optimizer-gate consistency/acceptance, optimizer CI gate-index consistency/acceptance, regression/baseline CI summaries, nested artifact-field composition, and opt-in real-example dogfooding reports for `examples-app` / `test-app`. The dogfooding contract keeps safety fail-fast while treating optimizer readiness as a read-only stability artifact: production mutation must remain disabled, rewrite applicability must stay false, known CSE policy blockers are counted, and no-candidate CSE literal-promotion / auto-vectorization blockers are accepted as the current baseline. The remaining I2 items are intentionally broader frontier work: deeper transformation-safety proofs, stronger canonicalization/common-computation detection, runtime-equivalence evidence for selected optimizer families, and eventual production rewrite promotion after A1/A2 runtime confidence is stable. These should stay open until backed by runtime evidence rather than being marked complete from read-only artifact coverage alone. diff --git a/docs/Home.md b/docs/Home.md index c9ebd9e0..c6121942 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -31,6 +31,7 @@ This documentation is written for users of the current alpha. It starts with pra ## Advanced Topics - [IR Validation](IR-Validation.md) - optional strict IR checks and read-only optimizer diagnostics. +- [IR Optimizer](IR-Optimizer.md) - optional backend-neutral optimizer layer plus separate vendor-provider SPI. - [Diagnostics Reference](Diagnostics-Reference.md) - diagnostic categories and how to interpret them. - [Validation and Operations](Validation-and-Operations.md) - local validation routines and OpenCL evidence artifacts. - [ASM Contract](ASM-Contract.md) - bytecode input guidance for advanced integrations. diff --git a/docs/IR-Optimizer.md b/docs/IR-Optimizer.md new file mode 100644 index 00000000..41ecfc30 --- /dev/null +++ b/docs/IR-Optimizer.md @@ -0,0 +1,105 @@ +# IR Optimizer + +The optional IR optimizer module is the future backend-neutral transform layer for `IrGpu` artifacts. + +The current module is intentionally a skeleton. It contributes one ServiceLoader-discovered no-op pass that records optimizer participation without changing the selected IR. This keeps the default runtime behavior safe while establishing the module, packaging, and report boundary for future optimization families. + +## Contract + +- `ir-validation` remains read-only and must not mutate `IrGpu`. +- `ir-optimizer` owns backend-neutral optimizer contracts and general passes. +- Vendor/device-specific optimizers live behind `GpuIrVendorOptimizationProposalProvider` and are loaded explicitly through `GpuIrVendorOptimizationProposalRegistry`, not through the default backend-neutral proposal bridge. +- Optimizer passes must not mutate the input artifact in place. +- Real rewrites must return a distinct optimized artifact, diagnostics, proof links, and rollback evidence. +- Missing optimizer modules, missing approval, failed proof, or disabled optimization must leave the original IR selected. + +## Current Skeleton + +`GpuIrNoOpOptimizationPass` reports a skipped optimizer pass at candidate-discovery stage. It proves that projects can add the optional optimizer module without enabling production mutation or changing generated backend source. + +The first real proposal provider is `GpuIrTextCanonicalizationProposalProvider`. It only canonicalizes transitional IR text bodies by normalizing line endings and stripping trailing spaces or tabs. It preserves typed IR bodies, backend outputs, metadata, and the original artifact object. By default it remains proposal-only; the validation sandwich selects the canonicalized artifact only when mutation is explicitly allowed. + +`GpuIrHelperDependencyDeduplicationProposalProvider` is the first backend-neutral metadata cleanup pass. It removes duplicate helper-dependency entries from method bodies while preserving first-occurrence order and leaving text bodies, typed bodies, backend outputs, and the original artifact unchanged. It is proposal-only by default and exists to prove the real rewrite/proposal path without changing kernel semantics. + +`GpuIrConstantFoldingPreviewProposalProvider` is a preview-only proof surface for future typed constant folding. It scans typed IR for simple binary expressions with plain decimal literal operands, records candidate counts, operator breakdown, numeric-kind breakdown, and the first folded value in proof fields, and always returns `NO_CHANGE`. It deliberately does not produce an optimized artifact, even when mutation is allowed. + +## Immutable Proposal Flow + +The optimizer contract is intentionally split into proposal and selection: + +1. Build a `GpuIrOptimizationProposalRequest` from the original `IrGpu` artifact and `GpuIrOptimizationPolicy` context. +2. Run a `GpuIrOptimizationProposalProvider` that returns `NO_CHANGE`, `REJECTED`, or `PROPOSED`. +3. Run the validation sandwich through `GpuIrOptimizationSandwichRunner`. +4. Select the optimized artifact only when the original artifact validates, the optimized artifact validates, and mutation is explicitly allowed. + +If mutation is disabled, a valid optimized artifact is reported as `PROPOSAL_ONLY` and the original IR remains selected. If post-validation fails, the runner reports `OPTIMIZED_INVALID_ROLLED_BACK` and keeps the original IR. + +## Policy Controls + +`GpuIrOptimizationPolicy` is the backend-neutral control surface for optimizer providers and runtime adapters. It currently covers: + +- `optimizerProfile` - high-level profile such as `off`, `diagnostic`, or future production profiles. +- `optimizationLevel` - shared optimization intensity hint independent of OpenCL/CUDA/Vulkan/Metal flags. +- `proposalOnly` - forces evidence collection without optimized IR selection. +- `mutationAllowed` - explicit opt-in required before the validation sandwich can select an optimized artifact. +- `fastMathAllowed` - explicit permission for future math-reassociation or precision-sensitive rewrites. +- `vendorAdaptationAllowed` - explicit permission for vendor/device-specific proposals to participate. +- `registerPressureSplittingAllowed` - explicit permission for future register-pressure/resource-shaping rewrites. +- `rollbackRequired` - records whether a proposal must provide rollback/fallback evidence. +- `proofRequired` - records whether proof/runtime-equivalence evidence is required for the proposal class. + +`GpuIrOptimizationProposalRequest` preserves the older constructor shape for compatibility, but it now exposes a normalized `policy()` view and serializes `policy.*` fields into context metadata. Provider authors should read the policy instead of inventing backend-specific context keys. Defaults are fail-closed: proposal-only, mutation disabled, fast math disabled, vendor adaptation disabled, register-pressure splitting disabled, rollback required, and proof required. + +## Runtime Bridge + +`GpuIrOptimizationProposalRegistry` owns backend-neutral provider discovery. It loads `GpuIrOptimizationProposalProvider` implementations through ServiceLoader, orders providers deterministically, and rejects duplicate extension ids before runtime adaptation. + +`GpuIrProposalRuntimeBridgePass` is the runtime adapter between the existing runtime optimizer pipeline and the immutable proposal-provider contract. It consumes providers from `GpuIrOptimizationProposalRegistry`, runs each provider through the validation sandwich, and reports the result as a runtime optimizer pass report. + +The default bridge is intentionally proposal-only. It can surface canonicalization proposals and proof metadata in runtime optimizer reports, but it does not select optimized IR unless constructed with explicit mutation permission. This lets `ir-optimizer` participate in runtime evidence collection without enabling production mutation. + +Runtime artifact dumping records bridge/provider evidence in `runtime-ir-optimizer-evidence.properties`. The artifact is filtered by the stable `javatogpu.ir-optimizer` provider prefix and includes pass counts, outcomes, proposal-only/selected counts, original/transformed IR identities, proof artifact fields, and diagnostics. This is an evidence surface only; it does not authorize optimized IR selection or production mutation by itself. + +The same evidence artifact now records `approvalTemplate.*` fields for each optimizer pass. Real proposal rewrites with distinct original/optimized identities and accepted proof metadata are marked `pending`; preview and no-change proposals are marked `not-applicable` with a first blocker such as `proposal-decision-not-proposed`. + +## Vendor Optimizer SPI + +`GpuIrVendorOptimizationProposalProvider` is the optional vendor/device-specific contract. It receives a `GpuIrVendorOptimizationProposalRequest` with backend target, vendor, device id/label, driver/API text, device class, optimizer profile, mutation flag, and all original context fields. The provider still returns the same immutable `GpuIrOptimizationProposal`, so vendor proposals go through the same validation sandwich and rollback semantics as backend-neutral proposals. + +`GpuIrVendorOptimizationProposalRegistry` loads vendor providers deterministically, rejects duplicate extension ids, and exposes an adapter back to `GpuIrOptimizationProposalProvider`. The default runtime bridge does not auto-load vendor providers; projects or future vendor modules must opt into that registry explicitly. This keeps `ir-optimizer` backend-neutral while leaving room for a separate `ir-vendor-optimizer` artifact. + +## Packaging External Providers + +Backend-neutral optimizer providers should depend on `javatogpu-ir-optimizer`, implement `GpuIrOptimizationProposalProvider`, and register the implementation in: + +```text +META-INF/services/net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrOptimizationProposalProvider +``` + +Vendor/device-specific optimizer providers should depend on `javatogpu-ir-vendor-optimizer` or `javatogpu-ir-optimizer`, implement `GpuIrVendorOptimizationProposalProvider`, and register the implementation in: + +```text +META-INF/services/net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrVendorOptimizationProposalProvider +``` + +Do not register a vendor provider in the backend-neutral provider descriptor unless it is intentionally safe for every backend/device context. The default runtime bridge loads only backend-neutral proposal providers. Vendor providers must be loaded explicitly through `GpuIrVendorOptimizationProposalRegistry`, adapted into the common proposal contract, and then passed through the same validation sandwich. + +Provider authors must expose stable `extensionId`, `extensionVersion`, and `extensionOrder` values. Duplicate ids are rejected by the registry. A provider that cannot prove a safe rewrite should return `NO_CHANGE` or `REJECTED`; it should not return a proposed artifact without proof fields, diagnostics, and rollback/equivalence evidence. + +## Approval Manifest + +`GpuIrOptimizationApprovalManifest` writes a review-only approval template for one immutable optimizer proposal. The template is bound to the optimizer id/version, original and optimized `IrGpu` identities, backend target, optimization profile, optional device vendor/label context, proof source/verdict, and rollback requirement. It is intended for packaging under: + +```text +META-INF/javatogpu/ir-optimization-approvals/ +``` + +Validation requires `status=approved`, explicit approval id/author/timestamp, matching proposal identities, matching backend/device context, matching proof metadata, `binding.rollback.required=true`, and `authorization.productionMutation=disabled`. A valid manifest is still `manual-review-only`: it proves that the proposal was reviewed, but it does not select optimized IR or grant production mutation by itself. + +`GpuIrOptimizationApprovalTemplateFormatter` is the bridge between proposal evidence and approval artifacts. It returns a pending template only for a real `PROPOSED` rewrite with distinct original/optimized identities and accepted proof metadata. Preview or `NO_CHANGE` proposals return `not-applicable` with a first blocker such as `proposal-decision-not-proposed`, so report tooling can expose why no approval template should be packaged yet. + +## Read Next + +- [IR Validation](IR-Validation.md) +- [Runtime Guide](Runtime-Guide.md) +- [Public API and Extension Contract](Public-API-And-Extension-Contract.md) diff --git a/docs/Runtime-Guide.md b/docs/Runtime-Guide.md index 58fd921e..9259d9bd 100644 --- a/docs/Runtime-Guide.md +++ b/docs/Runtime-Guide.md @@ -233,6 +233,8 @@ OpenCL isolated runtime-equivalence checks write raw pipeline comparison cases i Non-analysis optimizer passes with runtime-equivalence payload evidence also produce `runtime-optimizer-family-equivalence-payload.properties` plus durable files under `runtime-optimizer-family-equivalence-payload/family--/pass-/`. Each pass directory contains `manifest.properties`, CPU/reference, pre-optimization, post-optimization, tolerance, failure-fixture, and diagnostics properties files. When proof fields contain structured case evidence, the component files include deterministic indexed fields for case names, inputs, raw outputs, tolerances, equivalence flags, and diagnostics. Missing components are materialized with `status=not-recorded`; they are never inferred from another pass. Artifact paths are normalized and must remain inside the kernel's `runtime-compile-artifacts` directory. These files are diagnostic evidence only and cannot enable source switching or production mutation. +When the optional IR optimizer bridge participates, each kernel directory may also receive `runtime-ir-optimizer-evidence.properties`. The artifact records only proposal/provider evidence from stable `javatogpu.ir-optimizer` sources: pass counts, proposal-only versus selected optimized counts, rollback counts, IR identities, proof fields, approval-template applicability, and diagnostics. `openClValidationReport` aggregates this into `Runtime IR Optimizer Evidence` for CI visibility. The section is evidence-only and does not participate in history, source switching, or production mutation gates. + The same index records `familyBinding.*`. Binding remains `not-bound` unless exactly one optimizer family has passed runtime evidence whose comparison mode is explicitly family-specific (`optimizer-family::...`). Descriptor-versus-reconstructed-source evidence is pipeline validation and is deliberately rejected as optimizer-family proof. Run `./gradlew :processor:openClOptimizerFamilyPayloadFixtureTest` to materialize the CI contract fixture under `processor/build/reports/opencl/optimizer-family-payload-fixture/`. The fixture contains complete CSE and auto-vectorization payload trees plus `fixture-summary.properties`, including one structured raw case per family. It validates artifact layout and upload behavior, not production rewrite readiness. diff --git a/docs/Validation-and-Operations.md b/docs/Validation-and-Operations.md index 285ea56f..b8399685 100644 --- a/docs/Validation-and-Operations.md +++ b/docs/Validation-and-Operations.md @@ -83,6 +83,7 @@ processor/build/reports/opencl/backend-source-promotion-activation-gate.properti processor/build/reports/opencl/backend-source-promotion-activation-gate.properties.sha256 processor/build/reports/opencl/production-activation-token-smoke.properties processor/build/reports/opencl/production-activation-token-negative.properties +processor/build/reports/opencl/runtime-compile-artifacts/**/runtime-ir-optimizer-evidence.properties processor/build/reports/opencl/runtime-compile-artifacts/**/runtime-optimizer-family-equivalence-payload/ processor/build/reports/opencl/optimizer-family-payload-fixture/ processor/build/test-results/ diff --git a/docs/_Sidebar.md b/docs/_Sidebar.md index 890b0895..dbfd179c 100644 --- a/docs/_Sidebar.md +++ b/docs/_Sidebar.md @@ -11,6 +11,7 @@ - [Language Contract](Language-Contract.md) - [OpenCL Data Model](OpenCL-Data-Model.md) - [IR Validation](IR-Validation.md) +- [IR Optimizer](IR-Optimizer.md) - [Validation and Operations](Validation-and-Operations.md) - [ASM Contract](ASM-Contract.md) - [OpenCL Runner Contract](OpenCL-Runner-Contract.md) diff --git a/ir-optimizer/build.gradle b/ir-optimizer/build.gradle new file mode 100644 index 00000000..c5cbe971 --- /dev/null +++ b/ir-optimizer/build.gradle @@ -0,0 +1,185 @@ +plugins { + id 'java-library' + id 'maven-publish' + id 'signing' +} + +import groovy.xml.XmlSlurper + +description = 'Optional JavaToGpu IR optimizer pass skeleton and backend-neutral optimizer contracts.' +group = rootProject.group +version = rootProject.version + +def secretValue = { String gradlePropertyName, String environmentVariableName -> + providers.gradleProperty(gradlePropertyName) + .orElse(providers.environmentVariable(environmentVariableName)) +} + +def centralUsernameProvider = secretValue('mavenCentralUsername', 'MAVEN_CENTRAL_USERNAME') +def centralPasswordProvider = secretValue('mavenCentralPassword', 'MAVEN_CENTRAL_PASSWORD') +def signingKeyProvider = secretValue('signingInMemoryKey', 'SIGNING_IN_MEMORY_KEY') +def signingPasswordProvider = secretValue('signingInMemoryKeyPassword', 'SIGNING_IN_MEMORY_KEY_PASSWORD') +def publicationArtifactId = findProperty('javatogpu.irOptimizerArtifactId') ?: 'javatogpu-ir-optimizer' + +java { + withSourcesJar() + withJavadocJar() +} + +repositories { + mavenCentral() +} + +dependencies { + api project(':processor') + testImplementation platform('org.junit:junit-bom:5.10.0') + testImplementation 'org.junit.jupiter:junit-jupiter' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' +} + +tasks.withType(Javadoc).configureEach { + options.addStringOption('Xdoclint:none', '-quiet') +} + +test { + useJUnitPlatform() +} + +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + artifactId = publicationArtifactId + + pom { + name = 'JavaToGpu IR Optimizer' + description = project.description + url = 'https://github.com/Team-Argentum/JavaToGpu' + + licenses { + license { + name = 'GNU Lesser General Public License v3.0' + url = 'https://www.gnu.org/licenses/lgpl-3.0.en.html' + distribution = 'repo' + } + } + + developers { + developer { + id = 'Team-Argentum' + name = 'Team Argentum' + organization = 'Team Argentum' + organizationUrl = 'https://github.com/Team-Argentum' + } + } + + scm { + connection = 'scm:git:https://github.com/Team-Argentum/JavaToGpu.git' + developerConnection = 'scm:git:https://github.com/Team-Argentum/JavaToGpu.git' + url = 'https://github.com/Team-Argentum/JavaToGpu' + } + + withXml { + def dependenciesNode = asNode().dependencies + if (dependenciesNode) { + dependenciesNode[0].dependency.findAll { dependency -> + dependency.groupId.text() == project.group.toString() + && dependency.artifactId.text() == 'processor' + }.each { dependency -> + dependency.artifactId[0].setValue('javatogpu') + } + } + } + } + } + } + + repositories { + maven { + name = 'localStaging' + url = layout.buildDirectory.dir('maven-staging') + } + maven { + name = 'centralSnapshots' + url = uri('https://central.sonatype.com/repository/maven-snapshots/') + credentials { + username = centralUsernameProvider.getOrElse('') + password = centralPasswordProvider.getOrElse('') + } + } + maven { + name = 'centralReleases' + url = uri('https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/') + credentials { + username = centralUsernameProvider.getOrElse('') + password = centralPasswordProvider.getOrElse('') + } + } + } +} + +tasks.withType(GenerateModuleMetadata).configureEach { + enabled = false +} + +tasks.register('validateMavenCentralReleaseReadiness') { + group = 'publishing' + description = 'Fails release publishing if the generated Maven POM contains dependencies Maven Central should not release.' + dependsOn tasks.named('generatePomFileForMavenJavaPublication') + doLast { + if (version.toString().endsWith('-SNAPSHOT')) { + return + } + File pomFile = layout.buildDirectory.file('publications/mavenJava/pom-default.xml').get().asFile + if (!pomFile.isFile()) { + throw new GradleException('Maven Central release is not ready: generated POM is missing at ' + pomFile) + } + + def pom = new XmlSlurper(false, false).parse(pomFile) + java.util.List unsafeDependencies = [] + pom.dependencies.dependency.each { dependency -> + String groupId = dependency.groupId.text() + String artifactId = dependency.artifactId.text() + String versionText = dependency.version.text() + String coordinates = groupId + ':' + artifactId + ':' + (versionText.isBlank() ? '' : versionText) + String normalizedVersion = versionText.toLowerCase(Locale.ROOT) + + if (normalizedVersion.contains('snapshot')) { + unsafeDependencies.add(coordinates + ' (snapshot dependency)') + } else if (groupId.startsWith('com.github.') && normalizedVersion.contains('master')) { + unsafeDependencies.add(coordinates + ' (JitPack branch dependency)') + } + } + + if (!unsafeDependencies.isEmpty()) { + throw new GradleException('Maven Central release is not ready: generated POM contains non-release-safe dependencies: ' + + unsafeDependencies.join(', ')) + } + } +} + +tasks.matching { task -> + task.name == 'publishMavenJavaPublicationToCentralReleasesRepository' + || task.name == 'publishAllPublicationsToCentralReleasesRepository' +}.configureEach { + dependsOn tasks.named('validateMavenCentralReleaseReadiness') +} + +signing { + def signingKey = signingKeyProvider.orNull + def signingPassword = signingPasswordProvider.orNull + required { + !version.toString().endsWith('-SNAPSHOT') && gradle.taskGraph.allTasks.any { task -> + task.name.toLowerCase(Locale.ROOT).contains('central') + || task.name == 'publish' + } + } + if (signingKey != null && !signingKey.isBlank()) { + useInMemoryPgpKeys(signingKey, signingPassword) + } + sign publishing.publications.mavenJava +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingPreviewProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingPreviewProposalProvider.java new file mode 100644 index 00000000..ac6c386d --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingPreviewProposalProvider.java @@ -0,0 +1,259 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; + +import java.math.BigDecimal; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * Preview-only constant-folding analysis for simple typed literal binary expressions. + */ +public final class GpuIrConstantFoldingPreviewProposalProvider implements GpuIrOptimizationProposalProvider { + + public static final String PROVIDER_ID = GpuIrOptimizerModule.MODULE_ID + ".constant-folding-preview"; + public static final String PROVIDER_VERSION = PROVIDER_ID + ":1"; + + @Override + public GpuIrOptimizationProposal propose(GpuIrOptimizationProposalRequest request) { + IrGpuArtifact original = request.originalArtifact(); + Preview preview = Preview.from(original); + return new GpuIrOptimizationProposal( + extensionId(), + extensionVersion(), + original, + Optional.empty(), + GpuIrOptimizationProposalDecision.NO_CHANGE, + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.constant-folding-preview", + preview.verdict(), + preview.fields() + ), + "", + List.of(preview.diagnostic()) + ); + } + + @Override + public String extensionId() { + return PROVIDER_ID; + } + + @Override + public String extensionVersion() { + return PROVIDER_VERSION; + } + + @Override + public int extensionOrder() { + return 400; + } + + private record Preview( + int methodBodyCount, + int typedBodyCount, + int candidateCount, + Map operatorCounts, + Map numericKindCounts, + String firstCandidate, + String firstOperator, + String firstNumericKind, + String firstFoldedValue + ) { + + private static Preview from(IrGpuArtifact artifact) { + int methodBodyCount = artifact.module().methodBodies().size(); + int typedBodyCount = 0; + int candidateCount = 0; + LinkedHashMap operatorCounts = new LinkedHashMap<>(); + LinkedHashMap numericKindCounts = new LinkedHashMap<>(); + String firstCandidate = "none"; + String firstOperator = "none"; + String firstNumericKind = "none"; + String firstFoldedValue = "none"; + for (IrGpuMethodBody methodBody : artifact.module().methodBodies()) { + if (!methodBody.typedBody().available()) { + continue; + } + typedBodyCount++; + Map nodesById = nodesById(methodBody); + for (IrGpuTypedNode node : methodBody.typedBody().nodes()) { + Optional candidate = foldCandidate(methodBody.name(), node, nodesById); + if (candidate.isEmpty()) { + continue; + } + candidateCount++; + operatorCounts.merge(candidate.get().operator(), 1, Integer::sum); + numericKindCounts.merge(candidate.get().numericKind(), 1, Integer::sum); + if ("none".equals(firstCandidate)) { + firstCandidate = candidate.get().summary(); + firstOperator = candidate.get().operator(); + firstNumericKind = candidate.get().numericKind(); + firstFoldedValue = candidate.get().foldedValue(); + } + } + } + return new Preview( + methodBodyCount, + typedBodyCount, + candidateCount, + Map.copyOf(operatorCounts), + Map.copyOf(numericKindCounts), + firstCandidate, + firstOperator, + firstNumericKind, + firstFoldedValue + ); + } + + private String verdict() { + return candidateCount == 0 ? "preview-no-candidates" : "preview-candidates-recorded"; + } + + private String diagnostic() { + return candidateCount == 0 + ? "constant folding preview found no simple typed literal binary candidates" + : "constant folding preview recorded " + candidateCount + " simple typed literal binary candidate(s); no rewrite was proposed"; + } + + private Map fields() { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("methodBody.count", Integer.toString(methodBodyCount)); + fields.put("typedBody.count", Integer.toString(typedBodyCount)); + fields.put("candidate.count", Integer.toString(candidateCount)); + fields.put("rewrite.proposed", "false"); + fields.put("mutationRequired", "false"); + fields.put("productionAffecting", "false"); + fields.put("previewOnly", "true"); + fields.put("operator.counts", countsSummary(operatorCounts)); + fields.put("numericKind.counts", countsSummary(numericKindCounts)); + fields.put("firstCandidate", firstCandidate); + fields.put("firstOperator", firstOperator); + fields.put("firstNumericKind", firstNumericKind); + fields.put("firstFoldedValue", firstFoldedValue); + fields.put("firstBlocker", candidateCount == 0 ? "no-simple-literal-binary-candidates" : "preview-only-no-rewrite"); + return Map.copyOf(fields); + } + + private static String countsSummary(Map counts) { + if (counts == null || counts.isEmpty()) { + return "{}"; + } + return counts.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .map(entry -> entry.getKey() + "=" + entry.getValue()) + .collect(Collectors.joining(",", "{", "}")); + } + } + + private record FoldCandidate( + String methodName, + int nodeId, + String operator, + String left, + String right, + String numericKind, + String foldedValue + ) { + private String summary() { + return methodName + "#" + nodeId + "=" + left + operator + right; + } + } + + private static Map nodesById(IrGpuMethodBody methodBody) { + LinkedHashMap nodes = new LinkedHashMap<>(); + for (IrGpuTypedNode node : methodBody.typedBody().nodes()) { + nodes.put(node.id(), node); + } + return Map.copyOf(nodes); + } + + private static Optional foldCandidate( + String methodName, + IrGpuTypedNode node, + Map nodesById + ) { + if (!"GpuIrBinary".equals(node.kind())) { + return Optional.empty(); + } + String operator = node.attributes().getOrDefault("operator", ""); + if (!List.of("+", "-", "*", "/").contains(operator)) { + return Optional.empty(); + } + Optional left = literalChild(node, "left", nodesById); + Optional right = literalChild(node, "right", nodesById); + if (left.isEmpty() || right.isEmpty()) { + return Optional.empty(); + } + Optional folded = fold(operator, left.get(), right.get()); + String numericKind = numericKind(left.get(), right.get()); + return folded.map(value -> new FoldCandidate( + methodName, + node.id(), + operator, + left.get(), + right.get(), + numericKind, + value + )); + } + + private static Optional literalChild( + IrGpuTypedNode node, + String childName, + Map nodesById + ) { + List childIds = node.children().getOrDefault(childName, List.of()); + if (childIds.size() != 1) { + return Optional.empty(); + } + IrGpuTypedNode child = nodesById.get(childIds.get(0)); + if (child == null || !"GpuIrLiteral".equals(child.kind())) { + return Optional.empty(); + } + return Optional.ofNullable(child.attributes().get("sourceText")) + .filter(value -> !value.isBlank()) + .filter(GpuIrConstantFoldingPreviewProposalProvider::isPlainDecimalLiteral); + } + + private static Optional fold(String operator, String left, String right) { + BigDecimal leftValue = new BigDecimal(left); + BigDecimal rightValue = new BigDecimal(right); + if ("/".equals(operator) && BigDecimal.ZERO.compareTo(rightValue) == 0) { + return Optional.empty(); + } + BigDecimal folded = switch (operator) { + case "+" -> leftValue.add(rightValue); + case "-" -> leftValue.subtract(rightValue); + case "*" -> leftValue.multiply(rightValue); + case "/" -> dividesEvenly(leftValue, rightValue) + ? leftValue.divide(rightValue) + : null; + default -> null; + }; + return folded == null ? Optional.empty() : Optional.of(folded.stripTrailingZeros().toPlainString()); + } + + private static boolean dividesEvenly(BigDecimal left, BigDecimal right) { + try { + left.divide(right); + return true; + } catch (ArithmeticException exception) { + return false; + } + } + + private static boolean isPlainDecimalLiteral(String value) { + return value.matches("[-+]?(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?"); + } + + private static String numericKind(String left, String right) { + return left.contains(".") || right.contains(".") ? "decimal" : "integer"; + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrHelperDependencyDeduplicationProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrHelperDependencyDeduplicationProposalProvider.java new file mode 100644 index 00000000..864722c2 --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrHelperDependencyDeduplicationProposalProvider.java @@ -0,0 +1,146 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; + +/** + * Semantics-neutral metadata cleanup pass that removes duplicate helper dependencies. + */ +public final class GpuIrHelperDependencyDeduplicationProposalProvider implements GpuIrOptimizationProposalProvider { + + public static final String PROVIDER_ID = GpuIrOptimizerModule.MODULE_ID + ".helper-dependency-deduplication"; + public static final String PROVIDER_VERSION = PROVIDER_ID + ":1"; + + @Override + public GpuIrOptimizationProposal propose(GpuIrOptimizationProposalRequest request) { + IrGpuArtifact original = request.originalArtifact(); + ArrayList rewrittenBodies = new ArrayList<>(); + int changedBodies = 0; + int removedDependencies = 0; + + for (IrGpuMethodBody methodBody : original.module().methodBodies()) { + List deduplicated = deduplicate(methodBody.helperDependencies()); + if (!deduplicated.equals(methodBody.helperDependencies())) { + changedBodies++; + removedDependencies += methodBody.helperDependencies().size() - deduplicated.size(); + rewrittenBodies.add(copyWithHelperDependencies(methodBody, deduplicated)); + } else { + rewrittenBodies.add(methodBody); + } + } + + if (changedBodies == 0) { + return GpuIrOptimizationProposal.noChange( + extensionId(), + extensionVersion(), + original, + "IR helper dependency metadata is already deduplicated" + ); + } + + IrGpuArtifact optimized = copyWithModule(original, copyWithBodies(original.module(), rewrittenBodies)); + return GpuIrOptimizationProposal.proposed( + extensionId(), + extensionVersion(), + original, + optimized, + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.helper-dependency-deduplication", + "semantics-neutral-metadata-cleanup", + Map.of( + "changedMethodBodies", Integer.toString(changedBodies), + "removedDuplicateDependencies", Integer.toString(removedDependencies), + "mutationRequired", "false", + "preservedOrder", "first-occurrence" + ) + ), + List.of("deduplicated helper dependency metadata in " + changedBodies + " method body/bodies") + ); + } + + @Override + public String extensionId() { + return PROVIDER_ID; + } + + @Override + public String extensionVersion() { + return PROVIDER_VERSION; + } + + @Override + public int extensionOrder() { + return 300; + } + + static List deduplicate(List helperDependencies) { + if (helperDependencies == null || helperDependencies.isEmpty()) { + return List.of(); + } + LinkedHashSet unique = new LinkedHashSet<>(); + for (String dependency : helperDependencies) { + if (dependency != null && !dependency.isBlank()) { + unique.add(dependency); + } + } + return List.copyOf(unique); + } + + private static IrGpuMethodBody copyWithHelperDependencies( + IrGpuMethodBody methodBody, + List helperDependencies + ) { + return new IrGpuMethodBody( + methodBody.role(), + methodBody.name(), + methodBody.emittedName(), + methodBody.format(), + methodBody.body(), + methodBody.typedBody(), + methodBody.bodyIndex(), + helperDependencies, + methodBody.sourceLocation() + ); + } + + private static IrGpuModule copyWithBodies(IrGpuModule module, List methodBodies) { + return new IrGpuModule( + module.entryMethod(), + module.entryEmittedName(), + module.entryOpenClAttributes(), + module.entryAttributeMetadata(), + module.helperMethods(), + module.structs(), + methodBodies + ); + } + + private static IrGpuArtifact copyWithModule(IrGpuArtifact artifact, IrGpuModule module) { + return new IrGpuArtifact( + artifact.header(), + module, + artifact.entryParameters(), + artifact.launchMetadata(), + artifact.validationMetadata(), + artifact.featureMetadata(), + artifact.optimizerPolicyMetadata(), + artifact.regenerationMetadata(), + artifact.structMetadata(), + artifact.constants(), + artifact.constantData(), + artifact.backendOutputs(), + artifact.runtimeDefaultBackend(), + artifact.runtimeOptimizationProfile(), + artifact.methodDeviceConstraints(), + artifact.methodFallbackVariants(), + artifact.extensionParticipationMetadata() + ); + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrNoOpOptimizationPass.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrNoOpOptimizationPass.java new file mode 100644 index 00000000..9cd744ef --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrNoOpOptimizationPass.java @@ -0,0 +1,50 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactIdentity; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationPass; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationPassReport; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationReport; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationRequest; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationStage; + +import java.util.List; + +/** + * Safe skeleton pass proving that the optional optimizer module can participate without mutating IR. + */ +public final class GpuIrNoOpOptimizationPass implements GpuRuntimeIrOptimizationPass { + + public static final String PASS_ID = GpuIrOptimizerModule.MODULE_ID + ".noop"; + public static final String PASS_VERSION = GpuIrOptimizerModule.MODULE_VERSION; + + @Override + public GpuRuntimeIrOptimizationReport run(GpuRuntimeIrOptimizationRequest request) { + String identity = IrGpuArtifactIdentity.stableIdentity(request.artifact()); + GpuRuntimeIrOptimizationPassReport passReport = GpuRuntimeIrOptimizationPassReport.skipped( + passVersion(), + identity, + "optional ir-optimizer skeleton pass is proposal-only and does not mutate IR" + ).withStage(stage()); + return new GpuRuntimeIrOptimizationReport(request.artifact(), List.of(passReport)); + } + + @Override + public GpuRuntimeIrOptimizationStage stage() { + return GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY; + } + + @Override + public String passName() { + return PASS_ID; + } + + @Override + public String passVersion() { + return PASS_ID + ":" + PASS_VERSION; + } + + @Override + public int extensionOrder() { + return 100; + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrNoOpProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrNoOpProposalProvider.java new file mode 100644 index 00000000..17a57ec3 --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrNoOpProposalProvider.java @@ -0,0 +1,27 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +/** + * Backend-neutral no-op proposal provider used to pin the initial immutable optimizer contract. + */ +public final class GpuIrNoOpProposalProvider implements GpuIrOptimizationProposalProvider { + + @Override + public GpuIrOptimizationProposal propose(GpuIrOptimizationProposalRequest request) { + return GpuIrOptimizationProposal.noChange( + extensionId(), + extensionVersion(), + request.originalArtifact(), + "no-op proposal provider keeps original IR selected" + ); + } + + @Override + public String extensionId() { + return GpuIrNoOpOptimizationPass.PASS_ID; + } + + @Override + public String extensionVersion() { + return GpuIrNoOpOptimizationPass.PASS_ID + ":" + GpuIrNoOpOptimizationPass.PASS_VERSION; + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalManifest.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalManifest.java new file mode 100644 index 00000000..e3f9a02e --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalManifest.java @@ -0,0 +1,300 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; + +import java.time.Instant; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +/** + * Manual approval template and validator for one immutable optimizer proposal. + * + *

This artifact is deliberately review-only. A valid manifest proves that an operator reviewed a specific + * optimizer proposal identity, but it does not select optimized IR or grant production mutation by itself.

+ */ +public final class GpuIrOptimizationApprovalManifest { + + public static final String SCOPE = "irgpu-optimization-proposal-manual-approval"; + public static final String RESOURCE_DIRECTORY = "META-INF/javatogpu/ir-optimization-approvals/"; + private static final String REQUIRED = "REQUIRED"; + + private GpuIrOptimizationApprovalManifest() { + } + + public static String template( + GpuIrOptimizationProposal proposal, + GpuIrOptimizationProposalRequest request + ) { + GpuIrOptimizationProposal normalizedProposal = requireProposalCandidate(proposal); + GpuIrOptimizationProposalRequest normalizedRequest = normalizeRequest(request, normalizedProposal); + GpuRuntimeIrOptimizationProofArtifact proof = normalizedProposal.proofArtifact(); + StringBuilder builder = new StringBuilder(); + builder.append("formatVersion=1\n"); + builder.append("status=pending\n"); + builder.append("scope=").append(SCOPE).append('\n'); + builder.append("approval.id=").append(REQUIRED).append('\n'); + builder.append("approval.approvedBy=").append(REQUIRED).append('\n'); + builder.append("approval.approvedAtUtc=").append(REQUIRED).append('\n'); + builder.append("binding.optimizerId=").append(propertyValue(normalizedProposal.optimizerId())).append('\n'); + builder.append("binding.optimizerVersion=").append(propertyValue(normalizedProposal.optimizerVersion())).append('\n'); + builder.append("binding.originalIrIdentity=").append(propertyValue(normalizedProposal.originalIdentity())).append('\n'); + builder.append("binding.optimizedIrIdentity=").append(propertyValue(normalizedProposal.optimizedIdentity())).append('\n'); + builder.append("binding.backendTarget=").append(context(normalizedRequest, "backendTarget", "UNKNOWN")).append('\n'); + builder.append("binding.optimizationProfile=").append(propertyValue(normalizedRequest.optimizerProfile())).append('\n'); + builder.append("binding.deviceVendor=").append(context(normalizedRequest, "deviceProfile.vendor", "unknown")).append('\n'); + builder.append("binding.deviceLabel=").append(context(normalizedRequest, "deviceProfile.label", "unknown")).append('\n'); + builder.append("binding.proof.source=").append(propertyValue(proof.source())).append('\n'); + builder.append("binding.proof.verdict=").append(propertyValue(proof.verdict())).append('\n'); + builder.append("binding.rollback.required=true\n"); + builder.append("authorization.productionMutation=disabled\n"); + builder.append("authorization.scope=manual-review-only\n"); + builder.append("diagnostic=complete approval fields and set status=approved; validation remains review-only\n"); + return builder.toString(); + } + + public static Validation validate( + GpuIrOptimizationProposal proposal, + GpuIrOptimizationProposalRequest request, + Properties manifest + ) { + GpuIrOptimizationProposal normalizedProposal = proposal; + GpuIrOptimizationProposalRequest normalizedRequest = normalizeRequest(request, proposal); + Properties normalizedManifest = manifest == null ? new Properties() : manifest; + LinkedHashSet blockers = new LinkedHashSet<>(); + if (normalizedProposal == null) { + blockers.add("proposal-missing"); + } else { + blockers.addAll(proposalBlockers(normalizedProposal)); + } + + String approvalId = normalize(normalizedManifest.getProperty("approval.id"), "approval:missing"); + String approvedBy = normalize(normalizedManifest.getProperty("approval.approvedBy"), "missing"); + String approvedAtUtc = normalize(normalizedManifest.getProperty("approval.approvedAtUtc"), "missing"); + + requireEquals(blockers, normalizedManifest, "formatVersion", "1", "manifest-format-version-mismatch"); + requireEquals(blockers, normalizedManifest, "status", "approved", "manifest-not-approved"); + requireEquals(blockers, normalizedManifest, "scope", SCOPE, "manifest-scope-mismatch"); + requireApprovalValue(blockers, approvalId, "manifest-approval-id-missing"); + requireApprovalValue(blockers, approvedBy, "manifest-approved-by-missing"); + requireApprovalValue(blockers, approvedAtUtc, "manifest-approved-at-missing"); + if (!REQUIRED.equals(approvedAtUtc) && !"missing".equals(approvedAtUtc)) { + try { + Instant.parse(approvedAtUtc); + } catch (DateTimeParseException exception) { + blockers.add("manifest-approved-at-invalid"); + } + } + + String optimizerId = normalizedProposal == null ? "missing" : normalizedProposal.optimizerId(); + String optimizerVersion = normalizedProposal == null ? "missing" : normalizedProposal.optimizerVersion(); + String originalIdentity = normalizedProposal == null ? "missing" : normalizedProposal.originalIdentity(); + String optimizedIdentity = normalizedProposal == null ? "missing" : normalizedProposal.optimizedIdentity(); + GpuRuntimeIrOptimizationProofArtifact proof = normalizedProposal == null + ? GpuRuntimeIrOptimizationProofArtifact.fromFields("missing", "missing", Map.of()) + : normalizedProposal.proofArtifact(); + + requireBindingEquals(blockers, normalizedManifest, "binding.optimizerId", optimizerId, "manifest-optimizer-id-mismatch"); + requireBindingEquals(blockers, normalizedManifest, "binding.optimizerVersion", optimizerVersion, "manifest-optimizer-version-mismatch"); + requireBindingEquals(blockers, normalizedManifest, "binding.originalIrIdentity", originalIdentity, "manifest-original-ir-identity-mismatch"); + requireBindingEquals(blockers, normalizedManifest, "binding.optimizedIrIdentity", optimizedIdentity, "manifest-optimized-ir-identity-mismatch"); + requireBindingEquals(blockers, normalizedManifest, "binding.backendTarget", context(normalizedRequest, "backendTarget", "UNKNOWN"), "manifest-backend-target-mismatch"); + requireBindingEquals(blockers, normalizedManifest, "binding.optimizationProfile", normalizedRequest.optimizerProfile(), "manifest-optimization-profile-mismatch"); + requireBindingEquals(blockers, normalizedManifest, "binding.deviceVendor", context(normalizedRequest, "deviceProfile.vendor", "unknown"), "manifest-device-vendor-mismatch"); + requireBindingEquals(blockers, normalizedManifest, "binding.deviceLabel", context(normalizedRequest, "deviceProfile.label", "unknown"), "manifest-device-label-mismatch"); + requireBindingEquals(blockers, normalizedManifest, "binding.proof.source", proof.source(), "manifest-proof-source-mismatch"); + requireBindingEquals(blockers, normalizedManifest, "binding.proof.verdict", proof.verdict(), "manifest-proof-verdict-mismatch"); + requireEquals(blockers, normalizedManifest, "binding.rollback.required", "true", "manifest-rollback-required-mismatch"); + requireEquals(blockers, normalizedManifest, "authorization.productionMutation", "disabled", "manifest-production-mutation-not-disabled"); + requireEquals(blockers, normalizedManifest, "authorization.scope", "manual-review-only", "manifest-authorization-scope-mismatch"); + + boolean valid = blockers.isEmpty(); + return new Validation( + valid, + valid ? "approved" : "blocked", + approvalId, + approvedBy, + approvedAtUtc, + optimizerId, + optimizerVersion, + originalIdentity, + optimizedIdentity, + context(normalizedRequest, "backendTarget", "UNKNOWN"), + normalizedRequest.optimizerProfile(), + context(normalizedRequest, "deviceProfile.vendor", "unknown"), + context(normalizedRequest, "deviceProfile.label", "unknown"), + proof.source(), + proof.verdict(), + List.copyOf(blockers) + ); + } + + private static GpuIrOptimizationProposal requireProposalCandidate(GpuIrOptimizationProposal proposal) { + if (proposal == null) { + throw new IllegalArgumentException("Optimizer approval manifest requires a proposal"); + } + List blockers = proposalBlockers(proposal); + if (!blockers.isEmpty()) { + throw new IllegalStateException("Cannot create optimizer approval manifest template: " + blockers.get(0)); + } + return proposal; + } + + private static List proposalBlockers(GpuIrOptimizationProposal proposal) { + ArrayList blockers = new ArrayList<>(); + if (proposal.decision() != GpuIrOptimizationProposalDecision.PROPOSED) { + blockers.add("proposal-decision-not-proposed"); + } + if (!proposal.hasOptimizedArtifact()) { + blockers.add("proposal-optimized-artifact-missing"); + } + if (proposal.originalIdentity().equals(proposal.optimizedIdentity())) { + blockers.add("proposal-identities-not-distinct"); + } + GpuRuntimeIrOptimizationProofArtifact proof = proposal.proofArtifact(); + if (proof.source().isBlank() || "none".equals(proof.source()) || "ir-optimizer".equals(proof.source())) { + blockers.add("proposal-proof-source-not-specific"); + } + String verdict = proof.verdict().toLowerCase(java.util.Locale.ROOT); + if (verdict.isBlank() + || verdict.contains("not-proven") + || verdict.contains("rejected") + || verdict.contains("failed")) { + blockers.add("proposal-proof-verdict-not-accepted"); + } + return List.copyOf(blockers); + } + + private static GpuIrOptimizationProposalRequest normalizeRequest( + GpuIrOptimizationProposalRequest request, + GpuIrOptimizationProposal proposal + ) { + if (request != null) { + return request; + } + if (proposal == null) { + throw new IllegalArgumentException("Optimizer approval manifest requires a request when proposal is missing"); + } + return new GpuIrOptimizationProposalRequest(proposal.originalArtifact()); + } + + private static void requireEquals( + LinkedHashSet blockers, + Properties properties, + String key, + String expected, + String blocker + ) { + if (!expected.equals(properties.getProperty(key))) { + blockers.add(blocker); + } + } + + private static void requireBindingEquals( + LinkedHashSet blockers, + Properties manifest, + String key, + String expected, + String blocker + ) { + if (!normalize(expected, "unknown").equals(normalize(manifest.getProperty(key), "missing"))) { + blockers.add(blocker); + } + } + + private static void requireApprovalValue(LinkedHashSet blockers, String value, String blocker) { + if (value.isBlank() || REQUIRED.equals(value) || "missing".equals(value) || "approval:missing".equals(value)) { + blockers.add(blocker); + } + } + + private static String context(GpuIrOptimizationProposalRequest request, String key, String fallback) { + return propertyValue(request.contextFields().getOrDefault(key, fallback)); + } + + private static String propertyValue(String value) { + return normalize(value, "unknown").replace('\\', '/').replace('\r', ' ').replace('\n', ' '); + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } + + public record Validation( + boolean valid, + String status, + String approvalId, + String approvedBy, + String approvedAtUtc, + String optimizerId, + String optimizerVersion, + String originalIrIdentity, + String optimizedIrIdentity, + String backendTarget, + String optimizationProfile, + String deviceVendor, + String deviceLabel, + String proofSource, + String proofVerdict, + List blockers + ) { + public Validation { + status = normalize(status, valid ? "approved" : "blocked"); + approvalId = normalize(approvalId, "approval:missing"); + approvedBy = normalize(approvedBy, "missing"); + approvedAtUtc = normalize(approvedAtUtc, "missing"); + optimizerId = normalize(optimizerId, "optimizer:missing"); + optimizerVersion = normalize(optimizerVersion, "missing"); + originalIrIdentity = normalize(originalIrIdentity, "irgpu:missing"); + optimizedIrIdentity = normalize(optimizedIrIdentity, "irgpu:missing"); + backendTarget = normalize(backendTarget, "UNKNOWN"); + optimizationProfile = normalize(optimizationProfile, "off"); + deviceVendor = normalize(deviceVendor, "unknown"); + deviceLabel = normalize(deviceLabel, "unknown"); + proofSource = normalize(proofSource, "missing"); + proofVerdict = normalize(proofVerdict, "missing"); + blockers = blockers == null ? List.of() : List.copyOf(blockers); + if (valid && !blockers.isEmpty()) { + throw new IllegalArgumentException("Valid optimizer approval manifest must not contain blockers"); + } + } + + public String firstBlocker() { + return blockers.isEmpty() ? "none" : blockers.get(0); + } + + public String toPropertiesText() { + StringBuilder builder = new StringBuilder(); + builder.append("formatVersion=1\n"); + builder.append("status=").append(status).append('\n'); + builder.append("valid=").append(valid).append('\n'); + builder.append("scope=").append(SCOPE).append("-validation\n"); + builder.append("approval.id=").append(propertyValue(approvalId)).append('\n'); + builder.append("approval.approvedBy=").append(propertyValue(approvedBy)).append('\n'); + builder.append("approval.approvedAtUtc=").append(propertyValue(approvedAtUtc)).append('\n'); + builder.append("binding.optimizerId=").append(propertyValue(optimizerId)).append('\n'); + builder.append("binding.optimizerVersion=").append(propertyValue(optimizerVersion)).append('\n'); + builder.append("binding.originalIrIdentity=").append(propertyValue(originalIrIdentity)).append('\n'); + builder.append("binding.optimizedIrIdentity=").append(propertyValue(optimizedIrIdentity)).append('\n'); + builder.append("binding.backendTarget=").append(propertyValue(backendTarget)).append('\n'); + builder.append("binding.optimizationProfile=").append(propertyValue(optimizationProfile)).append('\n'); + builder.append("binding.deviceVendor=").append(propertyValue(deviceVendor)).append('\n'); + builder.append("binding.deviceLabel=").append(propertyValue(deviceLabel)).append('\n'); + builder.append("binding.proof.source=").append(propertyValue(proofSource)).append('\n'); + builder.append("binding.proof.verdict=").append(propertyValue(proofVerdict)).append('\n'); + builder.append("authorization.productionMutation=disabled\n"); + builder.append("authorization.scope=manual-review-only\n"); + builder.append("blocker.count=").append(blockers.size()).append('\n'); + for (int index = 0; index < blockers.size(); index++) { + builder.append("blocker.").append(index).append('=').append(blockers.get(index)).append('\n'); + } + builder.append("diagnostic=").append(valid + ? "optimizer approval manifest is valid for the reviewed proposal; selection remains external" + : "optimizer approval manifest is blocked by " + firstBlocker()).append('\n'); + return builder.toString(); + } + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalTemplateFormatter.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalTemplateFormatter.java new file mode 100644 index 00000000..90007a8f --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalTemplateFormatter.java @@ -0,0 +1,61 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Formats review-only approval templates for optimizer proposals without changing runtime selection. + */ +public final class GpuIrOptimizationApprovalTemplateFormatter { + + private GpuIrOptimizationApprovalTemplateFormatter() { + } + + public static GpuIrOptimizationApprovalTemplateResult format( + GpuIrOptimizationProposal proposal, + GpuIrOptimizationProposalRequest request + ) { + LinkedHashMap fields = baseFields(proposal); + if (proposal == null) { + fields.put("diagnostic", "optimizer approval template skipped because proposal is missing"); + return GpuIrOptimizationApprovalTemplateResult.notApplicable("proposal-missing", fields); + } + if (proposal.decision() != GpuIrOptimizationProposalDecision.PROPOSED) { + fields.put("diagnostic", "optimizer approval template skipped because proposal decision is " + proposal.decision()); + return GpuIrOptimizationApprovalTemplateResult.notApplicable("proposal-decision-not-proposed", fields); + } + try { + String template = GpuIrOptimizationApprovalManifest.template(proposal, request); + fields.put("diagnostic", "optimizer approval template is pending manual review"); + fields.put("resourceDirectory", GpuIrOptimizationApprovalManifest.RESOURCE_DIRECTORY); + return GpuIrOptimizationApprovalTemplateResult.pending(template, fields); + } catch (IllegalArgumentException | IllegalStateException failure) { + String blocker = firstBlocker(failure); + fields.put("diagnostic", failure.getMessage()); + return GpuIrOptimizationApprovalTemplateResult.notApplicable(blocker, fields); + } + } + + private static LinkedHashMap baseFields(GpuIrOptimizationProposal proposal) { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("scope", GpuIrOptimizationApprovalManifest.SCOPE); + fields.put("optimizerId", proposal == null ? "missing" : proposal.optimizerId()); + fields.put("optimizerVersion", proposal == null ? "missing" : proposal.optimizerVersion()); + fields.put("decision", proposal == null ? "missing" : proposal.decision().name()); + fields.put("hasOptimizedArtifact", Boolean.toString(proposal != null && proposal.hasOptimizedArtifact())); + fields.put("productionMutation", "disabled"); + fields.put("manualReviewOnly", "true"); + return fields; + } + + private static String firstBlocker(Throwable failure) { + String message = failure == null ? "unknown" : failure.getMessage(); + if (message == null || message.isBlank()) { + return "unknown"; + } + int separator = message.lastIndexOf(": "); + return separator >= 0 && separator + 2 < message.length() + ? message.substring(separator + 2) + : message; + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalTemplateResult.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalTemplateResult.java new file mode 100644 index 00000000..039c0c9f --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalTemplateResult.java @@ -0,0 +1,82 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Result of formatting a manual approval template for one optimizer proposal. + */ +public record GpuIrOptimizationApprovalTemplateResult( + String status, + boolean applicable, + String firstBlocker, + String templateText, + Map fields +) { + + public GpuIrOptimizationApprovalTemplateResult { + status = normalize(status, applicable ? "pending" : "not-applicable"); + firstBlocker = normalize(firstBlocker, applicable ? "none" : "unknown"); + templateText = templateText == null ? "" : templateText; + fields = fields == null ? Map.of() : Map.copyOf(fields); + if (applicable && templateText.isBlank()) { + throw new IllegalArgumentException("Applicable optimizer approval template result must include template text"); + } + } + + static GpuIrOptimizationApprovalTemplateResult pending( + String templateText, + Map fields + ) { + return new GpuIrOptimizationApprovalTemplateResult( + "pending", + true, + "none", + templateText, + fields + ); + } + + static GpuIrOptimizationApprovalTemplateResult notApplicable( + String firstBlocker, + Map fields + ) { + return new GpuIrOptimizationApprovalTemplateResult( + "not-applicable", + false, + firstBlocker, + "", + fields + ); + } + + public String toPropertiesText() { + LinkedHashMap values = new LinkedHashMap<>(); + values.put("formatVersion", "1"); + values.put("status", status); + values.put("applicable", Boolean.toString(applicable)); + values.put("firstBlocker", firstBlocker); + values.putAll(fields); + if (applicable) { + values.put("template.present", "true"); + values.put("template.scope", GpuIrOptimizationApprovalManifest.SCOPE); + values.put("template.resourceDirectory", GpuIrOptimizationApprovalManifest.RESOURCE_DIRECTORY); + } else { + values.put("template.present", "false"); + } + StringBuilder builder = new StringBuilder(); + values.forEach((key, value) -> builder.append(key) + .append('=') + .append(propertyValue(value)) + .append('\n')); + return builder.toString(); + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } + + private static String propertyValue(String value) { + return normalize(value, "unknown").replace('\\', '/').replace('\r', ' ').replace('\n', ' '); + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationPolicy.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationPolicy.java new file mode 100644 index 00000000..d0f4fd63 --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationPolicy.java @@ -0,0 +1,108 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; + +/** + * Backend-neutral optimizer controls shared by proposal providers and runtime adapters. + */ +public record GpuIrOptimizationPolicy( + String optimizerProfile, + String optimizationLevel, + boolean proposalOnly, + boolean mutationAllowed, + boolean fastMathAllowed, + boolean vendorAdaptationAllowed, + boolean registerPressureSplittingAllowed, + boolean rollbackRequired, + boolean proofRequired +) { + + public static final String OPTIMIZER_PROFILE_FIELD = "policy.optimizerProfile"; + public static final String OPTIMIZATION_LEVEL_FIELD = "policy.optimizationLevel"; + public static final String PROPOSAL_ONLY_FIELD = "policy.proposalOnly"; + public static final String MUTATION_ALLOWED_FIELD = "policy.mutationAllowed"; + public static final String FAST_MATH_ALLOWED_FIELD = "policy.fastMathAllowed"; + public static final String VENDOR_ADAPTATION_ALLOWED_FIELD = "policy.vendorAdaptationAllowed"; + public static final String REGISTER_PRESSURE_SPLITTING_ALLOWED_FIELD = "policy.registerPressureSplittingAllowed"; + public static final String ROLLBACK_REQUIRED_FIELD = "policy.rollbackRequired"; + public static final String PROOF_REQUIRED_FIELD = "policy.proofRequired"; + + public GpuIrOptimizationPolicy { + optimizerProfile = normalize(optimizerProfile, "off"); + optimizationLevel = normalize(optimizationLevel, optimizerProfile); + proposalOnly = proposalOnly || !mutationAllowed; + } + + public static GpuIrOptimizationPolicy off() { + return fromContext("off", false, Map.of()); + } + + public static GpuIrOptimizationPolicy fromContext( + String optimizerProfile, + boolean mutationAllowed, + Map contextFields + ) { + Map fields = contextFields == null ? Map.of() : contextFields; + boolean resolvedMutationAllowed = parseBoolean( + firstNonBlank(fields.get(MUTATION_ALLOWED_FIELD), fields.get("mutationAllowed")), + mutationAllowed + ); + return new GpuIrOptimizationPolicy( + firstNonBlank(fields.get(OPTIMIZER_PROFILE_FIELD), fields.get("optimizationProfile"), optimizerProfile), + firstNonBlank(fields.get(OPTIMIZATION_LEVEL_FIELD), fields.get("optimizationLevel"), optimizerProfile), + parseBoolean(firstNonBlank(fields.get(PROPOSAL_ONLY_FIELD), fields.get("proposalOnly")), !resolvedMutationAllowed), + resolvedMutationAllowed, + parseBoolean(firstNonBlank(fields.get(FAST_MATH_ALLOWED_FIELD), fields.get("fastMathAllowed")), false), + parseBoolean(firstNonBlank(fields.get(VENDOR_ADAPTATION_ALLOWED_FIELD), fields.get("vendorAdaptationAllowed")), false), + parseBoolean(firstNonBlank( + fields.get(REGISTER_PRESSURE_SPLITTING_ALLOWED_FIELD), + fields.get("registerPressureSplittingAllowed") + ), false), + parseBoolean(firstNonBlank(fields.get(ROLLBACK_REQUIRED_FIELD), fields.get("rollbackRequired")), true), + parseBoolean(firstNonBlank(fields.get(PROOF_REQUIRED_FIELD), fields.get("proofRequired")), true) + ); + } + + public Map asContextFields() { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put(OPTIMIZER_PROFILE_FIELD, optimizerProfile); + fields.put(OPTIMIZATION_LEVEL_FIELD, optimizationLevel); + fields.put(PROPOSAL_ONLY_FIELD, Boolean.toString(proposalOnly)); + fields.put(MUTATION_ALLOWED_FIELD, Boolean.toString(mutationAllowed)); + fields.put(FAST_MATH_ALLOWED_FIELD, Boolean.toString(fastMathAllowed)); + fields.put(VENDOR_ADAPTATION_ALLOWED_FIELD, Boolean.toString(vendorAdaptationAllowed)); + fields.put(REGISTER_PRESSURE_SPLITTING_ALLOWED_FIELD, Boolean.toString(registerPressureSplittingAllowed)); + fields.put(ROLLBACK_REQUIRED_FIELD, Boolean.toString(rollbackRequired)); + fields.put(PROOF_REQUIRED_FIELD, Boolean.toString(proofRequired)); + return Map.copyOf(fields); + } + + private static boolean parseBoolean(String value, boolean fallback) { + if (value == null || value.isBlank()) { + return fallback; + } + return switch (value.trim().toLowerCase(Locale.ROOT)) { + case "true", "yes", "enabled", "allow", "allowed", "required" -> true; + case "false", "no", "disabled", "deny", "denied", "optional" -> false; + default -> fallback; + }; + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value.trim(); + } + + private static String firstNonBlank(String... values) { + if (values == null) { + return ""; + } + for (String value : values) { + if (value != null && !value.isBlank()) { + return value; + } + } + return ""; + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposal.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposal.java new file mode 100644 index 00000000..e5ca3208 --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposal.java @@ -0,0 +1,124 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactIdentity; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +/** + * Immutable optimizer proposal: original IR plus an optional optimized replacement and proof metadata. + */ +public record GpuIrOptimizationProposal( + String optimizerId, + String optimizerVersion, + IrGpuArtifact originalArtifact, + Optional optimizedArtifact, + GpuIrOptimizationProposalDecision decision, + GpuRuntimeIrOptimizationProofArtifact proofArtifact, + String rollbackReason, + List diagnostics +) { + + public GpuIrOptimizationProposal { + optimizerId = normalize(optimizerId, "javatogpu.ir-optimizer.unknown"); + optimizerVersion = normalize(optimizerVersion, optimizerId + ":unknown"); + originalArtifact = Objects.requireNonNull(originalArtifact, "originalArtifact"); + optimizedArtifact = optimizedArtifact == null ? Optional.empty() : optimizedArtifact; + decision = decision == null ? GpuIrOptimizationProposalDecision.NO_CHANGE : decision; + proofArtifact = proofArtifact == null + ? GpuRuntimeIrOptimizationProofArtifact.fromFields("ir-optimizer", "not-proven", Map.of()) + : proofArtifact; + rollbackReason = rollbackReason == null ? "" : rollbackReason; + diagnostics = diagnostics == null ? List.of() : List.copyOf(diagnostics); + if (decision == GpuIrOptimizationProposalDecision.PROPOSED && optimizedArtifact.isEmpty()) { + throw new IllegalArgumentException("PROPOSED optimizer decisions must include an optimized artifact"); + } + if (decision == GpuIrOptimizationProposalDecision.PROPOSED) { + IrGpuArtifact optimized = optimizedArtifact.orElseThrow(); + if (optimized == originalArtifact) { + throw new IllegalArgumentException("PROPOSED optimizer decisions must return a distinct optimized artifact instance"); + } + if (IrGpuArtifactIdentity.stableIdentity(originalArtifact) + .equals(IrGpuArtifactIdentity.stableIdentity(optimized))) { + throw new IllegalArgumentException("PROPOSED optimizer decisions must change the optimized artifact identity"); + } + } + } + + public static GpuIrOptimizationProposal noChange( + String optimizerId, + String optimizerVersion, + IrGpuArtifact originalArtifact, + String diagnostic + ) { + return new GpuIrOptimizationProposal( + optimizerId, + optimizerVersion, + originalArtifact, + Optional.empty(), + GpuIrOptimizationProposalDecision.NO_CHANGE, + GpuRuntimeIrOptimizationProofArtifact.fromFields("ir-optimizer", "not-mutating", Map.of()), + "", + diagnostic == null || diagnostic.isBlank() ? List.of() : List.of(diagnostic) + ); + } + + public static GpuIrOptimizationProposal proposed( + String optimizerId, + String optimizerVersion, + IrGpuArtifact originalArtifact, + IrGpuArtifact optimizedArtifact, + GpuRuntimeIrOptimizationProofArtifact proofArtifact, + List diagnostics + ) { + return new GpuIrOptimizationProposal( + optimizerId, + optimizerVersion, + originalArtifact, + Optional.of(Objects.requireNonNull(optimizedArtifact, "optimizedArtifact")), + GpuIrOptimizationProposalDecision.PROPOSED, + proofArtifact, + "", + diagnostics + ); + } + + public static GpuIrOptimizationProposal rejected( + String optimizerId, + String optimizerVersion, + IrGpuArtifact originalArtifact, + String reason, + List diagnostics + ) { + return new GpuIrOptimizationProposal( + optimizerId, + optimizerVersion, + originalArtifact, + Optional.empty(), + GpuIrOptimizationProposalDecision.REJECTED, + GpuRuntimeIrOptimizationProofArtifact.fromFields("ir-optimizer", "rejected", Map.of()), + reason, + diagnostics + ); + } + + public String originalIdentity() { + return IrGpuArtifactIdentity.stableIdentity(originalArtifact); + } + + public String optimizedIdentity() { + return IrGpuArtifactIdentity.stableIdentity(optimizedArtifact); + } + + public boolean hasOptimizedArtifact() { + return optimizedArtifact.isPresent(); + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalDecision.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalDecision.java new file mode 100644 index 00000000..2fa3b430 --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalDecision.java @@ -0,0 +1,10 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +/** + * Immutable decision produced by a backend-neutral IR optimization proposal provider. + */ +public enum GpuIrOptimizationProposalDecision { + NO_CHANGE, + PROPOSED, + REJECTED +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalProvider.java new file mode 100644 index 00000000..79e6567b --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalProvider.java @@ -0,0 +1,32 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.extension.GpuExtension; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionCapability; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionPermission; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionPhase; + +import java.util.Set; + +/** + * Optional backend-neutral provider that proposes a new IR artifact without mutating the original. + */ +@FunctionalInterface +public interface GpuIrOptimizationProposalProvider extends GpuExtension { + + GpuIrOptimizationProposal propose(GpuIrOptimizationProposalRequest request); + + @Override + default Set extensionCapabilities() { + return Set.of(GpuExtensionCapability.IR_OPTIMIZATION_PROPOSAL); + } + + @Override + default GpuExtensionPhase extensionPhase() { + return GpuExtensionPhase.RUNTIME_IR_OPTIMIZATION; + } + + @Override + default GpuExtensionPermission extensionPermission() { + return GpuExtensionPermission.MUTATION_PROPOSAL; + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalRegistry.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalRegistry.java new file mode 100644 index 00000000..ce528a95 --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalRegistry.java @@ -0,0 +1,83 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.extension.GpuExtension; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; + +/** + * Backend-neutral registry for optional immutable IR optimization proposal providers. + */ +public final class GpuIrOptimizationProposalRegistry { + + private final List providers; + private final Map rejectionReasons; + + private GpuIrOptimizationProposalRegistry( + List providers, + Map rejectionReasons + ) { + this.providers = List.copyOf(providers); + this.rejectionReasons = Map.copyOf(rejectionReasons); + } + + public static GpuIrOptimizationProposalRegistry empty() { + return new GpuIrOptimizationProposalRegistry(List.of(), Map.of()); + } + + public static GpuIrOptimizationProposalRegistry of( + List providers + ) { + return build(providers == null ? List.of() : providers); + } + + public static GpuIrOptimizationProposalRegistry loadFromServiceLoader() { + ArrayList loaded = new ArrayList<>(); + ServiceLoader.load( + GpuIrOptimizationProposalProvider.class, + GpuIrOptimizationProposalProvider.class.getClassLoader() + ).forEach(loaded::add); + return build(loaded); + } + + public List providers() { + return providers; + } + + public Map rejectionReasons() { + return rejectionReasons; + } + + private static GpuIrOptimizationProposalRegistry build( + List candidates + ) { + ArrayList accepted = new ArrayList<>(); + LinkedHashMap rejected = new LinkedHashMap<>(); + LinkedHashMap byId = new LinkedHashMap<>(); + for (GpuIrOptimizationProposalProvider provider : candidates) { + if (provider == null) { + continue; + } + String extensionId = provider.extensionId(); + if (extensionId == null || extensionId.isBlank()) { + rejected.put(provider.getClass().getName(), "blank extension id"); + continue; + } + if (byId.containsKey(extensionId)) { + rejected.put(extensionId, "duplicate extension id"); + continue; + } + byId.put(extensionId, provider); + } + accepted.addAll(byId.values()); + accepted.sort(Comparator + .comparingInt(GpuExtension::extensionOrder) + .thenComparing(GpuExtension::extensionId) + .thenComparing(GpuExtension::extensionVersion)); + return new GpuIrOptimizationProposalRegistry(accepted, rejected); + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalRequest.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalRequest.java new file mode 100644 index 00000000..c1f4f7d7 --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalRequest.java @@ -0,0 +1,62 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; + +import java.util.Map; +import java.util.Objects; + +/** + * Backend-neutral immutable input for an IR optimization proposal. + */ +public record GpuIrOptimizationProposalRequest( + IrGpuArtifact originalArtifact, + String optimizerProfile, + boolean mutationAllowed, + Map contextFields +) { + + public GpuIrOptimizationProposalRequest(IrGpuArtifact originalArtifact) { + this(originalArtifact, "off", false, Map.of()); + } + + public GpuIrOptimizationProposalRequest( + IrGpuArtifact originalArtifact, + GpuIrOptimizationPolicy policy, + Map contextFields + ) { + this( + originalArtifact, + policy == null ? "off" : policy.optimizerProfile(), + policy != null && policy.mutationAllowed(), + mergePolicyFields(policy == null ? GpuIrOptimizationPolicy.off() : policy, contextFields) + ); + } + + public GpuIrOptimizationProposalRequest { + originalArtifact = Objects.requireNonNull(originalArtifact, "originalArtifact"); + GpuIrOptimizationPolicy policy = GpuIrOptimizationPolicy.fromContext( + optimizerProfile, + mutationAllowed, + contextFields + ); + optimizerProfile = policy.optimizerProfile(); + mutationAllowed = policy.mutationAllowed(); + contextFields = mergePolicyFields(policy, contextFields); + } + + public GpuIrOptimizationPolicy policy() { + return GpuIrOptimizationPolicy.fromContext(optimizerProfile, mutationAllowed, contextFields); + } + + private static Map mergePolicyFields( + GpuIrOptimizationPolicy policy, + Map contextFields + ) { + java.util.LinkedHashMap merged = new java.util.LinkedHashMap<>(); + if (contextFields != null) { + merged.putAll(contextFields); + } + merged.putAll(policy.asContextFields()); + return Map.copyOf(merged); + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichReport.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichReport.java new file mode 100644 index 00000000..256e0984 --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichReport.java @@ -0,0 +1,45 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactIdentity; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** + * Final immutable optimizer selection report after validation sandwich execution. + */ +public record GpuIrOptimizationSandwichReport( + IrGpuArtifact originalArtifact, + IrGpuArtifact selectedArtifact, + Optional proposal, + GpuIrOptimizationValidationResult originalValidation, + Optional optimizedValidation, + GpuIrOptimizationSandwichStatus status, + List diagnostics +) { + + public GpuIrOptimizationSandwichReport { + originalArtifact = Objects.requireNonNull(originalArtifact, "originalArtifact"); + selectedArtifact = Objects.requireNonNull(selectedArtifact, "selectedArtifact"); + proposal = proposal == null ? Optional.empty() : proposal; + originalValidation = Objects.requireNonNull(originalValidation, "originalValidation"); + optimizedValidation = optimizedValidation == null ? Optional.empty() : optimizedValidation; + status = status == null ? GpuIrOptimizationSandwichStatus.NO_CHANGE : status; + diagnostics = diagnostics == null ? List.of() : List.copyOf(diagnostics); + } + + public boolean selectedOriginal() { + return IrGpuArtifactIdentity.stableIdentity(originalArtifact) + .equals(IrGpuArtifactIdentity.stableIdentity(selectedArtifact)); + } + + public boolean selectedOptimized() { + return status == GpuIrOptimizationSandwichStatus.OPTIMIZED_SELECTED && !selectedOriginal(); + } + + public boolean requiresRollback() { + return status == GpuIrOptimizationSandwichStatus.OPTIMIZED_INVALID_ROLLED_BACK; + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichRunner.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichRunner.java new file mode 100644 index 00000000..1ce0c7cb --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichRunner.java @@ -0,0 +1,157 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** + * Executes the fail-closed validation sandwich around one immutable optimizer proposal. + */ +public final class GpuIrOptimizationSandwichRunner { + + private final GpuIrOptimizationValidationGate validationGate; + + public GpuIrOptimizationSandwichRunner(GpuIrOptimizationValidationGate validationGate) { + this.validationGate = validationGate == null ? GpuIrOptimizationValidationGate.alwaysValid() : validationGate; + } + + public static GpuIrOptimizationSandwichRunner alwaysValid() { + return new GpuIrOptimizationSandwichRunner(GpuIrOptimizationValidationGate.alwaysValid()); + } + + public GpuIrOptimizationSandwichReport run( + GpuIrOptimizationProposalRequest request, + GpuIrOptimizationProposalProvider provider + ) { + Objects.requireNonNull(request, "request"); + Objects.requireNonNull(provider, "provider"); + IrGpuArtifact originalArtifact = request.originalArtifact(); + ArrayList diagnostics = new ArrayList<>(); + + GpuIrOptimizationValidationResult originalValidation = validationGate.validate( + new GpuIrOptimizationValidationRequest( + originalArtifact, + GpuIrOptimizationValidationStage.ORIGINAL_BEFORE, + Optional.empty() + ) + ); + diagnostics.addAll(originalValidation.diagnostics()); + if (!originalValidation.valid()) { + return report( + originalArtifact, + originalArtifact, + Optional.empty(), + originalValidation, + Optional.empty(), + GpuIrOptimizationSandwichStatus.ORIGINAL_INVALID, + diagnostics + ); + } + + GpuIrOptimizationProposal proposal; + try { + proposal = Objects.requireNonNull(provider.propose(request), "proposal"); + } catch (RuntimeException exception) { + diagnostics.add(exception.getMessage() == null ? exception.getClass().getSimpleName() : exception.getMessage()); + return report( + originalArtifact, + originalArtifact, + Optional.empty(), + originalValidation, + Optional.empty(), + GpuIrOptimizationSandwichStatus.PROPOSAL_REJECTED, + diagnostics + ); + } + + diagnostics.addAll(proposal.diagnostics()); + if (proposal.decision() == GpuIrOptimizationProposalDecision.REJECTED) { + return report( + originalArtifact, + originalArtifact, + Optional.of(proposal), + originalValidation, + Optional.empty(), + GpuIrOptimizationSandwichStatus.PROPOSAL_REJECTED, + diagnostics + ); + } + if (!proposal.hasOptimizedArtifact()) { + return report( + originalArtifact, + originalArtifact, + Optional.of(proposal), + originalValidation, + Optional.empty(), + GpuIrOptimizationSandwichStatus.NO_CHANGE, + diagnostics + ); + } + + IrGpuArtifact optimizedArtifact = proposal.optimizedArtifact().orElseThrow(); + GpuIrOptimizationValidationResult optimizedValidation = validationGate.validate( + new GpuIrOptimizationValidationRequest( + optimizedArtifact, + GpuIrOptimizationValidationStage.OPTIMIZED_AFTER, + Optional.of(proposal) + ) + ); + diagnostics.addAll(optimizedValidation.diagnostics()); + if (!optimizedValidation.valid()) { + return report( + originalArtifact, + originalArtifact, + Optional.of(proposal), + originalValidation, + Optional.of(optimizedValidation), + GpuIrOptimizationSandwichStatus.OPTIMIZED_INVALID_ROLLED_BACK, + diagnostics + ); + } + if (!request.mutationAllowed()) { + diagnostics.add("optimized artifact validated but mutation is disabled; original IR remains selected"); + return report( + originalArtifact, + originalArtifact, + Optional.of(proposal), + originalValidation, + Optional.of(optimizedValidation), + GpuIrOptimizationSandwichStatus.PROPOSAL_ONLY, + diagnostics + ); + } + + return report( + originalArtifact, + optimizedArtifact, + Optional.of(proposal), + originalValidation, + Optional.of(optimizedValidation), + GpuIrOptimizationSandwichStatus.OPTIMIZED_SELECTED, + diagnostics + ); + } + + private static GpuIrOptimizationSandwichReport report( + IrGpuArtifact originalArtifact, + IrGpuArtifact selectedArtifact, + Optional proposal, + GpuIrOptimizationValidationResult originalValidation, + Optional optimizedValidation, + GpuIrOptimizationSandwichStatus status, + List diagnostics + ) { + return new GpuIrOptimizationSandwichReport( + originalArtifact, + selectedArtifact, + proposal, + originalValidation, + optimizedValidation, + status, + diagnostics + ); + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichStatus.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichStatus.java new file mode 100644 index 00000000..ca757e89 --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichStatus.java @@ -0,0 +1,13 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +/** + * Final selection decision after pre/post validation around an optimizer proposal. + */ +public enum GpuIrOptimizationSandwichStatus { + ORIGINAL_INVALID, + NO_CHANGE, + PROPOSAL_REJECTED, + PROPOSAL_ONLY, + OPTIMIZED_INVALID_ROLLED_BACK, + OPTIMIZED_SELECTED +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationValidationGate.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationValidationGate.java new file mode 100644 index 00000000..87f59d7a --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationValidationGate.java @@ -0,0 +1,14 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +/** + * Pluggable validation gate used before and after optimizer proposals. + */ +@FunctionalInterface +public interface GpuIrOptimizationValidationGate { + + GpuIrOptimizationValidationResult validate(GpuIrOptimizationValidationRequest request); + + static GpuIrOptimizationValidationGate alwaysValid() { + return request -> GpuIrOptimizationValidationResult.valid(request.stage()); + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationValidationRequest.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationValidationRequest.java new file mode 100644 index 00000000..817bf0c0 --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationValidationRequest.java @@ -0,0 +1,22 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; + +import java.util.Objects; +import java.util.Optional; + +/** + * Validation input for one stage of the optimizer validation sandwich. + */ +public record GpuIrOptimizationValidationRequest( + IrGpuArtifact artifact, + GpuIrOptimizationValidationStage stage, + Optional proposal +) { + + public GpuIrOptimizationValidationRequest { + artifact = Objects.requireNonNull(artifact, "artifact"); + stage = stage == null ? GpuIrOptimizationValidationStage.ORIGINAL_BEFORE : stage; + proposal = proposal == null ? Optional.empty() : proposal; + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationValidationResult.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationValidationResult.java new file mode 100644 index 00000000..16cca637 --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationValidationResult.java @@ -0,0 +1,41 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import java.util.List; +import java.util.Map; + +/** + * Compact validation verdict used by the optimizer sandwich runner. + */ +public record GpuIrOptimizationValidationResult( + GpuIrOptimizationValidationStage stage, + boolean valid, + String verdict, + Map fields, + List diagnostics +) { + + public GpuIrOptimizationValidationResult { + stage = stage == null ? GpuIrOptimizationValidationStage.ORIGINAL_BEFORE : stage; + verdict = verdict == null || verdict.isBlank() ? (valid ? "valid" : "invalid") : verdict; + fields = fields == null ? Map.of() : Map.copyOf(fields); + diagnostics = diagnostics == null ? List.of() : List.copyOf(diagnostics); + } + + public static GpuIrOptimizationValidationResult valid(GpuIrOptimizationValidationStage stage) { + return new GpuIrOptimizationValidationResult(stage, true, "valid", Map.of(), List.of()); + } + + public static GpuIrOptimizationValidationResult invalid( + GpuIrOptimizationValidationStage stage, + String verdict, + String diagnostic + ) { + return new GpuIrOptimizationValidationResult( + stage, + false, + verdict, + Map.of(), + diagnostic == null || diagnostic.isBlank() ? List.of() : List.of(diagnostic) + ); + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationValidationStage.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationValidationStage.java new file mode 100644 index 00000000..b9ccae41 --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationValidationStage.java @@ -0,0 +1,9 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +/** + * Validation checkpoints around immutable optimizer proposals. + */ +public enum GpuIrOptimizationValidationStage { + ORIGINAL_BEFORE, + OPTIMIZED_AFTER +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizerModule.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizerModule.java new file mode 100644 index 00000000..0e07f236 --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizerModule.java @@ -0,0 +1,15 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +/** + * Module metadata for the optional backend-neutral IR optimizer package. + */ +public final class GpuIrOptimizerModule { + + public static final String MODULE_ID = "javatogpu.ir-optimizer"; + public static final String MODULE_VERSION = "1"; + public static final String VENDOR_PROVIDER_ADAPTER_ID = MODULE_ID + ".vendor-provider-adapter"; + public static final String VENDOR_PROVIDER_ADAPTER_VERSION = VENDOR_PROVIDER_ADAPTER_ID + ":1"; + + private GpuIrOptimizerModule() { + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePass.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePass.java new file mode 100644 index 00000000..a0c96514 --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePass.java @@ -0,0 +1,205 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactIdentity; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationOutcome; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationPass; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationPassReport; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationReport; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationRequest; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationStage; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Runtime bridge that runs optional immutable proposal providers through the validation sandwich. + */ +public final class GpuIrProposalRuntimeBridgePass implements GpuRuntimeIrOptimizationPass { + + public static final String PASS_ID = GpuIrOptimizerModule.MODULE_ID + ".proposal-runtime-bridge"; + public static final String PASS_VERSION = PASS_ID + ":1"; + + private final List providers; + private final GpuIrOptimizationSandwichRunner sandwichRunner; + private final boolean mutationAllowed; + + public GpuIrProposalRuntimeBridgePass() { + this(loadProviders(), GpuIrOptimizationSandwichRunner.alwaysValid(), false); + } + + public GpuIrProposalRuntimeBridgePass( + List providers, + GpuIrOptimizationSandwichRunner sandwichRunner, + boolean mutationAllowed + ) { + this.providers = providers == null ? List.of() : List.copyOf(providers); + this.sandwichRunner = sandwichRunner == null ? GpuIrOptimizationSandwichRunner.alwaysValid() : sandwichRunner; + this.mutationAllowed = mutationAllowed; + } + + @Override + public GpuRuntimeIrOptimizationReport run(GpuRuntimeIrOptimizationRequest request) { + Optional current = request.artifact(); + if (current.isEmpty()) { + GpuRuntimeIrOptimizationPassReport passReport = GpuRuntimeIrOptimizationPassReport.skipped( + passVersion(), + IrGpuArtifactIdentity.MISSING_ARTIFACT_IDENTITY, + "proposal runtime bridge skipped because no IrGpu artifact is available" + ).withStage(stage()); + return new GpuRuntimeIrOptimizationReport(current, List.of(passReport)); + } + if (providers.isEmpty()) { + GpuRuntimeIrOptimizationPassReport passReport = GpuRuntimeIrOptimizationPassReport.skipped( + passVersion(), + IrGpuArtifactIdentity.stableIdentity(current), + "proposal runtime bridge found no proposal providers" + ).withStage(stage()); + return new GpuRuntimeIrOptimizationReport(current, List.of(passReport)); + } + + IrGpuArtifact selected = current.orElseThrow(); + ArrayList passReports = new ArrayList<>(); + for (GpuIrOptimizationProposalProvider provider : providers) { + GpuIrOptimizationProposalRequest proposalRequest = new GpuIrOptimizationProposalRequest( + selected, + request.compileRequest().options().optimizationProfile(), + mutationAllowed, + contextFields(request, mutationAllowed) + ); + GpuIrOptimizationSandwichReport sandwichReport = sandwichRunner.run(proposalRequest, provider); + passReports.add(toPassReport(provider, sandwichReport).withStage(stage())); + selected = sandwichReport.selectedArtifact(); + if (sandwichReport.requiresRollback() + || sandwichReport.status() == GpuIrOptimizationSandwichStatus.ORIGINAL_INVALID) { + break; + } + } + return new GpuRuntimeIrOptimizationReport(Optional.of(selected), passReports); + } + + @Override + public GpuRuntimeIrOptimizationStage stage() { + return GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY; + } + + @Override + public String passName() { + return PASS_ID; + } + + @Override + public String passVersion() { + return PASS_VERSION; + } + + @Override + public int extensionOrder() { + return 90; + } + + public List proposalProviders() { + return providers; + } + + private static GpuRuntimeIrOptimizationPassReport toPassReport( + GpuIrOptimizationProposalProvider provider, + GpuIrOptimizationSandwichReport report + ) { + String originalIdentity = IrGpuArtifactIdentity.stableIdentity(report.originalArtifact()); + String selectedIdentity = IrGpuArtifactIdentity.stableIdentity(report.selectedArtifact()); + Optional proposal = report.proposal(); + String transformedIdentity = proposal.map(GpuIrOptimizationProposal::optimizedIdentity).orElse(selectedIdentity); + GpuRuntimeIrOptimizationProofArtifact proofArtifact = proposal + .map(GpuIrOptimizationProposal::proofArtifact) + .orElseGet(() -> proof(report.status().name().toLowerCase(java.util.Locale.ROOT), Map.of())); + + return switch (report.status()) { + case OPTIMIZED_SELECTED -> new GpuRuntimeIrOptimizationPassReport( + GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, + provider.extensionVersion(), + GpuRuntimeIrOptimizationOutcome.APPLIED, + originalIdentity, + selectedIdentity, + "optimized-selected", + "", + proofArtifact, + report.diagnostics() + ); + case OPTIMIZED_INVALID_ROLLED_BACK -> new GpuRuntimeIrOptimizationPassReport( + GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, + provider.extensionVersion(), + GpuRuntimeIrOptimizationOutcome.ROLLED_BACK, + originalIdentity, + transformedIdentity, + report.optimizedValidation().map(GpuIrOptimizationValidationResult::verdict) + .orElse("optimized-validation-failed"), + "optimized artifact rejected by validation sandwich", + proofArtifact, + report.diagnostics() + ); + case ORIGINAL_INVALID -> new GpuRuntimeIrOptimizationPassReport( + GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, + provider.extensionVersion(), + GpuRuntimeIrOptimizationOutcome.FAILED, + originalIdentity, + originalIdentity, + report.originalValidation().verdict(), + "original artifact rejected by validation sandwich", + proofArtifact, + report.diagnostics() + ); + case PROPOSAL_ONLY -> skipped(provider, originalIdentity, transformedIdentity, "proposal-only", proofArtifact, report.diagnostics()); + case PROPOSAL_REJECTED -> skipped(provider, originalIdentity, originalIdentity, "proposal-rejected", proofArtifact, report.diagnostics()); + case NO_CHANGE -> skipped(provider, originalIdentity, originalIdentity, "not-mutating", proofArtifact, report.diagnostics()); + }; + } + + private static GpuRuntimeIrOptimizationPassReport skipped( + GpuIrOptimizationProposalProvider provider, + String originalIdentity, + String transformedIdentity, + String proofStatus, + GpuRuntimeIrOptimizationProofArtifact proofArtifact, + List diagnostics + ) { + return new GpuRuntimeIrOptimizationPassReport( + GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, + provider.extensionVersion(), + GpuRuntimeIrOptimizationOutcome.SKIPPED, + originalIdentity, + transformedIdentity, + proofStatus, + "", + proofArtifact, + diagnostics + ); + } + + private static GpuRuntimeIrOptimizationProofArtifact proof(String verdict, Map fields) { + return GpuRuntimeIrOptimizationProofArtifact.fromFields("ir-optimizer.proposal-runtime-bridge", verdict, fields); + } + + private static Map contextFields(GpuRuntimeIrOptimizationRequest request, boolean mutationAllowed) { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("backendTarget", request.compileRequest().options().backendTarget().name()); + fields.put("optimizationProfile", request.compileRequest().options().optimizationProfile()); + fields.put("deviceProfile.backendTarget", request.compileRequest().deviceProfile().backendTarget().name()); + fields.put("deviceProfile.id", request.compileRequest().deviceProfile().deviceId()); + fields.put("deviceProfile.label", request.compileRequest().deviceProfile().deviceLabel()); + fields.put("deviceProfile.vendor", request.compileRequest().deviceProfile().vendor()); + fields.put("deviceProfile.driverVersion", request.compileRequest().deviceProfile().driverVersion()); + fields.put("deviceProfile.apiVersionText", request.compileRequest().deviceProfile().apiVersionText()); + fields.put("deviceProfile.deviceClass", request.compileRequest().deviceProfile().deviceClass().name()); + fields.put("mutationAllowed", Boolean.toString(mutationAllowed)); + return Map.copyOf(fields); + } + + private static List loadProviders() { + return GpuIrOptimizationProposalRegistry.loadFromServiceLoader().providers(); + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTextCanonicalizationProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTextCanonicalizationProposalProvider.java new file mode 100644 index 00000000..67676456 --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTextCanonicalizationProposalProvider.java @@ -0,0 +1,152 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Semantics-neutral proposal pass that canonicalizes IR text bodies only. + */ +public final class GpuIrTextCanonicalizationProposalProvider implements GpuIrOptimizationProposalProvider { + + public static final String PROVIDER_ID = GpuIrOptimizerModule.MODULE_ID + ".text-canonicalization"; + public static final String PROVIDER_VERSION = PROVIDER_ID + ":1"; + + @Override + public GpuIrOptimizationProposal propose(GpuIrOptimizationProposalRequest request) { + IrGpuArtifact original = request.originalArtifact(); + List canonicalBodies = new ArrayList<>(); + int changedBodies = 0; + + for (IrGpuMethodBody methodBody : original.module().methodBodies()) { + String canonicalBody = canonicalize(methodBody.body()); + if (!canonicalBody.equals(methodBody.body())) { + changedBodies++; + } + canonicalBodies.add(copyWithBody(methodBody, canonicalBody)); + } + + if (changedBodies == 0) { + return GpuIrOptimizationProposal.noChange( + extensionId(), + extensionVersion(), + original, + "IR text bodies are already canonical" + ); + } + + IrGpuArtifact optimized = copyWithModule(original, copyWithBodies(original.module(), canonicalBodies)); + return GpuIrOptimizationProposal.proposed( + extensionId(), + extensionVersion(), + original, + optimized, + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.text-canonicalization", + "semantics-neutral-text-normalization", + Map.of( + "changedMethodBodies", Integer.toString(changedBodies), + "mutationRequired", "false", + "normalizations", "crlf-to-lf,trailing-whitespace" + ) + ), + List.of("canonicalized " + changedBodies + " IR text method body/bodies") + ); + } + + @Override + public String extensionId() { + return PROVIDER_ID; + } + + @Override + public String extensionVersion() { + return PROVIDER_VERSION; + } + + @Override + public int extensionOrder() { + return 200; + } + + static String canonicalize(String body) { + if (body == null || body.isEmpty()) { + return ""; + } + String normalized = body.replace("\r\n", "\n").replace('\r', '\n'); + String[] lines = normalized.split("\n", -1); + StringBuilder builder = new StringBuilder(normalized.length()); + for (int index = 0; index < lines.length; index++) { + if (index > 0) { + builder.append('\n'); + } + builder.append(stripTrailingWhitespace(lines[index])); + } + return builder.toString(); + } + + private static String stripTrailingWhitespace(String line) { + int end = line.length(); + while (end > 0) { + char value = line.charAt(end - 1); + if (value != ' ' && value != '\t') { + break; + } + end--; + } + return end == line.length() ? line : line.substring(0, end); + } + + private static IrGpuMethodBody copyWithBody(IrGpuMethodBody methodBody, String body) { + return new IrGpuMethodBody( + methodBody.role(), + methodBody.name(), + methodBody.emittedName(), + methodBody.format(), + body, + methodBody.typedBody(), + methodBody.bodyIndex(), + methodBody.helperDependencies(), + methodBody.sourceLocation() + ); + } + + private static IrGpuModule copyWithBodies(IrGpuModule module, List methodBodies) { + return new IrGpuModule( + module.entryMethod(), + module.entryEmittedName(), + module.entryOpenClAttributes(), + module.entryAttributeMetadata(), + module.helperMethods(), + module.structs(), + methodBodies + ); + } + + private static IrGpuArtifact copyWithModule(IrGpuArtifact artifact, IrGpuModule module) { + return new IrGpuArtifact( + artifact.header(), + module, + artifact.entryParameters(), + artifact.launchMetadata(), + artifact.validationMetadata(), + artifact.featureMetadata(), + artifact.optimizerPolicyMetadata(), + artifact.regenerationMetadata(), + artifact.structMetadata(), + artifact.constants(), + artifact.constantData(), + artifact.backendOutputs(), + artifact.runtimeDefaultBackend(), + artifact.runtimeOptimizationProfile(), + artifact.methodDeviceConstraints(), + artifact.methodFallbackVariants(), + artifact.extensionParticipationMetadata() + ); + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrVendorOptimizationProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrVendorOptimizationProposalProvider.java new file mode 100644 index 00000000..c2850dba --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrVendorOptimizationProposalProvider.java @@ -0,0 +1,53 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.api.GpuBackendTarget; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionCapability; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionPermission; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionPhase; + +import java.util.Set; + +/** + * Optional vendor/device-specific proposal provider. + * + *

Vendor providers are isolated from the default backend-neutral proposal ServiceLoader path. Projects may + * load them explicitly through {@link GpuIrVendorOptimizationProposalRegistry} and adapt them into the common + * immutable proposal contract when they want vendor-aware proposal evidence.

+ */ +@FunctionalInterface +public interface GpuIrVendorOptimizationProposalProvider extends GpuIrOptimizationProposalProvider { + + GpuIrOptimizationProposal proposeForVendor(GpuIrVendorOptimizationProposalRequest request); + + @Override + default GpuIrOptimizationProposal propose(GpuIrOptimizationProposalRequest request) { + return proposeForVendor(GpuIrVendorOptimizationProposalRequest.from(request)); + } + + @Override + default Set extensionCapabilities() { + return Set.of(GpuExtensionCapability.IR_OPTIMIZATION_PROPOSAL); + } + + @Override + default GpuExtensionPhase extensionPhase() { + return GpuExtensionPhase.RUNTIME_IR_OPTIMIZATION; + } + + @Override + default GpuExtensionPermission extensionPermission() { + return GpuExtensionPermission.MUTATION_PROPOSAL; + } + + default GpuBackendTarget supportedBackendTarget() { + return GpuBackendTarget.UNKNOWN; + } + + default String supportedVendor() { + return "unknown"; + } + + default boolean supports(GpuIrVendorOptimizationProposalRequest request) { + return request.matches(supportedBackendTarget(), supportedVendor()); + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrVendorOptimizationProposalRegistry.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrVendorOptimizationProposalRegistry.java new file mode 100644 index 00000000..fbd6c536 --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrVendorOptimizationProposalRegistry.java @@ -0,0 +1,104 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.extension.GpuExtension; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; + +/** + * Deterministic registry for optional vendor-specific IR optimizer proposal providers. + */ +public final class GpuIrVendorOptimizationProposalRegistry { + + private final List providers; + private final Map rejectionReasons; + + private GpuIrVendorOptimizationProposalRegistry( + List providers, + Map rejectionReasons + ) { + this.providers = List.copyOf(providers); + this.rejectionReasons = Map.copyOf(rejectionReasons); + } + + public static GpuIrVendorOptimizationProposalRegistry empty() { + return new GpuIrVendorOptimizationProposalRegistry(List.of(), Map.of()); + } + + public static GpuIrVendorOptimizationProposalRegistry of( + List providers + ) { + return build(providers == null ? List.of() : providers); + } + + public static GpuIrVendorOptimizationProposalRegistry loadFromServiceLoader() { + ArrayList loaded = new ArrayList<>(); + ServiceLoader.load( + GpuIrVendorOptimizationProposalProvider.class, + GpuIrVendorOptimizationProposalProvider.class.getClassLoader() + ).forEach(loaded::add); + return build(loaded); + } + + public List providers() { + return providers; + } + + public Map rejectionReasons() { + return rejectionReasons; + } + + public GpuIrOptimizationProposalProvider asProposalProvider() { + return request -> { + GpuIrVendorOptimizationProposalRequest vendorRequest = + GpuIrVendorOptimizationProposalRequest.from(request); + for (GpuIrVendorOptimizationProposalProvider provider : providers) { + if (provider.supports(vendorRequest)) { + return provider.proposeForVendor(vendorRequest); + } + } + return GpuIrOptimizationProposal.noChange( + GpuIrOptimizerModule.VENDOR_PROVIDER_ADAPTER_ID, + GpuIrOptimizerModule.VENDOR_PROVIDER_ADAPTER_VERSION, + request.originalArtifact(), + "no vendor IR optimizer proposal provider matched backend=" + + vendorRequest.backendTarget() + + ", vendor=" + + vendorRequest.vendor() + ); + }; + } + + private static GpuIrVendorOptimizationProposalRegistry build( + List candidates + ) { + ArrayList accepted = new ArrayList<>(); + LinkedHashMap rejected = new LinkedHashMap<>(); + LinkedHashMap byId = new LinkedHashMap<>(); + for (GpuIrVendorOptimizationProposalProvider provider : candidates) { + if (provider == null) { + continue; + } + String extensionId = provider.extensionId(); + if (extensionId == null || extensionId.isBlank()) { + rejected.put(provider.getClass().getName(), "blank extension id"); + continue; + } + if (byId.containsKey(extensionId)) { + rejected.put(extensionId, "duplicate extension id"); + continue; + } + byId.put(extensionId, provider); + } + accepted.addAll(byId.values()); + accepted.sort(Comparator + .comparingInt(GpuExtension::extensionOrder) + .thenComparing(GpuExtension::extensionId) + .thenComparing(GpuExtension::extensionVersion)); + return new GpuIrVendorOptimizationProposalRegistry(accepted, rejected); + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrVendorOptimizationProposalRequest.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrVendorOptimizationProposalRequest.java new file mode 100644 index 00000000..982c095a --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrVendorOptimizationProposalRequest.java @@ -0,0 +1,101 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.api.GpuBackendTarget; +import net.sixik.ga_utils.javatogpu.api.GpuDeviceClassTarget; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; + +import java.util.Locale; +import java.util.Map; +import java.util.Objects; + +/** + * Backend/vendor-specific view over an immutable IR optimization proposal request. + */ +public record GpuIrVendorOptimizationProposalRequest( + GpuIrOptimizationProposalRequest proposalRequest, + GpuBackendTarget backendTarget, + String vendor, + String deviceId, + String deviceLabel, + String driverVersion, + String apiVersionText, + GpuDeviceClassTarget deviceClass, + Map contextFields +) { + + public GpuIrVendorOptimizationProposalRequest { + proposalRequest = Objects.requireNonNull(proposalRequest, "proposalRequest"); + backendTarget = backendTarget == null ? GpuBackendTarget.UNKNOWN : backendTarget; + vendor = normalize(vendor); + deviceId = normalize(deviceId); + deviceLabel = normalize(deviceLabel); + driverVersion = normalize(driverVersion); + apiVersionText = normalize(apiVersionText); + deviceClass = deviceClass == null || deviceClass == GpuDeviceClassTarget.ANY + ? GpuDeviceClassTarget.UNKNOWN + : deviceClass; + contextFields = contextFields == null ? Map.of() : Map.copyOf(contextFields); + } + + public static GpuIrVendorOptimizationProposalRequest from(GpuIrOptimizationProposalRequest request) { + Map fields = request.contextFields(); + return new GpuIrVendorOptimizationProposalRequest( + request, + parseBackend(fields.get("backendTarget")), + fields.get("deviceProfile.vendor"), + fields.get("deviceProfile.id"), + fields.get("deviceProfile.label"), + fields.get("deviceProfile.driverVersion"), + fields.get("deviceProfile.apiVersionText"), + parseDeviceClass(fields.get("deviceProfile.deviceClass")), + fields + ); + } + + public IrGpuArtifact originalArtifact() { + return proposalRequest.originalArtifact(); + } + + public String optimizerProfile() { + return proposalRequest.optimizerProfile(); + } + + public boolean mutationAllowed() { + return proposalRequest.mutationAllowed(); + } + + public boolean matches(GpuBackendTarget expectedBackend, String expectedVendor) { + if (expectedBackend != null && expectedBackend != GpuBackendTarget.UNKNOWN && backendTarget != expectedBackend) { + return false; + } + return expectedVendor == null + || expectedVendor.isBlank() + || vendor.equalsIgnoreCase(expectedVendor); + } + + private static GpuBackendTarget parseBackend(String value) { + if (value == null || value.isBlank()) { + return GpuBackendTarget.UNKNOWN; + } + try { + return GpuBackendTarget.valueOf(value.trim().toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException ignored) { + return GpuBackendTarget.UNKNOWN; + } + } + + private static GpuDeviceClassTarget parseDeviceClass(String value) { + if (value == null || value.isBlank()) { + return GpuDeviceClassTarget.UNKNOWN; + } + try { + return GpuDeviceClassTarget.valueOf(value.trim().toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException ignored) { + return GpuDeviceClassTarget.UNKNOWN; + } + } + + private static String normalize(String value) { + return value == null || value.isBlank() ? "unknown" : value; + } +} diff --git a/ir-optimizer/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrOptimizationProposalProvider b/ir-optimizer/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrOptimizationProposalProvider new file mode 100644 index 00000000..25084855 --- /dev/null +++ b/ir-optimizer/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrOptimizationProposalProvider @@ -0,0 +1,4 @@ +net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrNoOpProposalProvider +net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrTextCanonicalizationProposalProvider +net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrHelperDependencyDeduplicationProposalProvider +net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrConstantFoldingPreviewProposalProvider diff --git a/ir-optimizer/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrVendorOptimizationProposalProvider b/ir-optimizer/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrVendorOptimizationProposalProvider new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/ir-optimizer/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrVendorOptimizationProposalProvider @@ -0,0 +1 @@ + diff --git a/ir-optimizer/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationPass b/ir-optimizer/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationPass new file mode 100644 index 00000000..a6f392cf --- /dev/null +++ b/ir-optimizer/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationPass @@ -0,0 +1 @@ +net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrProposalRuntimeBridgePass diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingPreviewProposalProviderTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingPreviewProposalProviderTest.java new file mode 100644 index 00000000..dfe237c2 --- /dev/null +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingPreviewProposalProviderTest.java @@ -0,0 +1,175 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBodyIndex; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuSourceLocation; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuIrConstantFoldingPreviewProposalProviderTest { + + @Test + void recordsSimpleLiteralBinaryCandidatesWithoutProposingRewrite() { + IrGpuArtifact original = artifact(typedBody( + new IrGpuTypedNode(0, "GpuIrReturn", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(2), + "right", List.of(3) + )), + new IrGpuTypedNode(2, "GpuIrLiteral", Map.of("sourceText", "2"), Map.of()), + new IrGpuTypedNode(3, "GpuIrLiteral", Map.of("sourceText", "3"), Map.of()) + )); + + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingPreviewProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertFalse(proposal.hasOptimizedArtifact()); + assertSame(original, proposal.originalArtifact()); + assertEquals("preview-candidates-recorded", proposal.proofArtifact().verdict()); + assertEquals("1", proposal.proofArtifact().fields().get("candidate.count")); + assertEquals("false", proposal.proofArtifact().fields().get("rewrite.proposed")); + assertEquals("true", proposal.proofArtifact().fields().get("previewOnly")); + assertEquals("{+=1}", proposal.proofArtifact().fields().get("operator.counts")); + assertEquals("{integer=1}", proposal.proofArtifact().fields().get("numericKind.counts")); + assertEquals("+", proposal.proofArtifact().fields().get("firstOperator")); + assertEquals("integer", proposal.proofArtifact().fields().get("firstNumericKind")); + assertEquals("5", proposal.proofArtifact().fields().get("firstFoldedValue")); + assertTrue(proposal.diagnostics().get(0).contains("no rewrite was proposed")); + } + + @Test + void recordsOperatorAndNumericKindBreakdownAcrossCandidates() { + IrGpuArtifact original = artifact(typedBody( + new IrGpuTypedNode(0, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(1), + "right", List.of(2) + )), + new IrGpuTypedNode(1, "GpuIrLiteral", Map.of("sourceText", "2"), Map.of()), + new IrGpuTypedNode(2, "GpuIrLiteral", Map.of("sourceText", "3"), Map.of()), + new IrGpuTypedNode(3, "GpuIrBinary", Map.of("operator", "*"), Map.of( + "left", List.of(4), + "right", List.of(5) + )), + new IrGpuTypedNode(4, "GpuIrLiteral", Map.of("sourceText", "1.5"), Map.of()), + new IrGpuTypedNode(5, "GpuIrLiteral", Map.of("sourceText", "2"), Map.of()), + new IrGpuTypedNode(6, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(7), + "right", List.of(8) + )), + new IrGpuTypedNode(7, "GpuIrLiteral", Map.of("sourceText", "4"), Map.of()), + new IrGpuTypedNode(8, "GpuIrLiteral", Map.of("sourceText", "5"), Map.of()) + )); + + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingPreviewProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals("3", proposal.proofArtifact().fields().get("candidate.count")); + assertEquals("{*=1,+=2}", proposal.proofArtifact().fields().get("operator.counts")); + assertEquals("{decimal=1,integer=2}", proposal.proofArtifact().fields().get("numericKind.counts")); + assertEquals("+", proposal.proofArtifact().fields().get("firstOperator")); + assertEquals("integer", proposal.proofArtifact().fields().get("firstNumericKind")); + assertEquals("5", proposal.proofArtifact().fields().get("firstFoldedValue")); + } + + @Test + void skipsUnsafeOrNonPlainLiteralShapes() { + IrGpuArtifact original = artifact(typedBody( + new IrGpuTypedNode(0, "GpuIrBinary", Map.of("operator", "/"), Map.of( + "left", List.of(1), + "right", List.of(2) + )), + new IrGpuTypedNode(1, "GpuIrLiteral", Map.of("sourceText", "1"), Map.of()), + new IrGpuTypedNode(2, "GpuIrLiteral", Map.of("sourceText", "0"), Map.of()), + new IrGpuTypedNode(3, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(4), + "right", List.of(5) + )), + new IrGpuTypedNode(4, "GpuIrLiteral", Map.of("sourceText", "1.0f"), Map.of()), + new IrGpuTypedNode(5, "GpuIrLiteral", Map.of("sourceText", "2"), Map.of()) + )); + + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingPreviewProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertEquals("preview-no-candidates", proposal.proofArtifact().verdict()); + assertEquals("0", proposal.proofArtifact().fields().get("candidate.count")); + assertEquals("{}", proposal.proofArtifact().fields().get("operator.counts")); + assertEquals("{}", proposal.proofArtifact().fields().get("numericKind.counts")); + assertEquals("no-simple-literal-binary-candidates", proposal.proofArtifact().fields().get("firstBlocker")); + } + + @Test + void previewStaysNoChangeEvenWhenMutationAllowed() { + IrGpuArtifact original = artifact(typedBody( + new IrGpuTypedNode(0, "GpuIrBinary", Map.of("operator", "*"), Map.of( + "left", List.of(1), + "right", List.of(2) + )), + new IrGpuTypedNode(1, "GpuIrLiteral", Map.of("sourceText", "4"), Map.of()), + new IrGpuTypedNode(2, "GpuIrLiteral", Map.of("sourceText", "5"), Map.of()) + )); + + GpuIrOptimizationSandwichReport report = GpuIrOptimizationSandwichRunner.alwaysValid() + .run(new GpuIrOptimizationProposalRequest(original, "diagnostic", true, Map.of()), + new GpuIrConstantFoldingPreviewProposalProvider()); + + assertEquals(GpuIrOptimizationSandwichStatus.NO_CHANGE, report.status()); + assertSame(original, report.selectedArtifact()); + assertTrue(report.selectedOriginal()); + assertFalse(report.selectedOptimized()); + assertEquals("20", report.proposal().orElseThrow().proofArtifact().fields().get("firstFoldedValue")); + } + + @Test + void previewProviderIsServiceLoaded() { + List providers = ServiceLoader + .load(GpuIrOptimizationProposalProvider.class) + .stream() + .map(ServiceLoader.Provider::get) + .filter(GpuIrConstantFoldingPreviewProposalProvider.class::isInstance) + .toList(); + + assertEquals(1, providers.size()); + } + + private static IrGpuTypedBody typedBody(IrGpuTypedNode... nodes) { + return new IrGpuTypedBody(IrGpuTypedBody.FORMAT, List.of(nodes[0].id()), List.of(nodes)); + } + + private static IrGpuArtifact artifact(IrGpuTypedBody typedBody) { + IrGpuMethodBody methodBody = new IrGpuMethodBody( + "entry", + "kernel", + "jtg_kernel", + "ir-text-v1", + "body\n return 1\n", + typedBody, + IrGpuBodyIndex.empty(), + List.of(), + IrGpuSourceLocation.unknown("kernel") + ); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + new IrGpuModule("kernel", "jtg_kernel", List.of(), List.of(), List.of(methodBody)), + List.of(IrGpuBackendOutput.openClSource("test.cl")), + "opencl", + "off" + ); + } +} diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrHelperDependencyDeduplicationProposalProviderTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrHelperDependencyDeduplicationProposalProviderTest.java new file mode 100644 index 00000000..ee85bb9e --- /dev/null +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrHelperDependencyDeduplicationProposalProviderTest.java @@ -0,0 +1,118 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuIrHelperDependencyDeduplicationProposalProviderTest { + + @Test + void deduplicateKeepsFirstOccurrenceOrderAndDropsBlankEntries() { + assertEquals( + List.of("helperA", "helperB", "helperC"), + GpuIrHelperDependencyDeduplicationProposalProvider.deduplicate( + List.of("helperA", "helperB", "helperA", " ", "helperC", "helperB") + ) + ); + } + + @Test + void alreadyDeduplicatedDependenciesReturnNoChangeProposal() { + IrGpuArtifact original = artifact(List.of("helperA", "helperB")); + + GpuIrOptimizationProposal proposal = new GpuIrHelperDependencyDeduplicationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertFalse(proposal.hasOptimizedArtifact()); + assertSame(original, proposal.originalArtifact()); + assertTrue(proposal.diagnostics().get(0).contains("already deduplicated")); + } + + @Test + void duplicateDependenciesProduceDistinctOptimizedArtifact() { + IrGpuArtifact original = artifact(List.of("helperA", "helperB", "helperA", "helperC", "helperB")); + + GpuIrOptimizationProposal proposal = new GpuIrHelperDependencyDeduplicationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertNotSame(original, optimized); + assertSame(original.module().methodBodies().get(0).typedBody(), optimized.module().methodBodies().get(0).typedBody()); + assertEquals(original.module().methodBodies().get(0).body(), optimized.module().methodBodies().get(0).body()); + assertEquals(List.of("helperA", "helperB", "helperC"), optimized.module().methodBodies().get(0).helperDependencies()); + assertEquals(List.of("helperA", "helperB", "helperA", "helperC", "helperB"), original.module().methodBodies().get(0).helperDependencies()); + assertEquals("1", proposal.proofArtifact().fields().get("changedMethodBodies")); + assertEquals("2", proposal.proofArtifact().fields().get("removedDuplicateDependencies")); + assertEquals("first-occurrence", proposal.proofArtifact().fields().get("preservedOrder")); + } + + @Test + void deduplicationStaysProposalOnlyByDefault() { + IrGpuArtifact original = artifact(List.of("helperA", "helperA")); + + GpuIrOptimizationSandwichReport report = GpuIrOptimizationSandwichRunner.alwaysValid() + .run(new GpuIrOptimizationProposalRequest(original), new GpuIrHelperDependencyDeduplicationProposalProvider()); + + assertEquals(GpuIrOptimizationSandwichStatus.PROPOSAL_ONLY, report.status()); + assertSame(original, report.selectedArtifact()); + assertTrue(report.selectedOriginal()); + assertFalse(report.selectedOptimized()); + } + + @Test + void deduplicationCanBeSelectedWhenMutationAllowed() { + IrGpuArtifact original = artifact(List.of("helperA", "helperA")); + + GpuIrOptimizationSandwichReport report = GpuIrOptimizationSandwichRunner.alwaysValid() + .run(new GpuIrOptimizationProposalRequest(original, "diagnostic", true, Map.of()), + new GpuIrHelperDependencyDeduplicationProposalProvider()); + + assertEquals(GpuIrOptimizationSandwichStatus.OPTIMIZED_SELECTED, report.status()); + assertEquals(List.of("helperA"), report.selectedArtifact().module().methodBodies().get(0).helperDependencies()); + assertTrue(report.selectedOptimized()); + } + + @Test + void deduplicationProviderIsServiceLoaded() { + List providers = ServiceLoader + .load(GpuIrOptimizationProposalProvider.class) + .stream() + .map(ServiceLoader.Provider::get) + .filter(GpuIrHelperDependencyDeduplicationProposalProvider.class::isInstance) + .toList(); + + assertEquals(1, providers.size()); + } + + private static IrGpuArtifact artifact(List helperDependencies) { + IrGpuMethodBody methodBody = IrGpuMethodBody.entry( + "test.Kernel.run", + "run", + "body\n return original\n", + helperDependencies + ); + IrGpuModule module = new IrGpuModule("test.Kernel.run", "run", List.of(), List.of(), List.of(methodBody)); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + module, + List.of(IrGpuBackendOutput.openClSource("test.cl")), + "opencl", + "off" + ); + } +} diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrNoOpOptimizationPassTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrNoOpOptimizationPassTest.java new file mode 100644 index 00000000..ab66ffad --- /dev/null +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrNoOpOptimizationPassTest.java @@ -0,0 +1,87 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.api.GpuBackendTarget; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.runtime.GpuKernelDescriptor; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileOptions; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileRequest; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeDeviceProfile; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationOutcome; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationReport; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationRequest; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationStage; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizerRegistry; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuIrNoOpOptimizationPassTest { + + @Test + void noOpPassReportsSkippedWithoutChangingArtifact() { + IrGpuArtifact artifact = artifact("body\n return original\n"); + GpuRuntimeIrOptimizationReport report = new GpuIrNoOpOptimizationPass() + .run(new GpuRuntimeIrOptimizationRequest(request(artifact), Optional.of(artifact))); + + assertSame(artifact, report.artifact().orElseThrow()); + assertFalse(report.requiresRollback()); + assertEquals(1, report.passReports().size()); + assertEquals(GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, report.passReports().get(0).stage()); + assertEquals(GpuIrNoOpOptimizationPass.PASS_ID + ":" + GpuIrNoOpOptimizationPass.PASS_VERSION, + report.passReports().get(0).optimizerVersion()); + assertEquals(GpuRuntimeIrOptimizationOutcome.SKIPPED, report.passReports().get(0).outcome()); + assertTrue(report.passReports().get(0).toLine().contains("does not mutate IR")); + } + + @Test + void directSkeletonPassKeepsRuntimeRegistryNoOpSemantics() { + IrGpuArtifact artifact = artifact("body\n return original\n"); + + GpuRuntimeIrOptimizationReport report = GpuRuntimeIrOptimizerRegistry.ofPasses( + List.of(new GpuIrNoOpOptimizationPass()) + ) + .optimizeWithReport(new GpuRuntimeIrOptimizationRequest(request(artifact), Optional.of(artifact))); + + assertSame(artifact, report.artifact().orElseThrow()); + assertFalse(report.requiresRollback()); + assertEquals(1, report.passReports().size()); + assertEquals(GpuRuntimeIrOptimizationOutcome.SKIPPED, report.passReports().get(0).outcome()); + } + + private static GpuRuntimeCompileRequest request(IrGpuArtifact artifact) { + GpuRuntimeDeviceProfile profile = GpuRuntimeDeviceProfile.generic(GpuBackendTarget.OPENCL, "test-device"); + return new GpuRuntimeCompileRequest( + new GpuKernelDescriptor("run", "test.Kernel.run", "", "test.Kernel.run.irgpu", List.of()), + GpuRuntimeCompileOptions.defaults(GpuBackendTarget.OPENCL), + profile, + Optional.of(artifact) + ); + } + + private static IrGpuArtifact artifact(String body) { + IrGpuMethodBody methodBody = IrGpuMethodBody.entry( + "test.Kernel.run", + "run", + body, + List.of() + ); + IrGpuModule module = new IrGpuModule("test.Kernel.run", "run", List.of(), List.of(), List.of(methodBody)); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + module, + List.of(IrGpuBackendOutput.openClSource("test.cl")), + "opencl", + "off" + ); + } +} diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalManifestTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalManifestTest.java new file mode 100644 index 00000000..c40d1a9d --- /dev/null +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalManifestTest.java @@ -0,0 +1,178 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; +import org.junit.jupiter.api.Test; + +import java.io.StringReader; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuIrOptimizationApprovalManifestTest { + + @Test + void writesPendingTemplateBoundToProposalAndRuntimeContext() throws Exception { + GpuIrOptimizationProposal proposal = proposal(); + GpuIrOptimizationProposalRequest request = request(proposal.originalArtifact()); + + String template = GpuIrOptimizationApprovalManifest.template(proposal, request); + + assertTrue(template.contains("status=pending")); + assertTrue(template.contains("scope=" + GpuIrOptimizationApprovalManifest.SCOPE)); + assertTrue(template.contains("approval.id=REQUIRED")); + assertTrue(template.contains("binding.optimizerId=optimizer:test")); + assertTrue(template.contains("binding.originalIrIdentity=" + proposal.originalIdentity())); + assertTrue(template.contains("binding.optimizedIrIdentity=" + proposal.optimizedIdentity())); + assertTrue(template.contains("binding.backendTarget=OPENCL")); + assertTrue(template.contains("binding.deviceVendor=NVIDIA")); + assertTrue(template.contains("binding.proof.verdict=runtime-equivalence-passed")); + } + + @Test + void validatesApprovedManifestAgainstExactProposalAndContext() throws Exception { + GpuIrOptimizationProposal proposal = proposal(); + GpuIrOptimizationProposalRequest request = request(proposal.originalArtifact()); + Properties manifest = approvedManifest(proposal, request); + + GpuIrOptimizationApprovalManifest.Validation validation = + GpuIrOptimizationApprovalManifest.validate(proposal, request, manifest); + + assertTrue(validation.valid()); + assertEquals("approved", validation.status()); + assertEquals("approval:optimizer-test", validation.approvalId()); + assertEquals(proposal.originalIdentity(), validation.originalIrIdentity()); + assertEquals(proposal.optimizedIdentity(), validation.optimizedIrIdentity()); + assertTrue(validation.toPropertiesText().contains("valid=true")); + assertTrue(validation.toPropertiesText().contains("blocker.count=0")); + } + + @Test + void blocksManifestForDifferentOptimizedIdentity() throws Exception { + GpuIrOptimizationProposal proposal = proposal(); + GpuIrOptimizationProposalRequest request = request(proposal.originalArtifact()); + Properties manifest = approvedManifest(proposal, request); + manifest.setProperty("binding.optimizedIrIdentity", "irgpu:sha256:other"); + + GpuIrOptimizationApprovalManifest.Validation validation = + GpuIrOptimizationApprovalManifest.validate(proposal, request, manifest); + + assertFalse(validation.valid()); + assertTrue(validation.blockers().contains("manifest-optimized-ir-identity-mismatch")); + } + + @Test + void blocksManifestForDifferentDeviceBinding() throws Exception { + GpuIrOptimizationProposal proposal = proposal(); + GpuIrOptimizationProposalRequest request = request(proposal.originalArtifact()); + Properties manifest = approvedManifest(proposal, request); + manifest.setProperty("binding.deviceVendor", "AMD"); + + GpuIrOptimizationApprovalManifest.Validation validation = + GpuIrOptimizationApprovalManifest.validate(proposal, request, manifest); + + assertFalse(validation.valid()); + assertTrue(validation.blockers().contains("manifest-device-vendor-mismatch")); + } + + @Test + void templateRequiresRealProposedDistinctArtifactAndAcceptedProof() { + IrGpuArtifact artifact = artifact("body\n"); + GpuIrOptimizationProposal noChange = GpuIrOptimizationProposal.noChange( + "optimizer:test", + "optimizer:test:1", + artifact, + "no change" + ); + GpuIrOptimizationProposal missingProof = GpuIrOptimizationProposal.proposed( + "optimizer:test", + "optimizer:test:1", + artifact, + artifact("body changed\n"), + GpuRuntimeIrOptimizationProofArtifact.fromFields("ir-optimizer", "not-proven", Map.of()), + List.of() + ); + + IllegalStateException noChangeFailure = assertThrows( + IllegalStateException.class, + () -> GpuIrOptimizationApprovalManifest.template(noChange, new GpuIrOptimizationProposalRequest(artifact)) + ); + IllegalStateException proofFailure = assertThrows( + IllegalStateException.class, + () -> GpuIrOptimizationApprovalManifest.template(missingProof, request(artifact)) + ); + + assertTrue(noChangeFailure.getMessage().contains("proposal-decision-not-proposed")); + assertTrue(proofFailure.getMessage().contains("proposal-proof-source-not-specific")); + } + + private static GpuIrOptimizationProposal proposal() { + IrGpuArtifact original = artifact("body\n"); + IrGpuArtifact optimized = artifact("body optimized\n"); + return GpuIrOptimizationProposal.proposed( + "optimizer:test", + "optimizer:test:1", + original, + optimized, + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.test-proof", + "runtime-equivalence-passed", + Map.of("case.count", "1") + ), + List.of("test proposal") + ); + } + + private static GpuIrOptimizationProposalRequest request(IrGpuArtifact artifact) { + return new GpuIrOptimizationProposalRequest( + artifact, + "diagnostic", + false, + Map.of( + "backendTarget", "OPENCL", + "deviceProfile.vendor", "NVIDIA", + "deviceProfile.label", "RTX" + ) + ); + } + + private static Properties approvedManifest( + GpuIrOptimizationProposal proposal, + GpuIrOptimizationProposalRequest request + ) throws Exception { + String approved = GpuIrOptimizationApprovalManifest.template(proposal, request) + .replace("status=pending", "status=approved") + .replace("approval.id=REQUIRED", "approval.id=approval:optimizer-test") + .replace("approval.approvedBy=REQUIRED", "approval.approvedBy=CI") + .replace("approval.approvedAtUtc=REQUIRED", "approval.approvedAtUtc=2026-07-12T00:00:00Z"); + Properties properties = new Properties(); + properties.load(new StringReader(approved)); + return properties; + } + + private static IrGpuArtifact artifact(String body) { + IrGpuMethodBody methodBody = IrGpuMethodBody.entry( + "test.Kernel.run", + "run", + body, + List.of() + ); + IrGpuModule module = new IrGpuModule("test.Kernel.run", "run", List.of(), List.of(), List.of(methodBody)); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + module, + List.of(IrGpuBackendOutput.openClSource("test.cl")), + "opencl", + "off" + ); + } +} diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalTemplateFormatterTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalTemplateFormatterTest.java new file mode 100644 index 00000000..142fdaa2 --- /dev/null +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalTemplateFormatterTest.java @@ -0,0 +1,134 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuIrOptimizationApprovalTemplateFormatterTest { + + @Test + void returnsNotApplicableForPreviewNoChangeProposal() { + IrGpuArtifact original = artifact("body\n"); + GpuIrOptimizationProposal preview = new GpuIrConstantFoldingPreviewProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + GpuIrOptimizationApprovalTemplateResult result = GpuIrOptimizationApprovalTemplateFormatter.format( + preview, + request(original) + ); + + assertFalse(result.applicable()); + assertEquals("not-applicable", result.status()); + assertEquals("proposal-decision-not-proposed", result.firstBlocker()); + assertTrue(result.templateText().isBlank()); + assertEquals("NO_CHANGE", result.fields().get("decision")); + assertTrue(result.toPropertiesText().contains("template.present=false")); + } + + @Test + void returnsPendingTemplateForRealProposedRewrite() { + IrGpuArtifact original = artifact("body\n"); + GpuIrOptimizationProposal proposal = proposed(original, artifact("body optimized\n")); + + GpuIrOptimizationApprovalTemplateResult result = GpuIrOptimizationApprovalTemplateFormatter.format( + proposal, + request(original) + ); + + assertTrue(result.applicable()); + assertEquals("pending", result.status()); + assertEquals("none", result.firstBlocker()); + assertTrue(result.templateText().contains("status=pending")); + assertTrue(result.templateText().contains("binding.optimizerId=optimizer:test")); + assertTrue(result.templateText().contains("authorization.productionMutation=disabled")); + assertTrue(result.toPropertiesText().contains("template.present=true")); + assertTrue(result.toPropertiesText().contains("template.resourceDirectory=META-INF/javatogpu/ir-optimization-approvals/")); + } + + @Test + void returnsNotApplicableForMalformedProposedRewrite() { + IrGpuArtifact original = artifact("body\n"); + GpuIrOptimizationProposal proposal = proposed( + original, + artifact("body optimized\n"), + GpuRuntimeIrOptimizationProofArtifact.fromFields("ir-optimizer", "not-proven", Map.of()) + ); + + GpuIrOptimizationApprovalTemplateResult result = GpuIrOptimizationApprovalTemplateFormatter.format( + proposal, + request(original) + ); + + assertFalse(result.applicable()); + assertEquals("proposal-proof-source-not-specific", result.firstBlocker()); + assertTrue(result.fields().get("diagnostic").contains("proposal-proof-source-not-specific")); + } + + private static GpuIrOptimizationProposal proposed(IrGpuArtifact original, IrGpuArtifact optimized) { + return proposed( + original, + optimized, + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.test-proof", + "runtime-equivalence-passed", + Map.of("case.count", "1") + ) + ); + } + + private static GpuIrOptimizationProposal proposed( + IrGpuArtifact original, + IrGpuArtifact optimized, + GpuRuntimeIrOptimizationProofArtifact proofArtifact + ) { + return GpuIrOptimizationProposal.proposed( + "optimizer:test", + "optimizer:test:1", + original, + optimized, + proofArtifact, + List.of("test proposal") + ); + } + + private static GpuIrOptimizationProposalRequest request(IrGpuArtifact artifact) { + return new GpuIrOptimizationProposalRequest( + artifact, + "diagnostic", + false, + Map.of( + "backendTarget", "OPENCL", + "deviceProfile.vendor", "NVIDIA", + "deviceProfile.label", "RTX" + ) + ); + } + + private static IrGpuArtifact artifact(String body) { + IrGpuMethodBody methodBody = IrGpuMethodBody.entry( + "test.Kernel.run", + "run", + body, + List.of() + ); + IrGpuModule module = new IrGpuModule("test.Kernel.run", "run", List.of(), List.of(), List.of(methodBody)); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + module, + List.of(IrGpuBackendOutput.openClSource("test.cl")), + "opencl", + "off" + ); + } +} diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationPolicyTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationPolicyTest.java new file mode 100644 index 00000000..08bdd28f --- /dev/null +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationPolicyTest.java @@ -0,0 +1,106 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuIrOptimizationPolicyTest { + + @Test + void defaultPolicyIsProposalOnlyAndFailClosed() { + GpuIrOptimizationPolicy policy = GpuIrOptimizationPolicy.off(); + + assertEquals("off", policy.optimizerProfile()); + assertEquals("off", policy.optimizationLevel()); + assertTrue(policy.proposalOnly()); + assertFalse(policy.mutationAllowed()); + assertFalse(policy.fastMathAllowed()); + assertFalse(policy.vendorAdaptationAllowed()); + assertFalse(policy.registerPressureSplittingAllowed()); + assertTrue(policy.rollbackRequired()); + assertTrue(policy.proofRequired()); + } + + @Test + void policyNormalizesLegacyAndExplicitContextFields() { + GpuIrOptimizationPolicy policy = GpuIrOptimizationPolicy.fromContext( + "diagnostic", + false, + Map.of( + "optimizationLevel", "level-2", + "mutationAllowed", "enabled", + "proposalOnly", "false", + "fastMathAllowed", "yes", + "vendorAdaptationAllowed", "allowed", + "registerPressureSplittingAllowed", "true", + "rollbackRequired", "required", + "proofRequired", "optional" + ) + ); + + assertEquals("diagnostic", policy.optimizerProfile()); + assertEquals("level-2", policy.optimizationLevel()); + assertFalse(policy.proposalOnly()); + assertTrue(policy.mutationAllowed()); + assertTrue(policy.fastMathAllowed()); + assertTrue(policy.vendorAdaptationAllowed()); + assertTrue(policy.registerPressureSplittingAllowed()); + assertTrue(policy.rollbackRequired()); + assertFalse(policy.proofRequired()); + } + + @Test + void proposalRequestPreservesPolicyAsBackendNeutralContext() { + GpuIrOptimizationPolicy policy = new GpuIrOptimizationPolicy( + "diagnostic", + "level-1", + false, + true, + true, + false, + true, + true, + true + ); + + GpuIrOptimizationProposalRequest request = new GpuIrOptimizationProposalRequest( + artifact(), + policy, + Map.of("backendTarget", "OPENCL") + ); + + assertEquals("diagnostic", request.optimizerProfile()); + assertTrue(request.mutationAllowed()); + assertEquals(policy, request.policy()); + assertEquals("OPENCL", request.contextFields().get("backendTarget")); + assertEquals("diagnostic", request.contextFields().get(GpuIrOptimizationPolicy.OPTIMIZER_PROFILE_FIELD)); + assertEquals("true", request.contextFields().get(GpuIrOptimizationPolicy.FAST_MATH_ALLOWED_FIELD)); + } + + private static IrGpuArtifact artifact() { + IrGpuMethodBody methodBody = IrGpuMethodBody.entry( + "test.Kernel.run", + "run", + "body\n", + List.of() + ); + IrGpuModule module = new IrGpuModule("test.Kernel.run", "run", List.of(), List.of(), List.of(methodBody)); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + module, + List.of(IrGpuBackendOutput.openClSource("test.cl")), + "opencl", + "off" + ); + } +} diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalRegistryTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalRegistryTest.java new file mode 100644 index 00000000..163fc9c7 --- /dev/null +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalRegistryTest.java @@ -0,0 +1,70 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuIrOptimizationProposalRegistryTest { + + @Test + void registryOrdersProvidersAndRejectsDuplicateIds() { + GpuIrOptimizationProposalRegistry registry = GpuIrOptimizationProposalRegistry.of(List.of( + new Provider("z-provider", 20), + new Provider("a-provider", 10), + new Provider("a-provider", 30) + )); + + assertEquals(2, registry.providers().size()); + assertEquals("a-provider", registry.providers().get(0).extensionId()); + assertEquals("z-provider", registry.providers().get(1).extensionId()); + assertEquals("duplicate extension id", registry.rejectionReasons().get("a-provider")); + } + + @Test + void serviceLoaderRegistryLoadsBackendNeutralProvidersOnly() { + List providerIds = GpuIrOptimizationProposalRegistry.loadFromServiceLoader() + .providers() + .stream() + .map(GpuIrOptimizationProposalProvider::extensionId) + .toList(); + + assertTrue(providerIds.contains(GpuIrNoOpOptimizationPass.PASS_ID)); + assertTrue(providerIds.contains(GpuIrTextCanonicalizationProposalProvider.PROVIDER_ID)); + assertTrue(providerIds.contains(GpuIrHelperDependencyDeduplicationProposalProvider.PROVIDER_ID)); + assertTrue(providerIds.contains(GpuIrConstantFoldingPreviewProposalProvider.PROVIDER_ID)); + assertTrue(providerIds.stream().noneMatch(id -> id.contains("vendor"))); + } + + private record Provider(String extensionId, int extensionOrder) implements GpuIrOptimizationProposalProvider { + + @Override + public GpuIrOptimizationProposal propose(GpuIrOptimizationProposalRequest request) { + IrGpuArtifact original = request.originalArtifact(); + return GpuIrOptimizationProposal.noChange( + extensionId(), + extensionVersion(), + original, + "test provider" + ); + } + + @Override + public String extensionId() { + return extensionId; + } + + @Override + public String extensionVersion() { + return extensionId + ":1"; + } + + @Override + public int extensionOrder() { + return extensionOrder; + } + } +} diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalTest.java new file mode 100644 index 00000000..87eb5c3f --- /dev/null +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalTest.java @@ -0,0 +1,77 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +class GpuIrOptimizationProposalTest { + + @Test + void proposedRewriteRejectsSameArtifactInstance() { + IrGpuArtifact original = artifact("body\n return original\n"); + + assertThrows( + IllegalArgumentException.class, + () -> GpuIrOptimizationProposal.proposed( + "test.optimizer", + "1", + original, + original, + proof(), + List.of() + ) + ); + } + + @Test + void proposedRewriteRejectsUnchangedArtifactIdentity() { + IrGpuArtifact original = artifact("body\n return original\n"); + IrGpuArtifact unchangedCopy = artifact("body\n return original\n"); + + assertThrows( + IllegalArgumentException.class, + () -> GpuIrOptimizationProposal.proposed( + "test.optimizer", + "1", + original, + unchangedCopy, + proof(), + List.of() + ) + ); + } + + private static GpuRuntimeIrOptimizationProofArtifact proof() { + return GpuRuntimeIrOptimizationProofArtifact.fromFields( + "test.optimizer", + "accepted", + Map.of("proof", "test") + ); + } + + private static IrGpuArtifact artifact(String body) { + IrGpuMethodBody methodBody = IrGpuMethodBody.entry( + "test.Kernel.run", + "run", + body, + List.of() + ); + IrGpuModule module = new IrGpuModule("test.Kernel.run", "run", List.of(), List.of(), List.of(methodBody)); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + module, + List.of(IrGpuBackendOutput.openClSource("test.cl")), + "opencl", + "off" + ); + } +} diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichRunnerTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichRunnerTest.java new file mode 100644 index 00000000..4704b29a --- /dev/null +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichRunnerTest.java @@ -0,0 +1,174 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuIrOptimizationSandwichRunnerTest { + + @Test + void noOpProposalKeepsOriginalSelected() { + IrGpuArtifact original = artifact("body\n return original\n"); + + GpuIrOptimizationSandwichReport report = GpuIrOptimizationSandwichRunner.alwaysValid() + .run(new GpuIrOptimizationProposalRequest(original), new GpuIrNoOpProposalProvider()); + + assertEquals(GpuIrOptimizationSandwichStatus.NO_CHANGE, report.status()); + assertSame(original, report.selectedArtifact()); + assertTrue(report.selectedOriginal()); + assertFalse(report.selectedOptimized()); + assertFalse(report.requiresRollback()); + assertTrue(report.proposal().orElseThrow().diagnostics().get(0).contains("keeps original IR")); + } + + @Test + void optimizedArtifactStaysProposalOnlyWhenMutationDisabled() { + IrGpuArtifact original = artifact("body\n return original\n"); + IrGpuArtifact optimized = artifact("body\n return optimized\n"); + GpuIrOptimizationProposalProvider provider = request -> proposed(original, optimized); + + GpuIrOptimizationSandwichReport report = GpuIrOptimizationSandwichRunner.alwaysValid() + .run(new GpuIrOptimizationProposalRequest(original), provider); + + assertEquals(GpuIrOptimizationSandwichStatus.PROPOSAL_ONLY, report.status()); + assertSame(original, report.selectedArtifact()); + assertTrue(report.selectedOriginal()); + assertFalse(report.selectedOptimized()); + assertTrue(report.diagnostics().contains( + "optimized artifact validated but mutation is disabled; original IR remains selected" + )); + } + + @Test + void optimizedArtifactCanBeSelectedWhenMutationAllowedAndValidationPasses() { + IrGpuArtifact original = artifact("body\n return original\n"); + IrGpuArtifact optimized = artifact("body\n return optimized\n"); + GpuIrOptimizationProposalProvider provider = request -> proposed(original, optimized); + + GpuIrOptimizationSandwichReport report = GpuIrOptimizationSandwichRunner.alwaysValid() + .run(new GpuIrOptimizationProposalRequest(original, "diagnostic", true, Map.of()), provider); + + assertEquals(GpuIrOptimizationSandwichStatus.OPTIMIZED_SELECTED, report.status()); + assertSame(optimized, report.selectedArtifact()); + assertFalse(report.selectedOriginal()); + assertTrue(report.selectedOptimized()); + assertFalse(report.requiresRollback()); + } + + @Test + void invalidOptimizedArtifactRollsBackToOriginal() { + IrGpuArtifact original = artifact("body\n return original\n"); + IrGpuArtifact optimized = artifact("body\n return optimized\n"); + GpuIrOptimizationValidationGate validationGate = request -> { + if (request.stage() == GpuIrOptimizationValidationStage.OPTIMIZED_AFTER) { + return GpuIrOptimizationValidationResult.invalid( + request.stage(), + "post-validation-failed", + "optimized artifact rejected" + ); + } + return GpuIrOptimizationValidationResult.valid(request.stage()); + }; + + GpuIrOptimizationSandwichReport report = new GpuIrOptimizationSandwichRunner(validationGate) + .run(new GpuIrOptimizationProposalRequest(original, "diagnostic", true, Map.of()), + request -> proposed(original, optimized)); + + assertEquals(GpuIrOptimizationSandwichStatus.OPTIMIZED_INVALID_ROLLED_BACK, report.status()); + assertSame(original, report.selectedArtifact()); + assertTrue(report.selectedOriginal()); + assertTrue(report.requiresRollback()); + assertEquals("post-validation-failed", report.optimizedValidation().orElseThrow().verdict()); + assertTrue(report.diagnostics().contains("optimized artifact rejected")); + } + + @Test + void invalidOriginalStopsBeforeProviderRuns() { + IrGpuArtifact original = artifact("body\n return original\n"); + GpuIrOptimizationValidationGate validationGate = request -> GpuIrOptimizationValidationResult.invalid( + request.stage(), + "original-invalid", + "original artifact rejected" + ); + + GpuIrOptimizationSandwichReport report = new GpuIrOptimizationSandwichRunner(validationGate) + .run(new GpuIrOptimizationProposalRequest(original), request -> { + throw new AssertionError("provider must not run when original validation fails"); + }); + + assertEquals(GpuIrOptimizationSandwichStatus.ORIGINAL_INVALID, report.status()); + assertSame(original, report.selectedArtifact()); + assertTrue(report.proposal().isEmpty()); + assertTrue(report.diagnostics().contains("original artifact rejected")); + } + + @Test + void malformedProposedDecisionWithoutArtifactIsRejected() { + IrGpuArtifact original = artifact("body\n return original\n"); + + assertThrows(IllegalArgumentException.class, () -> new GpuIrOptimizationProposal( + "optimizer:bad", + "optimizer:bad:1", + original, + java.util.Optional.empty(), + GpuIrOptimizationProposalDecision.PROPOSED, + GpuRuntimeIrOptimizationProofArtifact.fromFields("test", "bad", Map.of()), + "", + List.of() + )); + } + + @Test + void noOpProposalProviderIsServiceLoaded() { + List providers = ServiceLoader + .load(GpuIrOptimizationProposalProvider.class) + .stream() + .map(ServiceLoader.Provider::get) + .filter(GpuIrNoOpProposalProvider.class::isInstance) + .toList(); + + assertEquals(1, providers.size()); + } + + private static GpuIrOptimizationProposal proposed(IrGpuArtifact original, IrGpuArtifact optimized) { + return GpuIrOptimizationProposal.proposed( + "optimizer:test", + "optimizer:test:1", + original, + optimized, + GpuRuntimeIrOptimizationProofArtifact.fromFields("test", "proof-collected", Map.of()), + List.of("test proposal") + ); + } + + private static IrGpuArtifact artifact(String body) { + IrGpuMethodBody methodBody = IrGpuMethodBody.entry( + "test.Kernel.run", + "run", + body, + List.of() + ); + IrGpuModule module = new IrGpuModule("test.Kernel.run", "run", List.of(), List.of(), List.of(methodBody)); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + module, + List.of(IrGpuBackendOutput.openClSource("test.cl")), + "opencl", + "off" + ); + } +} diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePassTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePassTest.java new file mode 100644 index 00000000..f2c249b5 --- /dev/null +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePassTest.java @@ -0,0 +1,198 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.api.GpuBackendTarget; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.runtime.GpuKernelDescriptor; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileOptions; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileRequest; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeDeviceProfile; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationOutcome; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationPass; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationReport; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationRequest; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationStage; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizerRegistry; +import org.junit.jupiter.api.Test; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.ServiceLoader; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuIrProposalRuntimeBridgePassTest { + + @Test + void runtimeBridgeIsTheOnlyServiceLoadedRuntimePassFromModule() { + List passes = ServiceLoader + .load(GpuRuntimeIrOptimizationPass.class) + .stream() + .map(ServiceLoader.Provider::get) + .filter(pass -> pass.getClass().getName().startsWith("net.sixik.ga_utils.javatogpu.iroptimizer")) + .toList(); + + assertEquals(1, passes.size()); + assertInstanceOf(GpuIrProposalRuntimeBridgePass.class, passes.get(0)); + assertEquals(GpuIrProposalRuntimeBridgePass.PASS_VERSION, passes.get(0).passVersion()); + } + + @Test + void bridgeLoadsProposalProvidersInDeterministicOrder() { + GpuIrProposalRuntimeBridgePass bridge = new GpuIrProposalRuntimeBridgePass(); + + assertEquals(4, bridge.proposalProviders().size()); + assertInstanceOf(GpuIrNoOpProposalProvider.class, bridge.proposalProviders().get(0)); + assertInstanceOf(GpuIrTextCanonicalizationProposalProvider.class, bridge.proposalProviders().get(1)); + assertInstanceOf(GpuIrHelperDependencyDeduplicationProposalProvider.class, bridge.proposalProviders().get(2)); + assertInstanceOf(GpuIrConstantFoldingPreviewProposalProvider.class, bridge.proposalProviders().get(3)); + } + + @Test + void modulePublishesOnlyBackendNeutralProposalProviderDescriptorEntries() throws Exception { + List neutralDescriptor = serviceDescriptorLines(GpuIrOptimizationProposalProvider.class.getName()); + List vendorDescriptor = serviceDescriptorLines(GpuIrVendorOptimizationProposalProvider.class.getName()); + + assertTrue(neutralDescriptor.contains(GpuIrNoOpProposalProvider.class.getName())); + assertTrue(neutralDescriptor.contains(GpuIrTextCanonicalizationProposalProvider.class.getName())); + assertTrue(neutralDescriptor.contains(GpuIrHelperDependencyDeduplicationProposalProvider.class.getName())); + assertTrue(neutralDescriptor.contains(GpuIrConstantFoldingPreviewProposalProvider.class.getName())); + assertTrue(neutralDescriptor.stream().noneMatch(value -> value.contains("irvendoroptimizer"))); + assertTrue(vendorDescriptor.isEmpty()); + } + + @Test + void bridgeKeepsCanonicalizationProposalOnlyByDefault() { + IrGpuArtifact original = artifact("body \r\n return original\t\r\n"); + + GpuRuntimeIrOptimizationReport report = new GpuIrProposalRuntimeBridgePass() + .run(new GpuRuntimeIrOptimizationRequest(request(original), Optional.of(original))); + + assertSame(original, report.artifact().orElseThrow()); + assertFalse(report.requiresRollback()); + assertEquals(4, report.passReports().size()); + assertEquals(GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, report.passReports().get(1).stage()); + assertEquals(GpuRuntimeIrOptimizationOutcome.SKIPPED, report.passReports().get(1).outcome()); + assertEquals("proposal-only", report.passReports().get(1).proofStatus()); + assertTrue(report.passReports().get(1).diagnostics().contains( + "optimized artifact validated but mutation is disabled; original IR remains selected" + )); + } + + @Test + void bridgeCanSelectOptimizedArtifactWhenExplicitlyAllowed() { + IrGpuArtifact original = artifact("body \r\n return original\t\r\n"); + GpuIrProposalRuntimeBridgePass bridge = new GpuIrProposalRuntimeBridgePass( + List.of(new GpuIrTextCanonicalizationProposalProvider()), + GpuIrOptimizationSandwichRunner.alwaysValid(), + true + ); + + GpuRuntimeIrOptimizationReport report = bridge + .run(new GpuRuntimeIrOptimizationRequest(request(original), Optional.of(original))); + + assertEquals("body\n return original\n", report.artifact().orElseThrow().module().methodBodies().get(0).body()); + assertEquals(1, report.passReports().size()); + assertEquals(GpuRuntimeIrOptimizationOutcome.APPLIED, report.passReports().get(0).outcome()); + assertEquals("optimized-selected", report.passReports().get(0).proofStatus()); + assertFalse(report.requiresRollback()); + } + + @Test + void bridgeRollsBackWhenSandwichRejectsOptimizedArtifact() { + IrGpuArtifact original = artifact("body \r\n return original\t\r\n"); + GpuIrOptimizationValidationGate validationGate = validationRequest -> { + if (validationRequest.stage() == GpuIrOptimizationValidationStage.OPTIMIZED_AFTER) { + return GpuIrOptimizationValidationResult.invalid( + validationRequest.stage(), + "optimized-invalid", + "bridge test rejected optimized artifact" + ); + } + return GpuIrOptimizationValidationResult.valid(validationRequest.stage()); + }; + GpuIrProposalRuntimeBridgePass bridge = new GpuIrProposalRuntimeBridgePass( + List.of(new GpuIrTextCanonicalizationProposalProvider()), + new GpuIrOptimizationSandwichRunner(validationGate), + true + ); + + GpuRuntimeIrOptimizationReport report = bridge + .run(new GpuRuntimeIrOptimizationRequest(request(original), Optional.of(original))); + + assertSame(original, report.artifact().orElseThrow()); + assertTrue(report.requiresRollback()); + assertEquals(GpuRuntimeIrOptimizationOutcome.ROLLED_BACK, report.passReports().get(0).outcome()); + assertEquals("optimized-invalid", report.passReports().get(0).proofStatus()); + assertEquals("optimized artifact rejected by validation sandwich", report.passReports().get(0).rollbackReason()); + } + + @Test + void runtimeRegistrySeesBridgeButKeepsOriginalIrByDefault() { + IrGpuArtifact original = artifact("body \r\n return original\t\r\n"); + + GpuRuntimeIrOptimizationReport report = GpuRuntimeIrOptimizerRegistry.loadFromServiceLoader() + .optimizeWithReport(new GpuRuntimeIrOptimizationRequest(request(original), Optional.of(original))); + + assertSame(original, report.artifact().orElseThrow()); + assertTrue(report.passReports().stream().anyMatch(passReport -> + GpuIrProposalRuntimeBridgePass.PASS_VERSION.equals(passReport.optimizerVersion()) + || passReport.optimizerVersion().startsWith(GpuIrOptimizerModule.MODULE_ID) + )); + } + + private static GpuRuntimeCompileRequest request(IrGpuArtifact artifact) { + GpuRuntimeDeviceProfile profile = GpuRuntimeDeviceProfile.generic(GpuBackendTarget.OPENCL, "test-device"); + return new GpuRuntimeCompileRequest( + new GpuKernelDescriptor("run", "test.Kernel.run", "", "test.Kernel.run.irgpu", List.of()), + GpuRuntimeCompileOptions.openCl(List.of(), "diagnostic"), + profile, + Optional.of(artifact) + ); + } + + private static IrGpuArtifact artifact(String body) { + IrGpuMethodBody methodBody = IrGpuMethodBody.entry( + "test.Kernel.run", + "run", + body, + List.of() + ); + IrGpuModule module = new IrGpuModule("test.Kernel.run", "run", List.of(), List.of(), List.of(methodBody)); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + module, + List.of(IrGpuBackendOutput.openClSource("test.cl")), + "opencl", + "off" + ); + } + + private static List serviceDescriptorLines(String serviceName) throws Exception { + String resourceName = "META-INF/services/" + serviceName; + ClassLoader classLoader = GpuIrProposalRuntimeBridgePassTest.class.getClassLoader(); + try (java.io.InputStream stream = classLoader.getResourceAsStream(resourceName)) { + if (stream == null) { + return List.of(); + } + try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { + return reader.lines() + .map(String::trim) + .filter(line -> !line.isBlank()) + .filter(line -> !line.startsWith("#")) + .toList(); + } + } + } +} diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTextCanonicalizationProposalProviderTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTextCanonicalizationProposalProviderTest.java new file mode 100644 index 00000000..eeb959a8 --- /dev/null +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTextCanonicalizationProposalProviderTest.java @@ -0,0 +1,115 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuIrTextCanonicalizationProposalProviderTest { + + @Test + void canonicalizeNormalizesLineEndingsAndTrailingWhitespace() { + assertEquals( + "body\n return value\n", + GpuIrTextCanonicalizationProposalProvider.canonicalize("body \r\n return value\t\r\n") + ); + } + + @Test + void alreadyCanonicalBodyReturnsNoChangeProposal() { + IrGpuArtifact original = artifact("body\n return original\n"); + + GpuIrOptimizationProposal proposal = new GpuIrTextCanonicalizationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertFalse(proposal.hasOptimizedArtifact()); + assertSame(original, proposal.originalArtifact()); + assertTrue(proposal.diagnostics().get(0).contains("already canonical")); + } + + @Test + void nonCanonicalBodyProducesDistinctOptimizedArtifact() { + IrGpuArtifact original = artifact("body \r\n return original\t\r\n"); + + GpuIrOptimizationProposal proposal = new GpuIrTextCanonicalizationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertNotSame(original, optimized); + assertSame(original.module().methodBodies().get(0).typedBody(), optimized.module().methodBodies().get(0).typedBody()); + assertEquals("body\n return original\n", optimized.module().methodBodies().get(0).body()); + assertEquals("body \r\n return original\t\r\n", original.module().methodBodies().get(0).body()); + assertEquals("1", proposal.proofArtifact().fields().get("changedMethodBodies")); + assertEquals("false", proposal.proofArtifact().fields().get("mutationRequired")); + } + + @Test + void canonicalizationStaysProposalOnlyByDefault() { + IrGpuArtifact original = artifact("body \r\n return original\t\r\n"); + GpuIrTextCanonicalizationProposalProvider provider = new GpuIrTextCanonicalizationProposalProvider(); + + GpuIrOptimizationSandwichReport report = GpuIrOptimizationSandwichRunner.alwaysValid() + .run(new GpuIrOptimizationProposalRequest(original), provider); + + assertEquals(GpuIrOptimizationSandwichStatus.PROPOSAL_ONLY, report.status()); + assertSame(original, report.selectedArtifact()); + assertTrue(report.selectedOriginal()); + assertFalse(report.selectedOptimized()); + } + + @Test + void canonicalizationCanBeSelectedWhenMutationAllowed() { + IrGpuArtifact original = artifact("body \r\n return original\t\r\n"); + GpuIrTextCanonicalizationProposalProvider provider = new GpuIrTextCanonicalizationProposalProvider(); + + GpuIrOptimizationSandwichReport report = GpuIrOptimizationSandwichRunner.alwaysValid() + .run(new GpuIrOptimizationProposalRequest(original, "diagnostic", true, Map.of()), provider); + + assertEquals(GpuIrOptimizationSandwichStatus.OPTIMIZED_SELECTED, report.status()); + assertEquals("body\n return original\n", report.selectedArtifact().module().methodBodies().get(0).body()); + assertTrue(report.selectedOptimized()); + } + + @Test + void canonicalizationProviderIsServiceLoaded() { + List providers = ServiceLoader + .load(GpuIrOptimizationProposalProvider.class) + .stream() + .map(ServiceLoader.Provider::get) + .filter(GpuIrTextCanonicalizationProposalProvider.class::isInstance) + .toList(); + + assertEquals(1, providers.size()); + } + + private static IrGpuArtifact artifact(String body) { + IrGpuMethodBody methodBody = IrGpuMethodBody.entry( + "test.Kernel.run", + "run", + body, + List.of() + ); + IrGpuModule module = new IrGpuModule("test.Kernel.run", "run", List.of(), List.of(), List.of(methodBody)); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + module, + List.of(IrGpuBackendOutput.openClSource("test.cl")), + "opencl", + "off" + ); + } +} diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrVendorOptimizationProposalRegistryTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrVendorOptimizationProposalRegistryTest.java new file mode 100644 index 00000000..9271c167 --- /dev/null +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrVendorOptimizationProposalRegistryTest.java @@ -0,0 +1,183 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.api.GpuBackendTarget; +import net.sixik.ga_utils.javatogpu.api.GpuDeviceClassTarget; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuIrVendorOptimizationProposalRegistryTest { + + @Test + void registryOrdersProvidersAndRejectsDuplicateIds() { + GpuIrVendorOptimizationProposalRegistry registry = GpuIrVendorOptimizationProposalRegistry.of(List.of( + new VendorProvider("z-provider", GpuBackendTarget.OPENCL, "AMD", 20, false), + new VendorProvider("a-provider", GpuBackendTarget.OPENCL, "NVIDIA", 10, false), + new VendorProvider("a-provider", GpuBackendTarget.OPENCL, "Intel", 30, false) + )); + + assertEquals(2, registry.providers().size()); + assertEquals("a-provider", registry.providers().get(0).extensionId()); + assertEquals("z-provider", registry.providers().get(1).extensionId()); + assertEquals("duplicate extension id", registry.rejectionReasons().get("a-provider")); + } + + @Test + void adapterReturnsNoChangeWhenNoVendorProviderMatches() { + IrGpuArtifact original = artifact("body\n"); + GpuIrOptimizationProposalRequest request = request( + original, + Map.of( + "backendTarget", "OPENCL", + "deviceProfile.vendor", "Intel", + "deviceProfile.deviceClass", "IGPU" + ) + ); + GpuIrVendorOptimizationProposalRegistry registry = GpuIrVendorOptimizationProposalRegistry.of(List.of( + new VendorProvider("nvidia-provider", GpuBackendTarget.OPENCL, "NVIDIA", 0, false) + )); + + GpuIrOptimizationProposal proposal = registry.asProposalProvider().propose(request); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertFalse(proposal.hasOptimizedArtifact()); + assertSame(original, proposal.originalArtifact()); + assertTrue(proposal.diagnostics().get(0).contains("no vendor IR optimizer proposal provider matched")); + } + + @Test + void adapterRunsMatchingVendorProviderThroughCommonProposalContract() { + IrGpuArtifact original = artifact("body\n"); + GpuIrOptimizationProposalRequest request = request( + original, + Map.of( + "backendTarget", "OPENCL", + "deviceProfile.vendor", "NVIDIA", + "deviceProfile.id", "gpu-0", + "deviceProfile.label", "RTX", + "deviceProfile.driverVersion", "595.97", + "deviceProfile.apiVersionText", "OpenCL 3.0", + "deviceProfile.deviceClass", "DGPU" + ) + ); + GpuIrVendorOptimizationProposalRegistry registry = GpuIrVendorOptimizationProposalRegistry.of(List.of( + new VendorProvider("nvidia-provider", GpuBackendTarget.OPENCL, "NVIDIA", 0, true) + )); + + GpuIrOptimizationProposal proposal = registry.asProposalProvider().propose(request); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + assertEquals("nvidia-provider", proposal.optimizerId()); + assertTrue(proposal.proofArtifact().fields().containsKey("deviceLabel")); + assertEquals("RTX", proposal.proofArtifact().fields().get("deviceLabel")); + } + + @Test + void vendorRequestNormalizesUnknownFields() { + GpuIrVendorOptimizationProposalRequest request = GpuIrVendorOptimizationProposalRequest.from( + request(artifact("body\n"), Map.of("backendTarget", "bad-target")) + ); + + assertEquals(GpuBackendTarget.UNKNOWN, request.backendTarget()); + assertEquals("unknown", request.vendor()); + assertEquals(GpuDeviceClassTarget.UNKNOWN, request.deviceClass()); + } + + private static GpuIrOptimizationProposalRequest request( + IrGpuArtifact artifact, + Map contextFields + ) { + return new GpuIrOptimizationProposalRequest(artifact, "diagnostic", false, contextFields); + } + + private static IrGpuArtifact artifact(String body) { + IrGpuMethodBody methodBody = IrGpuMethodBody.entry( + "test.Kernel.run", + "run", + body, + List.of() + ); + IrGpuModule module = new IrGpuModule("test.Kernel.run", "run", List.of(), List.of(), List.of(methodBody)); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + module, + List.of(IrGpuBackendOutput.openClSource("test.cl")), + "opencl", + "off" + ); + } + + private record VendorProvider( + String extensionId, + GpuBackendTarget backendTarget, + String vendor, + int order, + boolean proposes + ) implements GpuIrVendorOptimizationProposalProvider { + + @Override + public GpuIrOptimizationProposal proposeForVendor(GpuIrVendorOptimizationProposalRequest request) { + if (!proposes) { + return GpuIrOptimizationProposal.noChange( + extensionId(), + extensionVersion(), + request.originalArtifact(), + "vendor provider did not find a safe proposal" + ); + } + return GpuIrOptimizationProposal.proposed( + extensionId(), + extensionVersion(), + request.originalArtifact(), + artifact("body\n// vendor proposal\n"), + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "vendor-test", + "proposal-only", + Map.of( + "backendTarget", request.backendTarget().name(), + "vendor", request.vendor(), + "deviceLabel", request.deviceLabel() + ) + ), + List.of("vendor provider proposed a candidate for " + request.vendor()) + ); + } + + @Override + public GpuBackendTarget supportedBackendTarget() { + return backendTarget; + } + + @Override + public String supportedVendor() { + return vendor; + } + + @Override + public String extensionId() { + return extensionId; + } + + @Override + public String extensionVersion() { + return extensionId + ":1"; + } + + @Override + public int extensionOrder() { + return order; + } + } +} diff --git a/ir-vendor-optimizer/build.gradle b/ir-vendor-optimizer/build.gradle new file mode 100644 index 00000000..8667df6c --- /dev/null +++ b/ir-vendor-optimizer/build.gradle @@ -0,0 +1,185 @@ +plugins { + id 'java-library' + id 'maven-publish' + id 'signing' +} + +import groovy.xml.XmlSlurper + +description = 'Optional JavaToGpu vendor-specific IR optimizer proposal providers.' +group = rootProject.group +version = rootProject.version + +def secretValue = { String gradlePropertyName, String environmentVariableName -> + providers.gradleProperty(gradlePropertyName) + .orElse(providers.environmentVariable(environmentVariableName)) +} + +def centralUsernameProvider = secretValue('mavenCentralUsername', 'MAVEN_CENTRAL_USERNAME') +def centralPasswordProvider = secretValue('mavenCentralPassword', 'MAVEN_CENTRAL_PASSWORD') +def signingKeyProvider = secretValue('signingInMemoryKey', 'SIGNING_IN_MEMORY_KEY') +def signingPasswordProvider = secretValue('signingInMemoryKeyPassword', 'SIGNING_IN_MEMORY_KEY_PASSWORD') +def publicationArtifactId = findProperty('javatogpu.irVendorOptimizerArtifactId') ?: 'javatogpu-ir-vendor-optimizer' + +java { + withSourcesJar() + withJavadocJar() +} + +repositories { + mavenCentral() +} + +dependencies { + api project(':ir-optimizer') + testImplementation platform('org.junit:junit-bom:5.10.0') + testImplementation 'org.junit.jupiter:junit-jupiter' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' +} + +tasks.withType(Javadoc).configureEach { + options.addStringOption('Xdoclint:none', '-quiet') +} + +test { + useJUnitPlatform() +} + +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + artifactId = publicationArtifactId + + pom { + name = 'JavaToGpu IR Vendor Optimizer' + description = project.description + url = 'https://github.com/Team-Argentum/JavaToGpu' + + licenses { + license { + name = 'GNU Lesser General Public License v3.0' + url = 'https://www.gnu.org/licenses/lgpl-3.0.en.html' + distribution = 'repo' + } + } + + developers { + developer { + id = 'Team-Argentum' + name = 'Team Argentum' + organization = 'Team Argentum' + organizationUrl = 'https://github.com/Team-Argentum' + } + } + + scm { + connection = 'scm:git:https://github.com/Team-Argentum/JavaToGpu.git' + developerConnection = 'scm:git:https://github.com/Team-Argentum/JavaToGpu.git' + url = 'https://github.com/Team-Argentum/JavaToGpu' + } + + withXml { + def dependenciesNode = asNode().dependencies + if (dependenciesNode) { + dependenciesNode[0].dependency.findAll { dependency -> + dependency.groupId.text() == project.group.toString() + && dependency.artifactId.text() == 'ir-optimizer' + }.each { dependency -> + dependency.artifactId[0].setValue('javatogpu-ir-optimizer') + } + } + } + } + } + } + + repositories { + maven { + name = 'localStaging' + url = layout.buildDirectory.dir('maven-staging') + } + maven { + name = 'centralSnapshots' + url = uri('https://central.sonatype.com/repository/maven-snapshots/') + credentials { + username = centralUsernameProvider.getOrElse('') + password = centralPasswordProvider.getOrElse('') + } + } + maven { + name = 'centralReleases' + url = uri('https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/') + credentials { + username = centralUsernameProvider.getOrElse('') + password = centralPasswordProvider.getOrElse('') + } + } + } +} + +tasks.withType(GenerateModuleMetadata).configureEach { + enabled = false +} + +tasks.register('validateMavenCentralReleaseReadiness') { + group = 'publishing' + description = 'Fails release publishing if the generated Maven POM contains dependencies Maven Central should not release.' + dependsOn tasks.named('generatePomFileForMavenJavaPublication') + doLast { + if (version.toString().endsWith('-SNAPSHOT')) { + return + } + File pomFile = layout.buildDirectory.file('publications/mavenJava/pom-default.xml').get().asFile + if (!pomFile.isFile()) { + throw new GradleException('Maven Central release is not ready: generated POM is missing at ' + pomFile) + } + + def pom = new XmlSlurper(false, false).parse(pomFile) + java.util.List unsafeDependencies = [] + pom.dependencies.dependency.each { dependency -> + String groupId = dependency.groupId.text() + String artifactId = dependency.artifactId.text() + String versionText = dependency.version.text() + String coordinates = groupId + ':' + artifactId + ':' + (versionText.isBlank() ? '' : versionText) + String normalizedVersion = versionText.toLowerCase(Locale.ROOT) + + if (normalizedVersion.contains('snapshot')) { + unsafeDependencies.add(coordinates + ' (snapshot dependency)') + } else if (groupId.startsWith('com.github.') && normalizedVersion.contains('master')) { + unsafeDependencies.add(coordinates + ' (JitPack branch dependency)') + } + } + + if (!unsafeDependencies.isEmpty()) { + throw new GradleException('Maven Central release is not ready: generated POM contains non-release-safe dependencies: ' + + unsafeDependencies.join(', ')) + } + } +} + +tasks.matching { task -> + task.name == 'publishMavenJavaPublicationToCentralReleasesRepository' + || task.name == 'publishAllPublicationsToCentralReleasesRepository' +}.configureEach { + dependsOn tasks.named('validateMavenCentralReleaseReadiness') +} + +signing { + def signingKey = signingKeyProvider.orNull + def signingPassword = signingPasswordProvider.orNull + required { + !version.toString().endsWith('-SNAPSHOT') && gradle.taskGraph.allTasks.any { task -> + task.name.toLowerCase(Locale.ROOT).contains('central') + || task.name == 'publish' + } + } + if (signingKey != null && !signingKey.isBlank()) { + useInMemoryPgpKeys(signingKey, signingPassword) + } + sign publishing.publications.mavenJava +} diff --git a/ir-vendor-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/irvendoroptimizer/GpuIrNoOpVendorOptimizationProposalProvider.java b/ir-vendor-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/irvendoroptimizer/GpuIrNoOpVendorOptimizationProposalProvider.java new file mode 100644 index 00000000..f13d193e --- /dev/null +++ b/ir-vendor-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/irvendoroptimizer/GpuIrNoOpVendorOptimizationProposalProvider.java @@ -0,0 +1,54 @@ +package net.sixik.ga_utils.javatogpu.irvendoroptimizer; + +import net.sixik.ga_utils.javatogpu.api.GpuBackendTarget; +import net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrOptimizationProposal; +import net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrVendorOptimizationProposalProvider; +import net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrVendorOptimizationProposalRequest; + +/** + * Vendor-provider skeleton that records opt-in discovery without proposing a rewrite. + */ +public final class GpuIrNoOpVendorOptimizationProposalProvider implements GpuIrVendorOptimizationProposalProvider { + + public static final String PROVIDER_ID = GpuIrVendorOptimizerModule.MODULE_ID + ".noop"; + public static final String PROVIDER_VERSION = PROVIDER_ID + ":1"; + + @Override + public GpuIrOptimizationProposal proposeForVendor(GpuIrVendorOptimizationProposalRequest request) { + return GpuIrOptimizationProposal.noChange( + extensionId(), + extensionVersion(), + request.originalArtifact(), + "vendor optimizer skeleton observed backend=" + + request.backendTarget() + + ", vendor=" + + request.vendor() + + " and kept original IR selected" + ); + } + + @Override + public GpuBackendTarget supportedBackendTarget() { + return GpuBackendTarget.UNKNOWN; + } + + @Override + public String supportedVendor() { + return ""; + } + + @Override + public String extensionId() { + return PROVIDER_ID; + } + + @Override + public String extensionVersion() { + return PROVIDER_VERSION; + } + + @Override + public int extensionOrder() { + return 100; + } +} diff --git a/ir-vendor-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/irvendoroptimizer/GpuIrVendorOptimizerModule.java b/ir-vendor-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/irvendoroptimizer/GpuIrVendorOptimizerModule.java new file mode 100644 index 00000000..3a556973 --- /dev/null +++ b/ir-vendor-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/irvendoroptimizer/GpuIrVendorOptimizerModule.java @@ -0,0 +1,13 @@ +package net.sixik.ga_utils.javatogpu.irvendoroptimizer; + +/** + * Module metadata for the optional vendor-specific IR optimizer provider package. + */ +public final class GpuIrVendorOptimizerModule { + + public static final String MODULE_ID = "javatogpu.ir-vendor-optimizer"; + public static final String MODULE_VERSION = "1"; + + private GpuIrVendorOptimizerModule() { + } +} diff --git a/ir-vendor-optimizer/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrVendorOptimizationProposalProvider b/ir-vendor-optimizer/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrVendorOptimizationProposalProvider new file mode 100644 index 00000000..a02091c2 --- /dev/null +++ b/ir-vendor-optimizer/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrVendorOptimizationProposalProvider @@ -0,0 +1 @@ +net.sixik.ga_utils.javatogpu.irvendoroptimizer.GpuIrNoOpVendorOptimizationProposalProvider diff --git a/ir-vendor-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/irvendoroptimizer/GpuIrVendorOptimizerModuleTest.java b/ir-vendor-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/irvendoroptimizer/GpuIrVendorOptimizerModuleTest.java new file mode 100644 index 00000000..0e5b0ede --- /dev/null +++ b/ir-vendor-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/irvendoroptimizer/GpuIrVendorOptimizerModuleTest.java @@ -0,0 +1,139 @@ +package net.sixik.ga_utils.javatogpu.irvendoroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrOptimizationProposal; +import net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrOptimizationProposalDecision; +import net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrOptimizationProposalProvider; +import net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrOptimizationProposalRequest; +import net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrProposalRuntimeBridgePass; +import net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrVendorOptimizationProposalProvider; +import net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrVendorOptimizationProposalRegistry; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationPass; +import org.junit.jupiter.api.Test; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuIrVendorOptimizerModuleTest { + + @Test + void vendorProviderIsLoadedOnlyThroughVendorSpi() { + List vendorProviders = ServiceLoader + .load(GpuIrVendorOptimizationProposalProvider.class) + .stream() + .map(ServiceLoader.Provider::get) + .filter(provider -> provider.getClass().getName().startsWith( + "net.sixik.ga_utils.javatogpu.irvendoroptimizer")) + .toList(); + List neutralProviders = ServiceLoader + .load(GpuIrOptimizationProposalProvider.class) + .stream() + .map(ServiceLoader.Provider::get) + .filter(provider -> provider.getClass().getName().startsWith( + "net.sixik.ga_utils.javatogpu.irvendoroptimizer")) + .toList(); + List runtimePasses = ServiceLoader + .load(GpuRuntimeIrOptimizationPass.class) + .stream() + .map(ServiceLoader.Provider::get) + .filter(pass -> pass.getClass().getName().startsWith( + "net.sixik.ga_utils.javatogpu.irvendoroptimizer")) + .toList(); + + assertEquals(1, vendorProviders.size()); + assertInstanceOf(GpuIrNoOpVendorOptimizationProposalProvider.class, vendorProviders.get(0)); + assertTrue(neutralProviders.isEmpty()); + assertTrue(runtimePasses.isEmpty()); + } + + @Test + void modulePublishesOnlyVendorProviderDescriptorEntry() throws Exception { + List vendorDescriptor = serviceDescriptorLines(GpuIrVendorOptimizationProposalProvider.class.getName()); + List neutralDescriptor = serviceDescriptorLines(GpuIrOptimizationProposalProvider.class.getName()); + List runtimeDescriptor = serviceDescriptorLines(GpuRuntimeIrOptimizationPass.class.getName()); + + assertEquals(List.of(GpuIrNoOpVendorOptimizationProposalProvider.class.getName()), vendorDescriptor); + assertTrue(neutralDescriptor.stream().noneMatch(value -> value.contains("irvendoroptimizer"))); + assertTrue(runtimeDescriptor.stream().noneMatch(value -> value.contains("irvendoroptimizer"))); + } + + @Test + void registryAdapterRunsNoOpVendorProviderWithoutSelectingOptimizedIr() { + IrGpuArtifact original = artifact("body\n"); + GpuIrVendorOptimizationProposalRegistry registry = + GpuIrVendorOptimizationProposalRegistry.loadFromServiceLoader(); + + GpuIrOptimizationProposal proposal = registry.asProposalProvider().propose(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of( + "backendTarget", "OPENCL", + "deviceProfile.vendor", "NVIDIA", + "deviceProfile.label", "RTX" + ) + )); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertFalse(proposal.hasOptimizedArtifact()); + assertSame(original, proposal.originalArtifact()); + assertEquals(GpuIrNoOpVendorOptimizationProposalProvider.PROVIDER_ID, proposal.optimizerId()); + assertTrue(proposal.diagnostics().get(0).contains("kept original IR selected")); + } + + @Test + void defaultRuntimeBridgeDoesNotAutoLoadVendorProviders() { + GpuIrProposalRuntimeBridgePass bridge = new GpuIrProposalRuntimeBridgePass(); + + assertTrue(bridge.proposalProviders().stream().noneMatch(provider -> + provider.getClass().getName().startsWith("net.sixik.ga_utils.javatogpu.irvendoroptimizer"))); + } + + private static IrGpuArtifact artifact(String body) { + IrGpuMethodBody methodBody = IrGpuMethodBody.entry( + "test.Kernel.run", + "run", + body, + List.of() + ); + IrGpuModule module = new IrGpuModule("test.Kernel.run", "run", List.of(), List.of(), List.of(methodBody)); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + module, + List.of(IrGpuBackendOutput.openClSource("test.cl")), + "opencl", + "off" + ); + } + + private static List serviceDescriptorLines(String serviceName) throws Exception { + String resourceName = "META-INF/services/" + serviceName; + ClassLoader classLoader = GpuIrVendorOptimizerModuleTest.class.getClassLoader(); + try (java.io.InputStream stream = classLoader.getResourceAsStream(resourceName)) { + if (stream == null) { + return List.of(); + } + try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { + return reader.lines() + .map(String::trim) + .filter(line -> !line.isBlank()) + .filter(line -> !line.startsWith("#")) + .toList(); + } + } + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java index 6b97a8d6..902e69b4 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java @@ -21,6 +21,8 @@ public final class GpuRuntimeCompileArtifactDumper { public static final String BACKEND_COMPILER_FEEDBACK_ARTIFACT = "backend-compiler-feedback.properties"; public static final String RUNTIME_EXTENSION_PARTICIPATION_ARTIFACT = "runtime-extension-participation.properties"; + public static final String RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT = + "runtime-ir-optimizer-evidence.properties"; public static final String RUNTIME_OPTIMIZER_FAMILY_EQUIVALENCE_PAYLOAD_DIRECTORY = "runtime-optimizer-family-equivalence-payload"; @@ -99,6 +101,10 @@ public static GpuRuntimeCompileArtifactDump dump( if (snapshot.optimizationReport().hasReports() || snapshot.productionOptimizerGate().productionProfileRequested()) { artifacts.put("optimizer-report.txt", snapshot.optimizationReport().toText()); } + String irOptimizerEvidence = formatRuntimeIrOptimizerEvidence(snapshot.optimizationReport()); + if (!irOptimizerEvidence.isBlank()) { + artifacts.put(RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT, irOptimizerEvidence); + } String runtimeIrAnalysis = formatRuntimeIrAnalysis(snapshot.optimizationReport(), compilerFeedbackReport); if (!runtimeIrAnalysis.isBlank()) { artifacts.put("runtime-ir-analysis.properties", runtimeIrAnalysis); @@ -699,6 +705,166 @@ private static String formatRuntimeIrAnalysis( return builder.toString(); } + private static String formatRuntimeIrOptimizerEvidence(GpuRuntimeIrOptimizationReport report) { + List irOptimizerReports = report.passReports().stream() + .filter(GpuRuntimeCompileArtifactDumper::isRuntimeIrOptimizerEvidence) + .toList(); + if (irOptimizerReports.isEmpty()) { + return ""; + } + StringBuilder builder = new StringBuilder(); + builder.append("status=recorded\n"); + builder.append("source=runtime-ir-optimizer\n"); + builder.append("providerPrefix=javatogpu.ir-optimizer\n"); + builder.append("pass.count=").append(irOptimizerReports.size()).append('\n'); + builder.append("applied.count=").append(countOutcome(irOptimizerReports, GpuRuntimeIrOptimizationOutcome.APPLIED)).append('\n'); + builder.append("skipped.count=").append(countOutcome(irOptimizerReports, GpuRuntimeIrOptimizationOutcome.SKIPPED)).append('\n'); + builder.append("rolledBack.count=").append(countOutcome(irOptimizerReports, GpuRuntimeIrOptimizationOutcome.ROLLED_BACK)).append('\n'); + builder.append("failed.count=").append(countOutcome(irOptimizerReports, GpuRuntimeIrOptimizationOutcome.FAILED)).append('\n'); + builder.append("proposalOnly.count=").append(countProofStatus(irOptimizerReports, "proposal-only")).append('\n'); + builder.append("selectedOptimized.count=").append(countProofStatus(irOptimizerReports, "optimized-selected")).append('\n'); + builder.append("approvalTemplate.pending.count=") + .append(countApprovalTemplateStatus(irOptimizerReports, "pending")) + .append('\n'); + builder.append("approvalTemplate.notApplicable.count=") + .append(countApprovalTemplateStatus(irOptimizerReports, "not-applicable")) + .append('\n'); + for (int index = 0; index < irOptimizerReports.size(); index++) { + GpuRuntimeIrOptimizationPassReport passReport = irOptimizerReports.get(index); + String prefix = "pass." + index + "."; + ApprovalTemplateEvidence approvalTemplate = approvalTemplateEvidence(passReport); + builder.append(prefix).append("passVersion=") + .append(safePropertyValue(passReport.optimizerVersion())).append('\n'); + builder.append(prefix).append("stage=").append(passReport.stage()).append('\n'); + builder.append(prefix).append("outcome=").append(passReport.outcome()).append('\n'); + builder.append(prefix).append("proofStatus=") + .append(safePropertyValue(passReport.proofStatus())).append('\n'); + builder.append(prefix).append("originalIrIdentity=") + .append(safePropertyValue(passReport.originalIrIdentity())).append('\n'); + builder.append(prefix).append("transformedIrIdentity=") + .append(safePropertyValue(passReport.transformedIrIdentity())).append('\n'); + builder.append(prefix).append("rollbackReason=") + .append(safePropertyValue(passReport.rollbackReason())).append('\n'); + builder.append(prefix).append("proofArtifact.source=") + .append(safePropertyValue(passReport.proofArtifact().source())).append('\n'); + builder.append(prefix).append("proofArtifact.verdict=") + .append(safePropertyValue(passReport.proofArtifact().verdict())).append('\n'); + builder.append(prefix).append("proofArtifact.field.count=") + .append(passReport.proofArtifact().fields().size()).append('\n'); + passReport.proofArtifact().fields().entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .forEach(entry -> builder.append(prefix).append("proofArtifact.field.") + .append(safePropertyValue(entry.getKey())).append('=') + .append(safePropertyValue(entry.getValue())).append('\n')); + builder.append(prefix).append("approvalTemplate.status=") + .append(approvalTemplate.status()).append('\n'); + builder.append(prefix).append("approvalTemplate.applicable=") + .append(approvalTemplate.applicable()).append('\n'); + builder.append(prefix).append("approvalTemplate.firstBlocker=") + .append(safePropertyValue(approvalTemplate.firstBlocker())).append('\n'); + builder.append(prefix).append("approvalTemplate.productionMutation=disabled\n"); + builder.append(prefix).append("approvalTemplate.manualReviewOnly=true\n"); + if (approvalTemplate.applicable()) { + builder.append(prefix) + .append("approvalTemplate.resourceDirectory=META-INF/javatogpu/ir-optimization-approvals/\n"); + } + builder.append(prefix).append("diagnostic.count=").append(passReport.diagnostics().size()).append('\n'); + for (int diagnosticIndex = 0; diagnosticIndex < passReport.diagnostics().size(); diagnosticIndex++) { + builder.append(prefix).append("diagnostic.").append(diagnosticIndex).append('=') + .append(safePropertyValue(passReport.diagnostics().get(diagnosticIndex))).append('\n'); + } + } + return builder.toString(); + } + + private static boolean isRuntimeIrOptimizerEvidence(GpuRuntimeIrOptimizationPassReport passReport) { + if (passReport == null) { + return false; + } + String optimizerVersion = passReport.optimizerVersion() == null ? "" : passReport.optimizerVersion(); + String proofSource = passReport.proofArtifact().source() == null ? "" : passReport.proofArtifact().source(); + return optimizerVersion.startsWith("javatogpu.ir-optimizer") + || proofSource.startsWith("ir-optimizer"); + } + + private static long countOutcome( + List passReports, + GpuRuntimeIrOptimizationOutcome outcome + ) { + return passReports.stream().filter(passReport -> passReport.outcome() == outcome).count(); + } + + private static long countProofStatus(List passReports, String proofStatus) { + return passReports.stream() + .filter(passReport -> proofStatus.equals(passReport.proofStatus())) + .count(); + } + + private static long countApprovalTemplateStatus( + List passReports, + String status + ) { + return passReports.stream() + .map(GpuRuntimeCompileArtifactDumper::approvalTemplateEvidence) + .filter(evidence -> status.equals(evidence.status())) + .count(); + } + + private static ApprovalTemplateEvidence approvalTemplateEvidence(GpuRuntimeIrOptimizationPassReport passReport) { + if (passReport == null) { + return ApprovalTemplateEvidence.notApplicable("pass-report-missing"); + } + Map proofFields = passReport.proofArtifact().fields(); + if ("false".equals(proofFields.get("rewrite.proposed")) + || "true".equals(proofFields.get("previewOnly"))) { + return ApprovalTemplateEvidence.notApplicable("proposal-decision-not-proposed"); + } + if (!"proposal-only".equals(passReport.proofStatus()) + && !"optimized-selected".equals(passReport.proofStatus())) { + return ApprovalTemplateEvidence.notApplicable("proposal-decision-not-proposed"); + } + if (!distinctIrIdentities(passReport.originalIrIdentity(), passReport.transformedIrIdentity())) { + return ApprovalTemplateEvidence.notApplicable("proposal-identities-not-distinct"); + } + String proofSource = passReport.proofArtifact().source(); + if (proofSource == null + || proofSource.isBlank() + || "none".equals(proofSource) + || "ir-optimizer".equals(proofSource)) { + return ApprovalTemplateEvidence.notApplicable("proposal-proof-source-not-specific"); + } + String proofVerdict = passReport.proofArtifact().verdict() == null + ? "" + : passReport.proofArtifact().verdict().toLowerCase(java.util.Locale.ROOT); + if (proofVerdict.isBlank() + || proofVerdict.contains("not-proven") + || proofVerdict.contains("rejected") + || proofVerdict.contains("failed")) { + return ApprovalTemplateEvidence.notApplicable("proposal-proof-verdict-not-accepted"); + } + return ApprovalTemplateEvidence.pending(); + } + + private static boolean distinctIrIdentities(String original, String transformed) { + String normalizedOriginal = original == null ? "" : original; + String normalizedTransformed = transformed == null ? "" : transformed; + return !normalizedOriginal.isBlank() + && !normalizedTransformed.isBlank() + && !"irgpu:missing".equals(normalizedOriginal) + && !"irgpu:missing".equals(normalizedTransformed) + && !normalizedOriginal.equals(normalizedTransformed); + } + + private record ApprovalTemplateEvidence(String status, boolean applicable, String firstBlocker) { + private static ApprovalTemplateEvidence pending() { + return new ApprovalTemplateEvidence("pending", true, "none"); + } + + private static ApprovalTemplateEvidence notApplicable(String firstBlocker) { + return new ApprovalTemplateEvidence("not-applicable", false, firstBlocker); + } + } + private static void appendCompilerFeedbackAnalysis( StringBuilder builder, List analysisReports, diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java new file mode 100644 index 00000000..93b2808d --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java @@ -0,0 +1,328 @@ +package net.sixik.ga_utils.javatogpu.runtime.opencl; + +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileArtifactDumper; + +import java.io.IOException; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +/** + * Aggregates optional ir-optimizer evidence artifacts into the OpenCL validation report. + */ +record OpenClRuntimeIrOptimizerEvidenceSummary(String status, List entries, String diagnostic) { + + private static final String ARTIFACT_FILE_NAME = + GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT; + + OpenClRuntimeIrOptimizerEvidenceSummary { + status = status == null || status.isBlank() ? "unknown" : status; + entries = entries == null ? List.of() : List.copyOf(entries); + diagnostic = diagnostic == null ? "" : diagnostic; + } + + static OpenClRuntimeIrOptimizerEvidenceSummary notRecorded() { + return new OpenClRuntimeIrOptimizerEvidenceSummary("not-recorded", List.of(), ""); + } + + static OpenClRuntimeIrOptimizerEvidenceSummary failed(Throwable failure) { + return new OpenClRuntimeIrOptimizerEvidenceSummary( + "failed", + List.of(), + failure == null ? "unknown failure" : failure.toString() + ); + } + + static OpenClRuntimeIrOptimizerEvidenceSummary read(Path workloadGateFile) throws IOException { + if (workloadGateFile == null || !Files.isRegularFile(workloadGateFile)) { + return notRecorded(); + } + Properties gate = loadProperties(workloadGateFile); + int kernelCount = parseInt(gate.getProperty("kernel.count"), 0); + if (kernelCount <= 0) { + return notRecorded(); + } + + Path reportDirectory = workloadGateFile.getParent(); + Path artifactRoot = reportDirectory == null + ? null + : reportDirectory.resolve("runtime-compile-artifacts"); + Map evidenceByResource = loadEvidenceArtifacts(artifactRoot); + ArrayList entries = new ArrayList<>(); + for (int index = 0; index < kernelCount; index++) { + String resource = gate.getProperty("kernel." + index + ".sourceKernelResource", "unknown"); + entries.add(Entry.from(resource, evidenceByResource.get(resource))); + } + return new OpenClRuntimeIrOptimizerEvidenceSummary("recorded", entries, ""); + } + + int count(String expectedStatus) { + int count = 0; + for (Entry entry : entries) { + if (expectedStatus.equals(entry.status())) { + count++; + } + } + return count; + } + + int totalPassCount() { + return entries.stream().mapToInt(Entry::passCount).sum(); + } + + int totalProposalOnlyCount() { + return entries.stream().mapToInt(Entry::proposalOnlyCount).sum(); + } + + int totalSelectedOptimizedCount() { + return entries.stream().mapToInt(Entry::selectedOptimizedCount).sum(); + } + + int totalRolledBackCount() { + return entries.stream().mapToInt(Entry::rolledBackCount).sum(); + } + + int totalApprovalTemplatePendingCount() { + return entries.stream().mapToInt(Entry::approvalTemplatePendingCount).sum(); + } + + int totalApprovalTemplateNotApplicableCount() { + return entries.stream().mapToInt(Entry::approvalTemplateNotApplicableCount).sum(); + } + + String providerSummary() { + LinkedHashMap counts = new LinkedHashMap<>(); + for (Entry entry : entries) { + for (Map.Entry provider : entry.providerCounts().entrySet()) { + counts.merge(provider.getKey(), provider.getValue(), Integer::sum); + } + } + if (counts.isEmpty()) { + return "none"; + } + StringBuilder summary = new StringBuilder(); + counts.forEach((provider, count) -> { + if (!summary.isEmpty()) { + summary.append(", "); + } + summary.append(provider).append('=').append(count); + }); + return summary.toString(); + } + + String toMarkdown() { + StringBuilder markdown = new StringBuilder(); + markdown.append("## Runtime IR Optimizer Evidence\n\n"); + markdown.append("- Status: `").append(inline(status)).append("`\n"); + if (!diagnostic.isBlank()) { + markdown.append("- Diagnostic: `").append(inline(diagnostic)).append("`\n\n"); + return markdown.toString(); + } + markdown.append("- Kernel count: `").append(entries.size()).append("`\n"); + markdown.append("- Recorded kernels: `").append(count("recorded")).append("`\n"); + markdown.append("- Missing artifacts: `").append(count("missing")).append("`\n"); + markdown.append("- Proposal pass count: `").append(totalPassCount()).append("`\n"); + markdown.append("- Proposal-only count: `").append(totalProposalOnlyCount()).append("`\n"); + markdown.append("- Selected optimized count: `").append(totalSelectedOptimizedCount()).append("`\n"); + markdown.append("- Rolled back count: `").append(totalRolledBackCount()).append("`\n"); + markdown.append("- Approval templates pending: `").append(totalApprovalTemplatePendingCount()).append("`\n"); + markdown.append("- Approval templates not applicable: `").append(totalApprovalTemplateNotApplicableCount()).append("`\n"); + markdown.append("- Providers: `").append(inline(providerSummary())).append("`\n\n"); + if (entries.isEmpty()) { + return markdown.toString(); + } + markdown.append("| Kernel resource | Status | Passes | Proposal-only | Selected | Rolled back | Approval pending | Approval N/A | Providers |\n"); + markdown.append("| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | --- |\n"); + for (Entry entry : entries) { + markdown.append("| `").append(table(entry.kernelResource())).append("` | `") + .append(table(entry.status())).append("` | `") + .append(entry.passCount()).append("` | `") + .append(entry.proposalOnlyCount()).append("` | `") + .append(entry.selectedOptimizedCount()).append("` | `") + .append(entry.rolledBackCount()).append("` | `") + .append(entry.approvalTemplatePendingCount()).append("` | `") + .append(entry.approvalTemplateNotApplicableCount()).append("` | `") + .append(table(formatProviderCounts(entry.providerCounts()))).append("` |\n"); + } + markdown.append('\n'); + return markdown.toString(); + } + + private static Map loadEvidenceArtifacts(Path artifactRoot) throws IOException { + LinkedHashMap artifacts = new LinkedHashMap<>(); + if (artifactRoot == null || !Files.isDirectory(artifactRoot)) { + return artifacts; + } + try (java.util.stream.Stream paths = Files.walk(artifactRoot)) { + for (Path path : paths + .filter(Files::isRegularFile) + .filter(candidate -> ARTIFACT_FILE_NAME.equals(candidate.getFileName().toString())) + .toList()) { + Properties properties = loadProperties(path); + String resource = firstNonBlank( + properties.getProperty("backendResource", ""), + properties.getProperty("kernelResource", "") + ); + if (resource.isBlank()) { + resource = inferResourceFromSiblingArtifacts(path.getParent()); + } + if (!resource.isBlank() && !"unknown".equals(resource)) { + artifacts.put(resource, properties); + } + } + } + return artifacts; + } + + private static String inferResourceFromSiblingArtifacts(Path directory) throws IOException { + if (directory == null || !Files.isDirectory(directory)) { + return ""; + } + for (String sibling : List.of( + GpuRuntimeCompileArtifactDumper.RUNTIME_EXTENSION_PARTICIPATION_ARTIFACT, + "backend-module.properties", + "backend-diagnostics.properties" + )) { + Path path = directory.resolve(sibling); + if (!Files.isRegularFile(path)) { + continue; + } + Properties properties = loadProperties(path); + String resource = firstNonBlank( + properties.getProperty("backendResource", ""), + properties.getProperty("resource", "") + ); + if (!resource.isBlank() && !"unknown".equals(resource)) { + return resource; + } + } + return ""; + } + + private static Properties loadProperties(Path path) throws IOException { + Properties properties = new Properties(); + try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { + properties.load(reader); + } + return properties; + } + + private static int parseInt(String value, int fallback) { + try { + return Integer.parseInt(value); + } catch (NumberFormatException ignored) { + return fallback; + } + } + + private static String firstNonBlank(String... values) { + if (values == null) { + return ""; + } + for (String value : values) { + if (value != null && !value.isBlank()) { + return value; + } + } + return ""; + } + + private static String inline(String value) { + return value == null ? "" : value.replace('\r', ' ').replace('\n', ' ').replace('`', '\''); + } + + private static String table(String value) { + return inline(value).replace("|", "\\|"); + } + + private static String formatProviderCounts(Map counts) { + if (counts == null || counts.isEmpty()) { + return "none"; + } + StringBuilder summary = new StringBuilder(); + counts.forEach((provider, count) -> { + if (!summary.isEmpty()) { + summary.append(", "); + } + summary.append(provider).append('=').append(count); + }); + return summary.toString(); + } + + record Entry( + String kernelResource, + String status, + int passCount, + int proposalOnlyCount, + int selectedOptimizedCount, + int rolledBackCount, + int approvalTemplatePendingCount, + int approvalTemplateNotApplicableCount, + Map providerCounts + ) { + + Entry { + kernelResource = normalize(kernelResource, "unknown"); + status = normalize(status, "unknown"); + passCount = Math.max(0, passCount); + proposalOnlyCount = Math.max(0, proposalOnlyCount); + selectedOptimizedCount = Math.max(0, selectedOptimizedCount); + rolledBackCount = Math.max(0, rolledBackCount); + approvalTemplatePendingCount = Math.max(0, approvalTemplatePendingCount); + approvalTemplateNotApplicableCount = Math.max(0, approvalTemplateNotApplicableCount); + providerCounts = normalizeProviderCounts(providerCounts); + } + + static Entry from(String kernelResource, Properties properties) { + if (properties == null || properties.isEmpty()) { + return new Entry(kernelResource, "missing", 0, 0, 0, 0, 0, 0, Map.of()); + } + return new Entry( + kernelResource, + properties.getProperty("status", "unknown"), + parseInt(properties.getProperty("pass.count"), 0), + parseInt(properties.getProperty("proposalOnly.count"), 0), + parseInt(properties.getProperty("selectedOptimized.count"), 0), + parseInt(properties.getProperty("rolledBack.count"), 0), + parseInt(properties.getProperty("approvalTemplate.pending.count"), 0), + parseInt(properties.getProperty("approvalTemplate.notApplicable.count"), 0), + parseProviderCounts(properties) + ); + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } + + private static Map parseProviderCounts(Properties properties) { + LinkedHashMap counts = new LinkedHashMap<>(); + int count = parseInt(properties.getProperty("pass.count"), 0); + for (int index = 0; index < count; index++) { + String provider = properties.getProperty("pass." + index + ".passVersion", ""); + if (!provider.isBlank()) { + counts.merge(provider, 1, Integer::sum); + } + } + return counts; + } + + private static Map normalizeProviderCounts(Map values) { + if (values == null || values.isEmpty()) { + return Map.of(); + } + LinkedHashMap normalized = new LinkedHashMap<>(); + values.forEach((provider, count) -> { + if (provider != null && !provider.isBlank() && count != null && count > 0) { + normalized.put(provider, count); + } + }); + return java.util.Collections.unmodifiableMap(normalized); + } + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReporter.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReporter.java index 202605a7..16a9f3f0 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReporter.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReporter.java @@ -62,6 +62,7 @@ public static void main(String[] args) throws IOException { OpenClKernelLaunchAdvisorySummary launchAdvisorySummary = loadKernelLaunchAdvisorySummary(); OpenClCompilerResourceSummary compilerResourceSummary = loadCompilerResourceSummary(); OpenClExtensionParticipationSummary extensionParticipationSummary = loadExtensionParticipationSummary(); + OpenClRuntimeIrOptimizerEvidenceSummary irOptimizerEvidenceSummary = loadIrOptimizerEvidenceSummary(); OpenClValidationHistoryEntry currentHistoryEntry = buildHistoryEntry( launchAdvisorySummary, compilerResourceSummary, @@ -74,7 +75,8 @@ public static void main(String[] args) throws IOException { launchAdvisoryDrift, compilerResourceSummary, compilerResourceDrift, - extensionParticipationSummary + extensionParticipationSummary, + irOptimizerEvidenceSummary ); String outputPath = System.getProperty(REPORT_FILE_PROPERTY); if (outputPath != null && !outputPath.isBlank()) { @@ -98,7 +100,8 @@ private static String buildReport( OpenClKernelLaunchAdvisoryDrift launchAdvisoryDrift, OpenClCompilerResourceSummary compilerResourceSummary, OpenClCompilerResourceDrift compilerResourceDrift, - OpenClExtensionParticipationSummary extensionParticipationSummary + OpenClExtensionParticipationSummary extensionParticipationSummary, + OpenClRuntimeIrOptimizerEvidenceSummary irOptimizerEvidenceSummary ) { StringBuilder markdown = new StringBuilder(); String requestedVendor = env("JTG_VALIDATION_VENDOR"); @@ -137,6 +140,7 @@ private static String buildReport( markdown.append(compilerResourceSummary.toMarkdown()); markdown.append(compilerResourceDrift.toMarkdown()); markdown.append(extensionParticipationSummary.toMarkdown()); + markdown.append(irOptimizerEvidenceSummary.toMarkdown()); appendIrGpuSourceReviewSummary(markdown); appendProductionSourceSwitchingValidationSummary(markdown); appendLongRunningSummary(markdown); @@ -192,6 +196,18 @@ private static OpenClExtensionParticipationSummary loadExtensionParticipationSum } } + private static OpenClRuntimeIrOptimizerEvidenceSummary loadIrOptimizerEvidenceSummary() { + String gatePath = System.getProperty(BACKEND_SOURCE_PROMOTION_WORKLOAD_GATE_FILE_PROPERTY); + if (gatePath == null || gatePath.isBlank()) { + return OpenClRuntimeIrOptimizerEvidenceSummary.notRecorded(); + } + try { + return OpenClRuntimeIrOptimizerEvidenceSummary.read(Paths.get(gatePath)); + } catch (Throwable failure) { + return OpenClRuntimeIrOptimizerEvidenceSummary.failed(failure); + } + } + private static OpenClKernelLaunchAdvisoryDrift loadKernelLaunchAdvisoryDrift( OpenClValidationHistoryEntry currentEntry ) { diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java index b6f73f7e..802ab554 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test; import java.util.List; +import java.util.Map; import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -1989,6 +1990,122 @@ void optimizerDriftArtifactCapturesRollbackAndFallbackCounts() { assertTrue(dump.artifact("runtime-production-mutation-safety.properties").contains("fallbackDecision=optimizer-rollback")); } + @Test + void dumpsRuntimeIrOptimizerEvidenceArtifactForProposalBridgeReports() { + IrGpuArtifact original = artifact("body\n return original\n"); + GpuBackendModuleArtifact backendArtifact = GpuBackendModuleArtifact.openClSource( + "__kernel void kernel(__global int* out) { out[0] = 1; }", + "runtime/lowered/kernel.cl", + "test-lowerer-v1" + ); + GpuRuntimeCompileRequest request = new GpuRuntimeCompileRequest( + descriptor(), + new GpuRuntimeCompileOptions(GpuBackendTarget.OPENCL, List.of(), "diagnostic"), + GpuRuntimeDeviceProfile.generic(GpuBackendTarget.OPENCL, "OpenCL"), + Optional.of(original) + ); + GpuRuntimeIrOptimizationPassReport noOpReport = GpuRuntimeIrOptimizationPassReport.skipped( + "javatogpu.ir-optimizer.noop:1", + "irgpu:sha256:original", + "no-op proposal provider keeps original IR selected" + ).withStage(GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY) + .withProofArtifact(GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer", + "not-mutating", + Map.of("provider", "noop") + )); + GpuRuntimeIrOptimizationPassReport canonicalizationReport = new GpuRuntimeIrOptimizationPassReport( + GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, + "javatogpu.ir-optimizer.text-canonicalization:1", + GpuRuntimeIrOptimizationOutcome.SKIPPED, + "irgpu:sha256:original", + "irgpu:sha256:canonical", + "proposal-only", + "", + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.text-canonicalization", + "semantics-neutral-text-normalization", + Map.of( + "changedMethodBodies", "1", + "mutationRequired", "false", + "normalizations", "crlf-to-lf,trailing-whitespace" + ) + ), + List.of("optimized artifact validated but mutation is disabled; original IR remains selected") + ); + GpuRuntimeIrOptimizationPassReport previewReport = new GpuRuntimeIrOptimizationPassReport( + GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, + "javatogpu.ir-optimizer.constant-folding-preview:1", + GpuRuntimeIrOptimizationOutcome.SKIPPED, + "irgpu:sha256:original", + "irgpu:sha256:original", + "not-mutating", + "", + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.constant-folding-preview", + "preview-candidates-recorded", + Map.of( + "candidate.count", "1", + "previewOnly", "true", + "rewrite.proposed", "false" + ) + ), + List.of("constant folding preview recorded evidence; no rewrite was proposed") + ); + GpuRuntimeIrOptimizationPassReport unrelatedReport = GpuRuntimeIrOptimizationPassReport.applied( + "optimizer:other", + "irgpu:sha256:original", + "irgpu:sha256:other", + "proof:other", + List.of("unrelated optimizer evidence") + ); + GpuRuntimeIrOptimizationReport optimizationReport = new GpuRuntimeIrOptimizationReport( + Optional.of(original), + List.of(noOpReport, canonicalizationReport, previewReport, unrelatedReport), + GpuOptimizationStrategyDecision.none(request) + ); + GpuRuntimeCompileArtifactSnapshot snapshot = GpuRuntimeCompileArtifactSnapshot.from( + request, + request, + backendArtifact, + GpuRuntimeCompileInvalidationStamp.from(request, backendArtifact, "optimizer:test-v1"), + GpuRuntimeCompileProvenance.from(request), + optimizationReport + ); + + GpuRuntimeCompileArtifactDump dump = GpuRuntimeCompileArtifactDumper.dump(snapshot); + + assertTrue(dump.hasArtifact(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT)); + String evidence = dump.artifact(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + assertTrue(evidence.contains("status=recorded")); + assertTrue(evidence.contains("source=runtime-ir-optimizer")); + assertTrue(evidence.contains("providerPrefix=javatogpu.ir-optimizer")); + assertTrue(evidence.contains("pass.count=3")); + assertTrue(evidence.contains("skipped.count=3")); + assertTrue(evidence.contains("applied.count=0")); + assertTrue(evidence.contains("proposalOnly.count=1")); + assertTrue(evidence.contains("selectedOptimized.count=0")); + assertTrue(evidence.contains("approvalTemplate.pending.count=1")); + assertTrue(evidence.contains("approvalTemplate.notApplicable.count=2")); + assertTrue(evidence.contains("pass.0.passVersion=javatogpu.ir-optimizer.noop:1")); + assertTrue(evidence.contains("pass.1.passVersion=javatogpu.ir-optimizer.text-canonicalization:1")); + assertTrue(evidence.contains("pass.2.passVersion=javatogpu.ir-optimizer.constant-folding-preview:1")); + assertTrue(evidence.contains("pass.1.proofStatus=proposal-only")); + assertTrue(evidence.contains("pass.1.transformedIrIdentity=irgpu:sha256:canonical")); + assertTrue(evidence.contains("pass.1.proofArtifact.source=ir-optimizer.text-canonicalization")); + assertTrue(evidence.contains("pass.1.proofArtifact.field.changedMethodBodies=1")); + assertTrue(evidence.contains("pass.1.proofArtifact.field.mutationRequired=false")); + assertTrue(evidence.contains("pass.0.approvalTemplate.status=not-applicable")); + assertTrue(evidence.contains("pass.0.approvalTemplate.firstBlocker=proposal-decision-not-proposed")); + assertTrue(evidence.contains("pass.1.approvalTemplate.status=pending")); + assertTrue(evidence.contains("pass.1.approvalTemplate.applicable=true")); + assertTrue(evidence.contains("pass.1.approvalTemplate.resourceDirectory=META-INF/javatogpu/ir-optimization-approvals/")); + assertTrue(evidence.contains("pass.2.approvalTemplate.status=not-applicable")); + assertTrue(evidence.contains("pass.2.approvalTemplate.firstBlocker=proposal-decision-not-proposed")); + assertTrue(evidence.contains("pass.2.proofArtifact.field.previewOnly=true")); + assertFalse(evidence.contains("optimizer:other")); + } + private static GpuKernelDescriptor descriptor() { return new GpuKernelDescriptor( "kernel", diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java new file mode 100644 index 00000000..4559df39 --- /dev/null +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java @@ -0,0 +1,81 @@ +package net.sixik.ga_utils.javatogpu.runtime.opencl; + +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileArtifactDumper; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class OpenClRuntimeIrOptimizerEvidenceSummaryTest { + + @TempDir + Path temporaryDirectory; + + @Test + void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { + Path reportDirectory = temporaryDirectory.resolve("reports").resolve("opencl"); + Files.createDirectories(reportDirectory); + Path workloadGate = reportDirectory.resolve("backend-source-promotion-workload-gate.properties"); + Files.writeString(workloadGate, """ + kernel.count=2 + kernel.0.sourceKernelResource=kernel-a.cl + kernel.1.sourceKernelResource=kernel-b.cl + """); + Path artifactDirectory = reportDirectory + .resolve("runtime-compile-artifacts") + .resolve("kernel-a"); + Files.createDirectories(artifactDirectory); + Files.writeString(artifactDirectory.resolve("backend-module.properties"), """ + resource=kernel-a.cl + backendTarget=OPENCL + """); + Files.writeString(artifactDirectory.resolve( + GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT), """ + status=recorded + pass.count=3 + proposalOnly.count=2 + selectedOptimized.count=1 + rolledBack.count=1 + approvalTemplate.pending.count=1 + approvalTemplate.notApplicable.count=2 + pass.0.passVersion=ir-optimizer:no-op:1 + pass.1.passVersion=ir-optimizer:text-canonicalization:1 + pass.2.passVersion=ir-optimizer:text-canonicalization:1 + """); + + OpenClRuntimeIrOptimizerEvidenceSummary summary = OpenClRuntimeIrOptimizerEvidenceSummary.read(workloadGate); + + assertEquals("recorded", summary.status()); + assertEquals(2, summary.entries().size()); + assertEquals("recorded", summary.entries().get(0).status()); + assertEquals("kernel-a.cl", summary.entries().get(0).kernelResource()); + assertEquals(3, summary.entries().get(0).passCount()); + assertEquals(2, summary.entries().get(0).proposalOnlyCount()); + assertEquals(1, summary.entries().get(0).selectedOptimizedCount()); + assertEquals(1, summary.entries().get(0).rolledBackCount()); + assertEquals(1, summary.entries().get(0).approvalTemplatePendingCount()); + assertEquals(2, summary.entries().get(0).approvalTemplateNotApplicableCount()); + assertEquals("missing", summary.entries().get(1).status()); + assertEquals(3, summary.totalPassCount()); + assertEquals(2, summary.totalProposalOnlyCount()); + assertEquals(1, summary.totalSelectedOptimizedCount()); + assertEquals(1, summary.totalRolledBackCount()); + assertEquals(1, summary.totalApprovalTemplatePendingCount()); + assertEquals(2, summary.totalApprovalTemplateNotApplicableCount()); + assertEquals( + "ir-optimizer:no-op:1=1, ir-optimizer:text-canonicalization:1=2", + summary.providerSummary() + ); + assertTrue(summary.toMarkdown().contains("## Runtime IR Optimizer Evidence")); + assertTrue(summary.toMarkdown().contains("- Proposal-only count: `2`")); + assertTrue(summary.toMarkdown().contains("- Approval templates pending: `1`")); + assertTrue(summary.toMarkdown().contains("- Approval templates not applicable: `2`")); + assertTrue(summary.toMarkdown().contains("- Providers: `ir-optimizer:no-op:1=1, ir-optimizer:text-canonicalization:1=2`")); + assertTrue(summary.toMarkdown().contains("| `kernel-a.cl` | `recorded` | `3` | `2` | `1` | `1` | `1` | `2` | `ir-optimizer:no-op:1=1, ir-optimizer:text-canonicalization:1=2` |")); + assertTrue(summary.toMarkdown().contains("| `kernel-b.cl` | `missing` | `0` | `0` | `0` | `0` | `0` | `0` | `none` |")); + } +} diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java index ec95702a..f50ef190 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java @@ -980,8 +980,8 @@ void validationReportAndHistoryExposeRuntimeSnapshotWorkloadGateEvidence() throw assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("sourcePromotionFirstBlockers=runtime equivalence must execute and pass before backend source promotion=1")); assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("sourcePromotionFirstBlockerFamilies=runtime-equivalence=1")); assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("kernelCount=2")); - assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("kernel.0=inline://integration/image-kernel.cl[diagnostics=1, sourceSwitching=compile-irgpu-source-review/operatorAccepted=false/sourcePromotionFirstBlocker=none, runtimeIr=optimized, optimizerDrift=recorded/2passes/rollback=0/proof=0/acceptedProof=0/blockingProof=0/optimizerFamilies=0/promotionReadyFamilies=0/payloadCompleteFamilies=0/payloadCompleteAll=false/fallback=none, productionMutation=false, sourceReady=true, i3=review-ready, families=none]")); - assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("kernel.1=inline://integration/perlin-kernel.cl[diagnostics=1, sourceSwitching=reject-production-irgpu-source/operatorAccepted=false/sourcePromotionFirstBlocker=runtime equivalence must execute and pass before backend source promotion, runtimeIr=original, optimizerDrift=recorded/3passes/rollback=0/proof=0/acceptedProof=0/blockingProof=0/optimizerFamilies=0/promotionReadyFamilies=0/payloadCompleteFamilies=0/payloadCompleteAll=false/fallback=production-ir-gate-blocked, productionMutation=false, sourceReady=false, i3=blocked, families=source-parity=1]")); + assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("kernel.0=inline://integration/image-kernel.cl[diagnostics=1, sourceSwitching=compile-irgpu-source-review/operatorAccepted=false/sourcePromotionFirstBlocker=none, runtimeIr=optimized, optimizerDrift=recorded/2passes/rollback=0/proof=0/acceptedProof=0/blockingProof=0/optimizerFamilies=0/promotionReadyFamilies=0/payloadCompleteFamilies=0/payloadCompleteAll=false, extensionParticipation=not-recorded/0executions/failedContinued=0/failedClosed=0/fallback=none, productionMutation=false, sourceReady=true, i3=review-ready, families=none]")); + assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("kernel.1=inline://integration/perlin-kernel.cl[diagnostics=1, sourceSwitching=reject-production-irgpu-source/operatorAccepted=false/sourcePromotionFirstBlocker=runtime equivalence must execute and pass before backend source promotion, runtimeIr=original, optimizerDrift=recorded/3passes/rollback=0/proof=0/acceptedProof=0/blockingProof=0/optimizerFamilies=0/promotionReadyFamilies=0/payloadCompleteFamilies=0/payloadCompleteAll=false, extensionParticipation=not-recorded/0executions/failedContinued=0/failedClosed=0/fallback=production-ir-gate-blocked, productionMutation=false, sourceReady=false, i3=blocked, families=source-parity=1]")); assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("productionSourceSwitching=disabled")); assertTrue(entries.get(0).productionPromotionExplainabilityStatus().contains("contract=valid")); assertTrue(entries.get(0).productionPromotionExplainabilityStatus().contains("decisionMode=diagnostic-only")); @@ -1362,6 +1362,28 @@ void validationReporterWritesKernelLaunchAdvisoryReportAndWorkflowFragment() thr "" ) ); + java.nio.file.Files.writeString( + artifactDirectory.resolve("backend-module.properties"), + String.join("\n", + "resource=workload/perlin.cl", + "backendTarget=OPENCL", + "" + ) + ); + java.nio.file.Files.writeString( + artifactDirectory.resolve( + net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileArtifactDumper + .RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT), + String.join("\n", + "status=recorded", + "pass.count=1", + "proposalOnly.count=1", + "selectedOptimized.count=0", + "rolledBack.count=0", + "pass.0.passVersion=ir-optimizer:text-canonicalization:1", + "" + ) + ); String previousGateFile = System.getProperty("javatogpu.opencl.backendSourcePromotionWorkloadGateFile"); String previousReportFile = System.getProperty("javatogpu.opencl.validationReportFile"); String previousSummaryFile = System.getProperty("javatogpu.opencl.kernelLaunchAdvisorySummaryFile"); @@ -1406,6 +1428,9 @@ void validationReporterWritesKernelLaunchAdvisoryReportAndWorkflowFragment() thr assertTrue(reportMarkdown.contains("## Kernel Launch Advisories")); assertTrue(reportMarkdown.contains("- Non-preferred multiple: `1`")); assertTrue(reportMarkdown.contains("`workload/perlin.cl` | `non-preferred-multiple`")); + assertTrue(reportMarkdown.contains("## Runtime IR Optimizer Evidence")); + assertTrue(reportMarkdown.contains("- Proposal-only count: `1`")); + assertTrue(reportMarkdown.contains("ir-optimizer:text-canonicalization:1=1")); assertTrue(summaryMarkdown.contains("## Kernel Launch Advisories")); assertTrue(summaryMarkdown.contains("- Blocking: `0`")); assertTrue(summaryMarkdown.contains("## Kernel Launch Advisory Drift")); diff --git a/settings.gradle b/settings.gradle index 0d56bf57..29359730 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,3 +3,5 @@ include 'test-app' include 'examples-app' include 'processor' include 'ir-validation' +include 'ir-optimizer' +include 'ir-vendor-optimizer' From ca0e6fb5d33deb5b5ae8c3fcf61aceb812316719 Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Sun, 12 Jul 2026 13:37:42 +0300 Subject: [PATCH 15/32] wip --- ROADMAP.md | 7 +- docs/IR-Optimizer.md | 10 +- docs/Runtime-Guide.md | 2 +- ...onstantFoldingPreviewProposalProvider.java | 156 ++++++++-- ...IrSafeLocalCsePreviewProposalProvider.java | 282 ++++++++++++++++++ ...rTypedDeadCodePreviewProposalProvider.java | 249 ++++++++++++++++ ...ptimizer.GpuIrOptimizationProposalProvider | 2 + ...antFoldingPreviewProposalProviderTest.java | 40 +++ ...GpuIrOptimizationProposalRegistryTest.java | 2 + .../GpuIrProposalRuntimeBridgePassTest.java | 8 +- ...feLocalCsePreviewProposalProviderTest.java | 163 ++++++++++ ...edDeadCodePreviewProposalProviderTest.java | 140 +++++++++ .../GpuIrSafetyValidatorTest.java | 53 ++-- .../GpuRuntimeCompileArtifactDumper.java | 279 +++++++++++++++++ ...enClRuntimeIrOptimizerEvidenceSummary.java | 185 +++++++++++- .../GpuRuntimeCompileArtifactDumperTest.java | 123 +++++++- ...RuntimeIrOptimizerEvidenceSummaryTest.java | 76 ++++- .../opencl/OpenClValidationReportTest.java | 44 +++ 18 files changed, 1741 insertions(+), 80 deletions(-) create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrSafeLocalCsePreviewProposalProvider.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedDeadCodePreviewProposalProvider.java create mode 100644 ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrSafeLocalCsePreviewProposalProviderTest.java create mode 100644 ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedDeadCodePreviewProposalProviderTest.java diff --git a/ROADMAP.md b/ROADMAP.md index 5a0bf075..4ef51895 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -675,7 +675,12 @@ Current rule: `ir-validation` remains strictly read-only. Optimizer modules may `GpuIrOptimizationPolicy` now provides the backend-neutral optimizer control surface for optimizer profile, optimization level, proposal-only mode, mutation permission, fast-math permission, vendor adaptation, register-pressure splitting, rollback requirement, and proof requirement. `GpuIrOptimizationProposalRequest` preserves its existing constructors while exposing the normalized policy view and serializing policy fields into context metadata, so proposal providers and runtime adapters can consume one shared policy contract without depending on OpenCL-only flags or runtime-local prototype details. - [x] Add first backend-neutral optimizer families only after the contract is stable. `GpuIrTextCanonicalizationProposalProvider` handles semantics-neutral transitional text normalization, and `GpuIrHelperDependencyDeduplicationProposalProvider` now performs the first backend-neutral metadata cleanup by removing duplicate helper dependencies while preserving first-occurrence order and leaving body/typed/backend output data unchanged. More aggressive families such as constant folding, dead-code cleanup, safe local CSE, private-array scalarization, and typed-body peephole rewrites remain future proposal-only candidates until pre/post validation plus runtime-equivalence evidence are available. - `GpuIrConstantFoldingPreviewProposalProvider` now adds the first constant-folding proof surface by scanning typed IR for simple plain-decimal literal binary expressions and recording candidate, operator, numeric-kind, and first-fold evidence without proposing a rewrite. + `GpuIrConstantFoldingPreviewProposalProvider` now adds the first constant-folding proof surface by scanning typed IR for simple plain-decimal literal binary expressions and recording candidate, operator, numeric-kind, first-fold, skipped-blocker, policy, proof, and safety evidence without proposing a rewrite. It explicitly records divide-by-zero, non-even division, unsupported operator, non-literal operand, and non-plain literal blockers, plus `runtimeEquivalenceRequiredBeforeRewrite`, `approvalRequiredBeforeRewrite`, and unresolved integer-overflow / floating-rounding proof flags so the future rewrite path cannot graduate without stronger evidence. + OpenCL runtime validation now aggregates that preview evidence into `constantFoldingPreview.*` fields in `runtime-ir-optimizer-evidence.properties` and surfaces pass, candidate, and skipped-blocker counts in the `Runtime IR Optimizer Evidence` report section, making constant-folding readiness visible without enabling optimized IR selection. + `GpuIrSafeLocalCsePreviewProposalProvider` now adds the matching safe-local-CSE proof surface by scanning typed IR for repeated local expressions and recording expression, candidate-expression, duplicate-expression, equivalence-class, blocker, policy, proof, and safety evidence without proposing a rewrite. It explicitly records unsupported-operator, impure-operand, and control-flow-boundary blockers plus required runtime-equivalence/approval gates and unresolved dominance / side-effect-freedom proof flags, so CSE readiness can be measured without enabling optimized IR selection. + OpenCL runtime validation now aggregates that preview evidence into `safeLocalCsePreview.*` fields in `runtime-ir-optimizer-evidence.properties` and surfaces pass, candidate-expression, duplicate-expression, and blocker counts in the `Runtime IR Optimizer Evidence` report section. + `GpuIrTypedDeadCodePreviewProposalProvider` now adds the first typed dead-code proof surface by walking typed-body roots and recording node, reachable-node, unreachable-node, unreachable-kind, blocker, policy, proof, and safety evidence without proposing a rewrite. It explicitly records missing-root, missing-child-reference, and side-effecting-unreachable-node blockers plus required runtime-equivalence/approval gates and unresolved side-effect-freedom proof flags, so dead-code cleanup readiness can be measured without enabling optimized IR selection. + OpenCL runtime validation now aggregates that preview evidence into `typedDeadCodePreview.*` fields in `runtime-ir-optimizer-evidence.properties` and surfaces pass, unreachable-node, and blocker counts in the `Runtime IR Optimizer Evidence` report section. - [x] Keep vendor optimization as a separate optional provider layer. `ir-vendor-optimizer` now exists as a separate optional module skeleton. It contributes vendor proposals only through `GpuIrVendorOptimizationProposalProvider` and `GpuIrVendorOptimizationProposalRegistry`, while the default runtime bridge keeps loading only backend-neutral proposal providers. Future vendor modules may consume target device profile, backend compiler feedback, resource metrics, driver quirks, launch advisories, and promotion evidence, but missing modules, missing matching device evidence, or disabled vendor adaptation must leave backend-neutral IR selection unchanged. - [x] Gate production optimizer selection through approval-bound artifacts. diff --git a/docs/IR-Optimizer.md b/docs/IR-Optimizer.md index 41ecfc30..1bc53819 100644 --- a/docs/IR-Optimizer.md +++ b/docs/IR-Optimizer.md @@ -21,7 +21,15 @@ The first real proposal provider is `GpuIrTextCanonicalizationProposalProvider`. `GpuIrHelperDependencyDeduplicationProposalProvider` is the first backend-neutral metadata cleanup pass. It removes duplicate helper-dependency entries from method bodies while preserving first-occurrence order and leaving text bodies, typed bodies, backend outputs, and the original artifact unchanged. It is proposal-only by default and exists to prove the real rewrite/proposal path without changing kernel semantics. -`GpuIrConstantFoldingPreviewProposalProvider` is a preview-only proof surface for future typed constant folding. It scans typed IR for simple binary expressions with plain decimal literal operands, records candidate counts, operator breakdown, numeric-kind breakdown, and the first folded value in proof fields, and always returns `NO_CHANGE`. It deliberately does not produce an optimized artifact, even when mutation is allowed. +`GpuIrConstantFoldingPreviewProposalProvider` is a preview-only proof surface for future typed constant folding. It scans typed IR for simple binary expressions with plain decimal literal operands, records candidate counts, operator breakdown, numeric-kind breakdown, first folded value, skipped blocker counts, policy state, and required proof gates, and always returns `NO_CHANGE`. It deliberately does not produce an optimized artifact, even when mutation is allowed. + +The preview records blockers such as divide-by-zero, non-even division, unsupported operators, non-literal operands, and non-plain literals. It also records that runtime-equivalence and approval are required before any future rewrite, and that integer-overflow / floating-point-rounding safety is not yet proven. This keeps constant folding useful as evidence while preventing it from silently becoming an applied transform. + +`GpuIrSafeLocalCsePreviewProposalProvider` is a second preview-only proof surface for future safe local common-subexpression elimination. It scans typed IR for repeated local expressions, records expression/candidate/duplicate/equivalence-class counts, and reports blockers such as unsupported operators, impure operands, and control-flow boundaries. It always returns `NO_CHANGE`, marks the proof as preview-only, and records that dominance, side-effect freedom, runtime equivalence, and approval must be proven before any future rewrite can graduate. + +`GpuIrTypedDeadCodePreviewProposalProvider` is a third preview-only proof surface for future typed dead-code cleanup. It walks typed-body roots, records reachable versus unreachable node counts, unreachable-kind breakdown, missing root / missing child-reference blockers, and side-effecting unreachable-node blockers. It always returns `NO_CHANGE`, marks the proof as preview-only, and records that runtime equivalence, approval, and side-effect freedom must be proven before any future cleanup rewrite can graduate. + +OpenCL validation aggregates these proof fields into `constantFoldingPreview.*`, `safeLocalCsePreview.*`, and `typedDeadCodePreview.*` summary fields inside `runtime-ir-optimizer-evidence.properties`. The generated `Runtime IR Optimizer Evidence` Markdown section shows constant-folding preview pass/candidate/blocker counts, safe-local-CSE pass/candidate-expression/duplicate-expression/blocker counts, and typed dead-code pass/unreachable-node/blocker counts. These fields are report-only and do not participate in source switching, selected-IR replacement, or production mutation gates. ## Immutable Proposal Flow diff --git a/docs/Runtime-Guide.md b/docs/Runtime-Guide.md index 9259d9bd..006541e4 100644 --- a/docs/Runtime-Guide.md +++ b/docs/Runtime-Guide.md @@ -233,7 +233,7 @@ OpenCL isolated runtime-equivalence checks write raw pipeline comparison cases i Non-analysis optimizer passes with runtime-equivalence payload evidence also produce `runtime-optimizer-family-equivalence-payload.properties` plus durable files under `runtime-optimizer-family-equivalence-payload/family--/pass-/`. Each pass directory contains `manifest.properties`, CPU/reference, pre-optimization, post-optimization, tolerance, failure-fixture, and diagnostics properties files. When proof fields contain structured case evidence, the component files include deterministic indexed fields for case names, inputs, raw outputs, tolerances, equivalence flags, and diagnostics. Missing components are materialized with `status=not-recorded`; they are never inferred from another pass. Artifact paths are normalized and must remain inside the kernel's `runtime-compile-artifacts` directory. These files are diagnostic evidence only and cannot enable source switching or production mutation. -When the optional IR optimizer bridge participates, each kernel directory may also receive `runtime-ir-optimizer-evidence.properties`. The artifact records only proposal/provider evidence from stable `javatogpu.ir-optimizer` sources: pass counts, proposal-only versus selected optimized counts, rollback counts, IR identities, proof fields, approval-template applicability, and diagnostics. `openClValidationReport` aggregates this into `Runtime IR Optimizer Evidence` for CI visibility. The section is evidence-only and does not participate in history, source switching, or production mutation gates. +When the optional IR optimizer bridge participates, each kernel directory may also receive `runtime-ir-optimizer-evidence.properties`. The artifact records only proposal/provider evidence from stable `javatogpu.ir-optimizer` sources: pass counts, proposal-only versus selected optimized counts, rollback counts, IR identities, proof fields, approval-template applicability, and diagnostics. Constant-folding preview proof is also aggregated into `constantFoldingPreview.*` fields for pass count, candidate count, skipped blockers, required runtime-equivalence/approval gates, and unresolved integer-overflow / floating-point-rounding safety flags. Safe-local-CSE preview proof is aggregated into `safeLocalCsePreview.*` fields for pass count, expression/candidate/duplicate/equivalence-class counts, unsupported-operator / impure-operand / control-flow-boundary blockers, required runtime-equivalence/approval gates, and unresolved dominance / side-effect-freedom proof flags. Typed dead-code preview proof is aggregated into `typedDeadCodePreview.*` fields for pass count, typed/reachable/unreachable node counts, missing-root / missing-child-reference / side-effecting-unreachable blockers, required runtime-equivalence/approval gates, and unresolved side-effect-freedom proof flags. `openClValidationReport` aggregates this into `Runtime IR Optimizer Evidence` for CI visibility, including constant-folding, safe-local-CSE, and typed dead-code preview candidate and blocker totals. The section is evidence-only and does not participate in history, source switching, selected-IR replacement, or production mutation gates. The same index records `familyBinding.*`. Binding remains `not-bound` unless exactly one optimizer family has passed runtime evidence whose comparison mode is explicitly family-specific (`optimizer-family::...`). Descriptor-versus-reconstructed-source evidence is pipeline validation and is deliberately rejected as optimizer-family proof. diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingPreviewProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingPreviewProposalProvider.java index ac6c386d..ac2b06fc 100644 --- a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingPreviewProposalProvider.java +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingPreviewProposalProvider.java @@ -59,24 +59,36 @@ private record Preview( int methodBodyCount, int typedBodyCount, int candidateCount, + int skippedNonPlainLiteralCount, + int skippedDivideByZeroCount, + int skippedNonEvenDivisionCount, + int skippedUnsupportedOperatorCount, + int skippedNonLiteralOperandCount, Map operatorCounts, Map numericKindCounts, String firstCandidate, String firstOperator, String firstNumericKind, - String firstFoldedValue + String firstFoldedValue, + String firstSkippedReason ) { private static Preview from(IrGpuArtifact artifact) { int methodBodyCount = artifact.module().methodBodies().size(); int typedBodyCount = 0; int candidateCount = 0; + int skippedNonPlainLiteralCount = 0; + int skippedDivideByZeroCount = 0; + int skippedNonEvenDivisionCount = 0; + int skippedUnsupportedOperatorCount = 0; + int skippedNonLiteralOperandCount = 0; LinkedHashMap operatorCounts = new LinkedHashMap<>(); LinkedHashMap numericKindCounts = new LinkedHashMap<>(); String firstCandidate = "none"; String firstOperator = "none"; String firstNumericKind = "none"; String firstFoldedValue = "none"; + String firstSkippedReason = "none"; for (IrGpuMethodBody methodBody : artifact.module().methodBodies()) { if (!methodBody.typedBody().available()) { continue; @@ -84,18 +96,30 @@ private static Preview from(IrGpuArtifact artifact) { typedBodyCount++; Map nodesById = nodesById(methodBody); for (IrGpuTypedNode node : methodBody.typedBody().nodes()) { - Optional candidate = foldCandidate(methodBody.name(), node, nodesById); - if (candidate.isEmpty()) { + FoldAnalysis analysis = analyzeFoldCandidate(methodBody.name(), node, nodesById); + if (analysis.candidate().isEmpty()) { + switch (analysis.skippedReason()) { + case "non-plain-literal" -> skippedNonPlainLiteralCount++; + case "division-by-zero" -> skippedDivideByZeroCount++; + case "non-even-division" -> skippedNonEvenDivisionCount++; + case "unsupported-operator" -> skippedUnsupportedOperatorCount++; + case "non-literal-operand" -> skippedNonLiteralOperandCount++; + default -> { } + } + if ("none".equals(firstSkippedReason) && !"none".equals(analysis.skippedReason())) { + firstSkippedReason = analysis.skippedReason(); + } continue; } + FoldCandidate candidate = analysis.candidate().orElseThrow(); candidateCount++; - operatorCounts.merge(candidate.get().operator(), 1, Integer::sum); - numericKindCounts.merge(candidate.get().numericKind(), 1, Integer::sum); + operatorCounts.merge(candidate.operator(), 1, Integer::sum); + numericKindCounts.merge(candidate.numericKind(), 1, Integer::sum); if ("none".equals(firstCandidate)) { - firstCandidate = candidate.get().summary(); - firstOperator = candidate.get().operator(); - firstNumericKind = candidate.get().numericKind(); - firstFoldedValue = candidate.get().foldedValue(); + firstCandidate = candidate.summary(); + firstOperator = candidate.operator(); + firstNumericKind = candidate.numericKind(); + firstFoldedValue = candidate.foldedValue(); } } } @@ -103,12 +127,18 @@ private static Preview from(IrGpuArtifact artifact) { methodBodyCount, typedBodyCount, candidateCount, + skippedNonPlainLiteralCount, + skippedDivideByZeroCount, + skippedNonEvenDivisionCount, + skippedUnsupportedOperatorCount, + skippedNonLiteralOperandCount, Map.copyOf(operatorCounts), Map.copyOf(numericKindCounts), firstCandidate, firstOperator, firstNumericKind, - firstFoldedValue + firstFoldedValue, + firstSkippedReason ); } @@ -127,16 +157,31 @@ private Map fields() { fields.put("methodBody.count", Integer.toString(methodBodyCount)); fields.put("typedBody.count", Integer.toString(typedBodyCount)); fields.put("candidate.count", Integer.toString(candidateCount)); + fields.put("skipped.nonPlainLiteral.count", Integer.toString(skippedNonPlainLiteralCount)); + fields.put("skipped.divideByZero.count", Integer.toString(skippedDivideByZeroCount)); + fields.put("skipped.nonEvenDivision.count", Integer.toString(skippedNonEvenDivisionCount)); + fields.put("skipped.unsupportedOperator.count", Integer.toString(skippedUnsupportedOperatorCount)); + fields.put("skipped.nonLiteralOperand.count", Integer.toString(skippedNonLiteralOperandCount)); fields.put("rewrite.proposed", "false"); fields.put("mutationRequired", "false"); fields.put("productionAffecting", "false"); fields.put("previewOnly", "true"); + fields.put("policy.proposalOnly", "true"); + fields.put("policy.mutationAllowed", "false"); + fields.put("policy.fastMathAllowed", "false"); + fields.put("policy.rollbackRequired", "true"); + fields.put("policy.proofRequired", "true"); + fields.put("proof.runtimeEquivalenceRequiredBeforeRewrite", "true"); + fields.put("proof.approvalRequiredBeforeRewrite", "true"); + fields.put("safety.integerOverflowProven", "false"); + fields.put("safety.floatingPointRoundingProven", "false"); fields.put("operator.counts", countsSummary(operatorCounts)); fields.put("numericKind.counts", countsSummary(numericKindCounts)); fields.put("firstCandidate", firstCandidate); fields.put("firstOperator", firstOperator); fields.put("firstNumericKind", firstNumericKind); fields.put("firstFoldedValue", firstFoldedValue); + fields.put("firstSkippedReason", firstSkippedReason); fields.put("firstBlocker", candidateCount == 0 ? "no-simple-literal-binary-candidates" : "preview-only-no-rewrite"); return Map.copyOf(fields); } @@ -166,6 +211,17 @@ private String summary() { } } + private record FoldAnalysis(Optional candidate, String skippedReason) { + + private static FoldAnalysis candidate(FoldCandidate candidate) { + return new FoldAnalysis(Optional.of(candidate), "none"); + } + + private static FoldAnalysis skipped(String reason) { + return new FoldAnalysis(Optional.empty(), reason); + } + } + private static Map nodesById(IrGpuMethodBody methodBody) { LinkedHashMap nodes = new LinkedHashMap<>(); for (IrGpuTypedNode node : methodBody.typedBody().nodes()) { @@ -174,59 +230,92 @@ private static Map nodesById(IrGpuMethodBody methodBody return Map.copyOf(nodes); } - private static Optional foldCandidate( + private static FoldAnalysis analyzeFoldCandidate( String methodName, IrGpuTypedNode node, Map nodesById ) { if (!"GpuIrBinary".equals(node.kind())) { - return Optional.empty(); + return FoldAnalysis.skipped("none"); } String operator = node.attributes().getOrDefault("operator", ""); if (!List.of("+", "-", "*", "/").contains(operator)) { - return Optional.empty(); + return FoldAnalysis.skipped("unsupported-operator"); } - Optional left = literalChild(node, "left", nodesById); - Optional right = literalChild(node, "right", nodesById); - if (left.isEmpty() || right.isEmpty()) { - return Optional.empty(); + LiteralRead left = literalChild(node, "left", nodesById); + LiteralRead right = literalChild(node, "right", nodesById); + if (left.value().isEmpty() || right.value().isEmpty()) { + if ("non-plain-literal".equals(left.skippedReason()) || "non-plain-literal".equals(right.skippedReason())) { + return FoldAnalysis.skipped("non-plain-literal"); + } + return FoldAnalysis.skipped("non-literal-operand"); + } + FoldResult folded = fold(operator, left.value().orElseThrow(), right.value().orElseThrow()); + if (folded.value().isEmpty()) { + return FoldAnalysis.skipped(folded.skippedReason()); } - Optional folded = fold(operator, left.get(), right.get()); - String numericKind = numericKind(left.get(), right.get()); - return folded.map(value -> new FoldCandidate( + String numericKind = numericKind(left.value().orElseThrow(), right.value().orElseThrow()); + return FoldAnalysis.candidate(new FoldCandidate( methodName, node.id(), operator, - left.get(), - right.get(), + left.value().orElseThrow(), + right.value().orElseThrow(), numericKind, - value + folded.value().orElseThrow() )); } - private static Optional literalChild( + private record LiteralRead(Optional value, String skippedReason) { + + private static LiteralRead found(String value) { + return new LiteralRead(Optional.of(value), "none"); + } + + private static LiteralRead skipped(String reason) { + return new LiteralRead(Optional.empty(), reason); + } + } + + private static LiteralRead literalChild( IrGpuTypedNode node, String childName, Map nodesById ) { List childIds = node.children().getOrDefault(childName, List.of()); if (childIds.size() != 1) { - return Optional.empty(); + return LiteralRead.skipped("non-literal-operand"); } IrGpuTypedNode child = nodesById.get(childIds.get(0)); if (child == null || !"GpuIrLiteral".equals(child.kind())) { - return Optional.empty(); + return LiteralRead.skipped("non-literal-operand"); + } + String value = child.attributes().get("sourceText"); + if (value == null || value.isBlank()) { + return LiteralRead.skipped("non-plain-literal"); } - return Optional.ofNullable(child.attributes().get("sourceText")) - .filter(value -> !value.isBlank()) - .filter(GpuIrConstantFoldingPreviewProposalProvider::isPlainDecimalLiteral); + if (!isPlainDecimalLiteral(value)) { + return LiteralRead.skipped("non-plain-literal"); + } + return LiteralRead.found(value); } - private static Optional fold(String operator, String left, String right) { + private record FoldResult(Optional value, String skippedReason) { + + private static FoldResult folded(String value) { + return new FoldResult(Optional.of(value), "none"); + } + + private static FoldResult skipped(String reason) { + return new FoldResult(Optional.empty(), reason); + } + } + + private static FoldResult fold(String operator, String left, String right) { BigDecimal leftValue = new BigDecimal(left); BigDecimal rightValue = new BigDecimal(right); if ("/".equals(operator) && BigDecimal.ZERO.compareTo(rightValue) == 0) { - return Optional.empty(); + return FoldResult.skipped("division-by-zero"); } BigDecimal folded = switch (operator) { case "+" -> leftValue.add(rightValue); @@ -237,7 +326,10 @@ private static Optional fold(String operator, String left, String right) : null; default -> null; }; - return folded == null ? Optional.empty() : Optional.of(folded.stripTrailingZeros().toPlainString()); + if (folded == null) { + return FoldResult.skipped("/".equals(operator) ? "non-even-division" : "unsupported-operator"); + } + return FoldResult.folded(folded.stripTrailingZeros().toPlainString()); } private static boolean dividesEvenly(BigDecimal left, BigDecimal right) { diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrSafeLocalCsePreviewProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrSafeLocalCsePreviewProposalProvider.java new file mode 100644 index 00000000..6183cfc4 --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrSafeLocalCsePreviewProposalProvider.java @@ -0,0 +1,282 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * Preview-only local CSE readiness analysis for repeated pure typed expressions. + */ +public final class GpuIrSafeLocalCsePreviewProposalProvider implements GpuIrOptimizationProposalProvider { + + public static final String PROVIDER_ID = GpuIrOptimizerModule.MODULE_ID + ".safe-local-cse-preview"; + public static final String PROVIDER_VERSION = PROVIDER_ID + ":1"; + + @Override + public GpuIrOptimizationProposal propose(GpuIrOptimizationProposalRequest request) { + IrGpuArtifact original = request.originalArtifact(); + Preview preview = Preview.from(original); + return new GpuIrOptimizationProposal( + extensionId(), + extensionVersion(), + original, + Optional.empty(), + GpuIrOptimizationProposalDecision.NO_CHANGE, + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.safe-local-cse-preview", + preview.verdict(), + preview.fields() + ), + "", + List.of(preview.diagnostic()) + ); + } + + @Override + public String extensionId() { + return PROVIDER_ID; + } + + @Override + public String extensionVersion() { + return PROVIDER_VERSION; + } + + @Override + public int extensionOrder() { + return 410; + } + + private record Preview( + int methodBodyCount, + int typedBodyCount, + int expressionCount, + int candidateExpressionCount, + int duplicateExpressionCount, + int equivalenceClassCount, + int blockedUnsupportedOperatorCount, + int blockedImpureOperandCount, + int blockedControlFlowBoundaryCount, + Map operatorCounts, + String firstCandidate, + String firstBlocker + ) { + + private static Preview from(IrGpuArtifact artifact) { + int methodBodyCount = artifact.module().methodBodies().size(); + int typedBodyCount = 0; + int expressionCount = 0; + int candidateExpressionCount = 0; + int duplicateExpressionCount = 0; + int blockedUnsupportedOperatorCount = 0; + int blockedImpureOperandCount = 0; + int blockedControlFlowBoundaryCount = 0; + LinkedHashMap operatorCounts = new LinkedHashMap<>(); + LinkedHashMap expressionCounts = new LinkedHashMap<>(); + LinkedHashMap firstExpressionSummary = new LinkedHashMap<>(); + String firstBlocker = "none"; + + for (IrGpuMethodBody methodBody : artifact.module().methodBodies()) { + if (!methodBody.typedBody().available()) { + continue; + } + typedBodyCount++; + Map nodesById = nodesById(methodBody); + if (hasControlFlowBoundary(methodBody)) { + blockedControlFlowBoundaryCount++; + if ("none".equals(firstBlocker)) { + firstBlocker = "control-flow-boundary-present"; + } + } + for (IrGpuTypedNode node : methodBody.typedBody().nodes()) { + if (!"GpuIrBinary".equals(node.kind())) { + continue; + } + expressionCount++; + CandidateAnalysis analysis = analyzeCandidate(methodBody.name(), node, nodesById); + if (analysis.candidate().isEmpty()) { + switch (analysis.blocker()) { + case "unsupported-operator" -> blockedUnsupportedOperatorCount++; + case "impure-operand" -> blockedImpureOperandCount++; + default -> { } + } + if ("none".equals(firstBlocker) && !"none".equals(analysis.blocker())) { + firstBlocker = analysis.blocker(); + } + continue; + } + Candidate candidate = analysis.candidate().orElseThrow(); + candidateExpressionCount++; + operatorCounts.merge(candidate.operator(), 1, Integer::sum); + expressionCounts.merge(candidate.fingerprint(), 1, Integer::sum); + firstExpressionSummary.putIfAbsent(candidate.fingerprint(), candidate.summary()); + } + } + + int equivalenceClassCount = 0; + String firstCandidate = "none"; + for (Map.Entry entry : expressionCounts.entrySet()) { + if (entry.getValue() <= 1) { + continue; + } + equivalenceClassCount++; + duplicateExpressionCount += entry.getValue(); + if ("none".equals(firstCandidate)) { + firstCandidate = firstExpressionSummary.getOrDefault(entry.getKey(), entry.getKey()); + } + } + if (equivalenceClassCount == 0 && "none".equals(firstBlocker)) { + firstBlocker = candidateExpressionCount == 0 ? "no-pure-local-expressions" : "no-repeated-pure-expressions"; + } + + return new Preview( + methodBodyCount, + typedBodyCount, + expressionCount, + candidateExpressionCount, + duplicateExpressionCount, + equivalenceClassCount, + blockedUnsupportedOperatorCount, + blockedImpureOperandCount, + blockedControlFlowBoundaryCount, + Map.copyOf(operatorCounts), + firstCandidate, + firstBlocker + ); + } + + private String verdict() { + return equivalenceClassCount == 0 ? "preview-no-candidates" : "preview-candidates-recorded"; + } + + private String diagnostic() { + return equivalenceClassCount == 0 + ? "safe local CSE preview found no repeated pure typed expressions" + : "safe local CSE preview recorded " + equivalenceClassCount + " repeated pure expression class(es); no rewrite was proposed"; + } + + private Map fields() { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("methodBody.count", Integer.toString(methodBodyCount)); + fields.put("typedBody.count", Integer.toString(typedBodyCount)); + fields.put("expression.count", Integer.toString(expressionCount)); + fields.put("candidateExpression.count", Integer.toString(candidateExpressionCount)); + fields.put("duplicateExpression.count", Integer.toString(duplicateExpressionCount)); + fields.put("equivalenceClass.count", Integer.toString(equivalenceClassCount)); + fields.put("blocked.unsupportedOperator.count", Integer.toString(blockedUnsupportedOperatorCount)); + fields.put("blocked.impureOperand.count", Integer.toString(blockedImpureOperandCount)); + fields.put("blocked.controlFlowBoundary.count", Integer.toString(blockedControlFlowBoundaryCount)); + fields.put("rewrite.proposed", "false"); + fields.put("mutationRequired", "false"); + fields.put("productionAffecting", "false"); + fields.put("previewOnly", "true"); + fields.put("policy.proposalOnly", "true"); + fields.put("policy.mutationAllowed", "false"); + fields.put("policy.rollbackRequired", "true"); + fields.put("policy.proofRequired", "true"); + fields.put("proof.runtimeEquivalenceRequiredBeforeRewrite", "true"); + fields.put("proof.approvalRequiredBeforeRewrite", "true"); + fields.put("safety.dominanceProven", "false"); + fields.put("safety.sideEffectFreedomProven", "false"); + fields.put("safety.valueNumberingScope", "method-local-preview"); + fields.put("operator.counts", countsSummary(operatorCounts)); + fields.put("firstCandidate", firstCandidate); + fields.put("firstBlocker", equivalenceClassCount == 0 ? firstBlocker : "preview-only-no-rewrite"); + return Map.copyOf(fields); + } + + private static String countsSummary(Map counts) { + if (counts == null || counts.isEmpty()) { + return "{}"; + } + return counts.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .map(entry -> entry.getKey() + "=" + entry.getValue()) + .collect(Collectors.joining(",", "{", "}")); + } + } + + private record Candidate(String methodName, int nodeId, String operator, String fingerprint) { + private String summary() { + return methodName + "#" + nodeId + "=" + fingerprint; + } + } + + private record CandidateAnalysis(Optional candidate, String blocker) { + private static CandidateAnalysis candidate(Candidate candidate) { + return new CandidateAnalysis(Optional.of(candidate), "none"); + } + + private static CandidateAnalysis blocked(String blocker) { + return new CandidateAnalysis(Optional.empty(), blocker); + } + } + + private static CandidateAnalysis analyzeCandidate( + String methodName, + IrGpuTypedNode node, + Map nodesById + ) { + String operator = node.attributes().getOrDefault("operator", ""); + if (!List.of("+", "-", "*", "&", "|", "^", "==", "!=", "<", "<=", ">", ">=").contains(operator)) { + return CandidateAnalysis.blocked("unsupported-operator"); + } + Optional left = pureFingerprint(node, "left", nodesById); + Optional right = pureFingerprint(node, "right", nodesById); + if (left.isEmpty() || right.isEmpty()) { + return CandidateAnalysis.blocked("impure-operand"); + } + String fingerprint = "binary(" + operator + "," + left.orElseThrow() + "," + right.orElseThrow() + ")"; + return CandidateAnalysis.candidate(new Candidate(methodName, node.id(), operator, fingerprint)); + } + + private static Optional pureFingerprint( + IrGpuTypedNode node, + String childName, + Map nodesById + ) { + List childIds = node.children().getOrDefault(childName, List.of()); + if (childIds.size() != 1) { + return Optional.empty(); + } + IrGpuTypedNode child = nodesById.get(childIds.get(0)); + if (child == null) { + return Optional.empty(); + } + if ("GpuIrLiteral".equals(child.kind())) { + return Optional.of("literal:" + child.attributes().getOrDefault("sourceText", "")); + } + if ("GpuIrVariableRef".equals(child.kind())) { + return Optional.of("var:" + child.attributes().getOrDefault("name", "")); + } + if ("GpuIrBinary".equals(child.kind())) { + CandidateAnalysis nested = analyzeCandidate("nested", child, nodesById); + return nested.candidate().map(Candidate::fingerprint); + } + return Optional.empty(); + } + + private static boolean hasControlFlowBoundary(IrGpuMethodBody methodBody) { + for (IrGpuTypedNode node : methodBody.typedBody().nodes()) { + if (List.of("GpuIrIf", "GpuIrForLoop", "GpuIrWhileLoop", "GpuIrDoWhileLoop", "GpuIrSwitch").contains(node.kind())) { + return true; + } + } + return false; + } + + private static Map nodesById(IrGpuMethodBody methodBody) { + LinkedHashMap nodes = new LinkedHashMap<>(); + for (IrGpuTypedNode node : methodBody.typedBody().nodes()) { + nodes.put(node.id(), node); + } + return Map.copyOf(nodes); + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedDeadCodePreviewProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedDeadCodePreviewProposalProvider.java new file mode 100644 index 00000000..daabc0e8 --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedDeadCodePreviewProposalProvider.java @@ -0,0 +1,249 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; + +import java.util.ArrayDeque; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Preview-only typed dead-code readiness analysis for unreachable typed IR nodes. + */ +public final class GpuIrTypedDeadCodePreviewProposalProvider implements GpuIrOptimizationProposalProvider { + + public static final String PROVIDER_ID = GpuIrOptimizerModule.MODULE_ID + ".typed-dead-code-preview"; + public static final String PROVIDER_VERSION = PROVIDER_ID + ":1"; + + @Override + public GpuIrOptimizationProposal propose(GpuIrOptimizationProposalRequest request) { + IrGpuArtifact original = request.originalArtifact(); + Preview preview = Preview.from(original); + return new GpuIrOptimizationProposal( + extensionId(), + extensionVersion(), + original, + Optional.empty(), + GpuIrOptimizationProposalDecision.NO_CHANGE, + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.typed-dead-code-preview", + preview.verdict(), + preview.fields() + ), + "", + List.of(preview.diagnostic()) + ); + } + + @Override + public String extensionId() { + return PROVIDER_ID; + } + + @Override + public String extensionVersion() { + return PROVIDER_VERSION; + } + + @Override + public int extensionOrder() { + return 420; + } + + private record Preview( + int methodBodyCount, + int typedBodyCount, + int nodeCount, + int reachableNodeCount, + int unreachableNodeCount, + int rootMissingCount, + int missingChildReferenceCount, + int sideEffectingUnreachableNodeCount, + Map unreachableKindCounts, + String firstUnreachableNode, + String firstBlocker + ) { + + private static Preview from(IrGpuArtifact artifact) { + int methodBodyCount = artifact.module().methodBodies().size(); + int typedBodyCount = 0; + int nodeCount = 0; + int reachableNodeCount = 0; + int unreachableNodeCount = 0; + int rootMissingCount = 0; + int missingChildReferenceCount = 0; + int sideEffectingUnreachableNodeCount = 0; + LinkedHashMap unreachableKindCounts = new LinkedHashMap<>(); + String firstUnreachableNode = "none"; + String firstBlocker = "none"; + + for (IrGpuMethodBody methodBody : artifact.module().methodBodies()) { + if (!methodBody.typedBody().available()) { + continue; + } + typedBodyCount++; + IrGpuTypedBody typedBody = methodBody.typedBody(); + Map nodesById = nodesById(typedBody); + Reachability reachability = reachableNodeIds(typedBody, nodesById); + nodeCount += typedBody.nodes().size(); + reachableNodeCount += reachability.reachableNodeIds().size(); + rootMissingCount += reachability.rootMissingCount(); + missingChildReferenceCount += reachability.missingChildReferenceCount(); + for (IrGpuTypedNode node : typedBody.nodes()) { + if (reachability.reachableNodeIds().contains(node.id())) { + continue; + } + unreachableNodeCount++; + unreachableKindCounts.merge(node.kind(), 1, Integer::sum); + if (isSideEffecting(node)) { + sideEffectingUnreachableNodeCount++; + if ("none".equals(firstBlocker)) { + firstBlocker = "side-effecting-unreachable-node"; + } + } + if ("none".equals(firstUnreachableNode)) { + firstUnreachableNode = methodBody.name() + "#" + node.id() + "=" + node.kind(); + } + } + if (reachability.rootMissingCount() > 0 && "none".equals(firstBlocker)) { + firstBlocker = "missing-root-node"; + } + if (reachability.missingChildReferenceCount() > 0 && "none".equals(firstBlocker)) { + firstBlocker = "missing-child-reference"; + } + } + if (unreachableNodeCount == 0 && "none".equals(firstBlocker)) { + firstBlocker = nodeCount == 0 ? "no-typed-nodes" : "no-unreachable-typed-nodes"; + } + + return new Preview( + methodBodyCount, + typedBodyCount, + nodeCount, + reachableNodeCount, + unreachableNodeCount, + rootMissingCount, + missingChildReferenceCount, + sideEffectingUnreachableNodeCount, + Map.copyOf(unreachableKindCounts), + firstUnreachableNode, + firstBlocker + ); + } + + private String verdict() { + return unreachableNodeCount == 0 ? "preview-no-candidates" : "preview-candidates-recorded"; + } + + private String diagnostic() { + return unreachableNodeCount == 0 + ? "typed dead-code preview found no unreachable typed nodes" + : "typed dead-code preview recorded " + unreachableNodeCount + " unreachable typed node(s); no rewrite was proposed"; + } + + private Map fields() { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("methodBody.count", Integer.toString(methodBodyCount)); + fields.put("typedBody.count", Integer.toString(typedBodyCount)); + fields.put("node.count", Integer.toString(nodeCount)); + fields.put("reachableNode.count", Integer.toString(reachableNodeCount)); + fields.put("unreachableNode.count", Integer.toString(unreachableNodeCount)); + fields.put("blocked.missingRoot.count", Integer.toString(rootMissingCount)); + fields.put("blocked.missingChildReference.count", Integer.toString(missingChildReferenceCount)); + fields.put("blocked.sideEffectingUnreachableNode.count", Integer.toString(sideEffectingUnreachableNodeCount)); + fields.put("rewrite.proposed", "false"); + fields.put("mutationRequired", "false"); + fields.put("productionAffecting", "false"); + fields.put("previewOnly", "true"); + fields.put("policy.proposalOnly", "true"); + fields.put("policy.mutationAllowed", "false"); + fields.put("policy.rollbackRequired", "true"); + fields.put("policy.proofRequired", "true"); + fields.put("proof.runtimeEquivalenceRequiredBeforeRewrite", "true"); + fields.put("proof.approvalRequiredBeforeRewrite", "true"); + fields.put("safety.sideEffectFreedomProven", "false"); + fields.put("safety.reachabilityScope", "typed-body-root-preview"); + fields.put("unreachableKind.counts", countsSummary(unreachableKindCounts)); + fields.put("firstUnreachableNode", firstUnreachableNode); + fields.put("firstBlocker", unreachableNodeCount == 0 ? firstBlocker : "preview-only-no-rewrite"); + return Map.copyOf(fields); + } + + private static String countsSummary(Map counts) { + if (counts == null || counts.isEmpty()) { + return "{}"; + } + return counts.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .map(entry -> entry.getKey() + "=" + entry.getValue()) + .collect(Collectors.joining(",", "{", "}")); + } + } + + private record Reachability(Set reachableNodeIds, int rootMissingCount, int missingChildReferenceCount) { + } + + private static Reachability reachableNodeIds(IrGpuTypedBody typedBody, Map nodesById) { + HashSet reachable = new HashSet<>(); + ArrayDeque pending = new ArrayDeque<>(); + int rootMissingCount = 0; + int missingChildReferenceCount = 0; + for (Integer rootNodeId : typedBody.rootNodeIds()) { + if (rootNodeId == null || !nodesById.containsKey(rootNodeId)) { + rootMissingCount++; + continue; + } + pending.add(rootNodeId); + } + while (!pending.isEmpty()) { + int nodeId = pending.removeFirst(); + if (!reachable.add(nodeId)) { + continue; + } + IrGpuTypedNode node = nodesById.get(nodeId); + if (node == null) { + continue; + } + for (List childIds : node.children().values()) { + for (Integer childId : childIds) { + if (childId == null || !nodesById.containsKey(childId)) { + missingChildReferenceCount++; + continue; + } + if (!reachable.contains(childId)) { + pending.add(childId); + } + } + } + } + return new Reachability(Set.copyOf(reachable), rootMissingCount, missingChildReferenceCount); + } + + private static boolean isSideEffecting(IrGpuTypedNode node) { + return List.of( + "GpuIrAssignment", + "GpuIrHelperCall", + "GpuIrIntrinsicCall", + "GpuIrReturn", + "GpuIrBreak", + "GpuIrContinue", + "GpuIrLoopBreak" + ).contains(node.kind()); + } + + private static Map nodesById(IrGpuTypedBody typedBody) { + LinkedHashMap nodes = new LinkedHashMap<>(); + for (IrGpuTypedNode node : typedBody.nodes()) { + nodes.put(node.id(), node); + } + return Map.copyOf(nodes); + } +} diff --git a/ir-optimizer/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrOptimizationProposalProvider b/ir-optimizer/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrOptimizationProposalProvider index 25084855..5751f36c 100644 --- a/ir-optimizer/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrOptimizationProposalProvider +++ b/ir-optimizer/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrOptimizationProposalProvider @@ -2,3 +2,5 @@ net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrNoOpProposalProvider net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrTextCanonicalizationProposalProvider net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrHelperDependencyDeduplicationProposalProvider net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrConstantFoldingPreviewProposalProvider +net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrSafeLocalCsePreviewProposalProvider +net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrTypedDeadCodePreviewProposalProvider diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingPreviewProposalProviderTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingPreviewProposalProviderTest.java index dfe237c2..ce729d0b 100644 --- a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingPreviewProposalProviderTest.java +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingPreviewProposalProviderTest.java @@ -42,8 +42,16 @@ void recordsSimpleLiteralBinaryCandidatesWithoutProposingRewrite() { assertSame(original, proposal.originalArtifact()); assertEquals("preview-candidates-recorded", proposal.proofArtifact().verdict()); assertEquals("1", proposal.proofArtifact().fields().get("candidate.count")); + assertEquals("0", proposal.proofArtifact().fields().get("skipped.nonPlainLiteral.count")); + assertEquals("0", proposal.proofArtifact().fields().get("skipped.divideByZero.count")); assertEquals("false", proposal.proofArtifact().fields().get("rewrite.proposed")); assertEquals("true", proposal.proofArtifact().fields().get("previewOnly")); + assertEquals("true", proposal.proofArtifact().fields().get("policy.proposalOnly")); + assertEquals("false", proposal.proofArtifact().fields().get("policy.mutationAllowed")); + assertEquals("true", proposal.proofArtifact().fields().get("proof.runtimeEquivalenceRequiredBeforeRewrite")); + assertEquals("true", proposal.proofArtifact().fields().get("proof.approvalRequiredBeforeRewrite")); + assertEquals("false", proposal.proofArtifact().fields().get("safety.integerOverflowProven")); + assertEquals("false", proposal.proofArtifact().fields().get("safety.floatingPointRoundingProven")); assertEquals("{+=1}", proposal.proofArtifact().fields().get("operator.counts")); assertEquals("{integer=1}", proposal.proofArtifact().fields().get("numericKind.counts")); assertEquals("+", proposal.proofArtifact().fields().get("firstOperator")); @@ -109,11 +117,43 @@ void skipsUnsafeOrNonPlainLiteralShapes() { assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); assertEquals("preview-no-candidates", proposal.proofArtifact().verdict()); assertEquals("0", proposal.proofArtifact().fields().get("candidate.count")); + assertEquals("1", proposal.proofArtifact().fields().get("skipped.nonPlainLiteral.count")); + assertEquals("1", proposal.proofArtifact().fields().get("skipped.divideByZero.count")); + assertEquals("0", proposal.proofArtifact().fields().get("skipped.nonEvenDivision.count")); + assertEquals("0", proposal.proofArtifact().fields().get("skipped.unsupportedOperator.count")); + assertEquals("0", proposal.proofArtifact().fields().get("skipped.nonLiteralOperand.count")); assertEquals("{}", proposal.proofArtifact().fields().get("operator.counts")); assertEquals("{}", proposal.proofArtifact().fields().get("numericKind.counts")); + assertEquals("division-by-zero", proposal.proofArtifact().fields().get("firstSkippedReason")); assertEquals("no-simple-literal-binary-candidates", proposal.proofArtifact().fields().get("firstBlocker")); } + @Test + void recordsNonEvenDivisionAndUnsupportedOperatorBlockers() { + IrGpuArtifact original = artifact(typedBody( + new IrGpuTypedNode(0, "GpuIrBinary", Map.of("operator", "/"), Map.of( + "left", List.of(1), + "right", List.of(2) + )), + new IrGpuTypedNode(1, "GpuIrLiteral", Map.of("sourceText", "1"), Map.of()), + new IrGpuTypedNode(2, "GpuIrLiteral", Map.of("sourceText", "3"), Map.of()), + new IrGpuTypedNode(3, "GpuIrBinary", Map.of("operator", "%"), Map.of( + "left", List.of(4), + "right", List.of(5) + )), + new IrGpuTypedNode(4, "GpuIrLiteral", Map.of("sourceText", "5"), Map.of()), + new IrGpuTypedNode(5, "GpuIrLiteral", Map.of("sourceText", "2"), Map.of()) + )); + + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingPreviewProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals("0", proposal.proofArtifact().fields().get("candidate.count")); + assertEquals("1", proposal.proofArtifact().fields().get("skipped.nonEvenDivision.count")); + assertEquals("1", proposal.proofArtifact().fields().get("skipped.unsupportedOperator.count")); + assertEquals("non-even-division", proposal.proofArtifact().fields().get("firstSkippedReason")); + } + @Test void previewStaysNoChangeEvenWhenMutationAllowed() { IrGpuArtifact original = artifact(typedBody( diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalRegistryTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalRegistryTest.java index 163fc9c7..9d1a7a9e 100644 --- a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalRegistryTest.java +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalRegistryTest.java @@ -36,6 +36,8 @@ void serviceLoaderRegistryLoadsBackendNeutralProvidersOnly() { assertTrue(providerIds.contains(GpuIrTextCanonicalizationProposalProvider.PROVIDER_ID)); assertTrue(providerIds.contains(GpuIrHelperDependencyDeduplicationProposalProvider.PROVIDER_ID)); assertTrue(providerIds.contains(GpuIrConstantFoldingPreviewProposalProvider.PROVIDER_ID)); + assertTrue(providerIds.contains(GpuIrSafeLocalCsePreviewProposalProvider.PROVIDER_ID)); + assertTrue(providerIds.contains(GpuIrTypedDeadCodePreviewProposalProvider.PROVIDER_ID)); assertTrue(providerIds.stream().noneMatch(id -> id.contains("vendor"))); } diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePassTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePassTest.java index f2c249b5..21992092 100644 --- a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePassTest.java +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePassTest.java @@ -52,11 +52,13 @@ void runtimeBridgeIsTheOnlyServiceLoadedRuntimePassFromModule() { void bridgeLoadsProposalProvidersInDeterministicOrder() { GpuIrProposalRuntimeBridgePass bridge = new GpuIrProposalRuntimeBridgePass(); - assertEquals(4, bridge.proposalProviders().size()); + assertEquals(6, bridge.proposalProviders().size()); assertInstanceOf(GpuIrNoOpProposalProvider.class, bridge.proposalProviders().get(0)); assertInstanceOf(GpuIrTextCanonicalizationProposalProvider.class, bridge.proposalProviders().get(1)); assertInstanceOf(GpuIrHelperDependencyDeduplicationProposalProvider.class, bridge.proposalProviders().get(2)); assertInstanceOf(GpuIrConstantFoldingPreviewProposalProvider.class, bridge.proposalProviders().get(3)); + assertInstanceOf(GpuIrSafeLocalCsePreviewProposalProvider.class, bridge.proposalProviders().get(4)); + assertInstanceOf(GpuIrTypedDeadCodePreviewProposalProvider.class, bridge.proposalProviders().get(5)); } @Test @@ -68,6 +70,8 @@ void modulePublishesOnlyBackendNeutralProposalProviderDescriptorEntries() throws assertTrue(neutralDescriptor.contains(GpuIrTextCanonicalizationProposalProvider.class.getName())); assertTrue(neutralDescriptor.contains(GpuIrHelperDependencyDeduplicationProposalProvider.class.getName())); assertTrue(neutralDescriptor.contains(GpuIrConstantFoldingPreviewProposalProvider.class.getName())); + assertTrue(neutralDescriptor.contains(GpuIrSafeLocalCsePreviewProposalProvider.class.getName())); + assertTrue(neutralDescriptor.contains(GpuIrTypedDeadCodePreviewProposalProvider.class.getName())); assertTrue(neutralDescriptor.stream().noneMatch(value -> value.contains("irvendoroptimizer"))); assertTrue(vendorDescriptor.isEmpty()); } @@ -81,7 +85,7 @@ void bridgeKeepsCanonicalizationProposalOnlyByDefault() { assertSame(original, report.artifact().orElseThrow()); assertFalse(report.requiresRollback()); - assertEquals(4, report.passReports().size()); + assertEquals(6, report.passReports().size()); assertEquals(GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, report.passReports().get(1).stage()); assertEquals(GpuRuntimeIrOptimizationOutcome.SKIPPED, report.passReports().get(1).outcome()); assertEquals("proposal-only", report.passReports().get(1).proofStatus()); diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrSafeLocalCsePreviewProposalProviderTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrSafeLocalCsePreviewProposalProviderTest.java new file mode 100644 index 00000000..28537927 --- /dev/null +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrSafeLocalCsePreviewProposalProviderTest.java @@ -0,0 +1,163 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBodyIndex; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuSourceLocation; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuIrSafeLocalCsePreviewProposalProviderTest { + + @Test + void recordsRepeatedPureLocalExpressionCandidatesWithoutProposingRewrite() { + IrGpuArtifact original = artifact(typedBody( + new IrGpuTypedNode(0, "GpuIrReturn", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(2), + "right", List.of(3) + )), + new IrGpuTypedNode(2, "GpuIrVariableRef", Map.of("name", "left"), Map.of()), + new IrGpuTypedNode(3, "GpuIrVariableRef", Map.of("name", "right"), Map.of()), + new IrGpuTypedNode(4, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(5), + "right", List.of(6) + )), + new IrGpuTypedNode(5, "GpuIrVariableRef", Map.of("name", "left"), Map.of()), + new IrGpuTypedNode(6, "GpuIrVariableRef", Map.of("name", "right"), Map.of()) + )); + + GpuIrOptimizationProposal proposal = new GpuIrSafeLocalCsePreviewProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertFalse(proposal.hasOptimizedArtifact()); + assertSame(original, proposal.originalArtifact()); + assertEquals("preview-candidates-recorded", proposal.proofArtifact().verdict()); + assertEquals("2", proposal.proofArtifact().fields().get("expression.count")); + assertEquals("2", proposal.proofArtifact().fields().get("candidateExpression.count")); + assertEquals("2", proposal.proofArtifact().fields().get("duplicateExpression.count")); + assertEquals("1", proposal.proofArtifact().fields().get("equivalenceClass.count")); + assertEquals("{+=2}", proposal.proofArtifact().fields().get("operator.counts")); + assertEquals("false", proposal.proofArtifact().fields().get("rewrite.proposed")); + assertEquals("true", proposal.proofArtifact().fields().get("previewOnly")); + assertEquals("true", proposal.proofArtifact().fields().get("proof.runtimeEquivalenceRequiredBeforeRewrite")); + assertEquals("true", proposal.proofArtifact().fields().get("proof.approvalRequiredBeforeRewrite")); + assertEquals("false", proposal.proofArtifact().fields().get("safety.dominanceProven")); + assertEquals("false", proposal.proofArtifact().fields().get("safety.sideEffectFreedomProven")); + assertEquals("method-local-preview", proposal.proofArtifact().fields().get("safety.valueNumberingScope")); + assertEquals("preview-only-no-rewrite", proposal.proofArtifact().fields().get("firstBlocker")); + assertTrue(proposal.proofArtifact().fields().get("firstCandidate").contains("binary(+,var:left,var:right)")); + assertTrue(proposal.diagnostics().get(0).contains("no rewrite was proposed")); + } + + @Test + void recordsUnsupportedOperatorImpureOperandAndControlFlowBlockers() { + IrGpuArtifact original = artifact(typedBody( + new IrGpuTypedNode(0, "GpuIrIf", Map.of(), Map.of("condition", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", "%"), Map.of( + "left", List.of(2), + "right", List.of(3) + )), + new IrGpuTypedNode(2, "GpuIrVariableRef", Map.of("name", "left"), Map.of()), + new IrGpuTypedNode(3, "GpuIrVariableRef", Map.of("name", "right"), Map.of()), + new IrGpuTypedNode(4, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(5), + "right", List.of(6) + )), + new IrGpuTypedNode(5, "GpuIrHelperCall", Map.of("helper", "sideEffect"), Map.of()), + new IrGpuTypedNode(6, "GpuIrVariableRef", Map.of("name", "value"), Map.of()) + )); + + GpuIrOptimizationProposal proposal = new GpuIrSafeLocalCsePreviewProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertEquals("preview-no-candidates", proposal.proofArtifact().verdict()); + assertEquals("2", proposal.proofArtifact().fields().get("expression.count")); + assertEquals("0", proposal.proofArtifact().fields().get("candidateExpression.count")); + assertEquals("0", proposal.proofArtifact().fields().get("equivalenceClass.count")); + assertEquals("1", proposal.proofArtifact().fields().get("blocked.unsupportedOperator.count")); + assertEquals("1", proposal.proofArtifact().fields().get("blocked.impureOperand.count")); + assertEquals("1", proposal.proofArtifact().fields().get("blocked.controlFlowBoundary.count")); + assertEquals("control-flow-boundary-present", proposal.proofArtifact().fields().get("firstBlocker")); + } + + @Test + void previewStaysNoChangeEvenWhenMutationAllowed() { + IrGpuArtifact original = artifact(typedBody( + new IrGpuTypedNode(0, "GpuIrBinary", Map.of("operator", "*"), Map.of( + "left", List.of(1), + "right", List.of(2) + )), + new IrGpuTypedNode(1, "GpuIrLiteral", Map.of("sourceText", "4"), Map.of()), + new IrGpuTypedNode(2, "GpuIrLiteral", Map.of("sourceText", "5"), Map.of()), + new IrGpuTypedNode(3, "GpuIrBinary", Map.of("operator", "*"), Map.of( + "left", List.of(4), + "right", List.of(5) + )), + new IrGpuTypedNode(4, "GpuIrLiteral", Map.of("sourceText", "4"), Map.of()), + new IrGpuTypedNode(5, "GpuIrLiteral", Map.of("sourceText", "5"), Map.of()) + )); + + GpuIrOptimizationSandwichReport report = GpuIrOptimizationSandwichRunner.alwaysValid() + .run(new GpuIrOptimizationProposalRequest(original, "diagnostic", true, Map.of()), + new GpuIrSafeLocalCsePreviewProposalProvider()); + + assertEquals(GpuIrOptimizationSandwichStatus.NO_CHANGE, report.status()); + assertSame(original, report.selectedArtifact()); + assertTrue(report.selectedOriginal()); + assertFalse(report.selectedOptimized()); + assertEquals("1", report.proposal().orElseThrow().proofArtifact().fields().get("equivalenceClass.count")); + } + + @Test + void previewProviderIsServiceLoaded() { + List providers = ServiceLoader + .load(GpuIrOptimizationProposalProvider.class) + .stream() + .map(ServiceLoader.Provider::get) + .filter(GpuIrSafeLocalCsePreviewProposalProvider.class::isInstance) + .toList(); + + assertEquals(1, providers.size()); + } + + private static IrGpuTypedBody typedBody(IrGpuTypedNode... nodes) { + return new IrGpuTypedBody(IrGpuTypedBody.FORMAT, List.of(nodes[0].id()), List.of(nodes)); + } + + private static IrGpuArtifact artifact(IrGpuTypedBody typedBody) { + IrGpuMethodBody methodBody = new IrGpuMethodBody( + "entry", + "kernel", + "jtg_kernel", + "ir-text-v1", + "body\n return 1\n", + typedBody, + IrGpuBodyIndex.empty(), + List.of(), + IrGpuSourceLocation.unknown("kernel") + ); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + new IrGpuModule("kernel", "jtg_kernel", List.of(), List.of(), List.of(methodBody)), + List.of(IrGpuBackendOutput.openClSource("test.cl")), + "opencl", + "off" + ); + } +} diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedDeadCodePreviewProposalProviderTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedDeadCodePreviewProposalProviderTest.java new file mode 100644 index 00000000..e7201bb6 --- /dev/null +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedDeadCodePreviewProposalProviderTest.java @@ -0,0 +1,140 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBodyIndex; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuSourceLocation; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuIrTypedDeadCodePreviewProposalProviderTest { + + @Test + void recordsUnreachableTypedNodesWithoutProposingRewrite() { + IrGpuArtifact original = artifact(typedBody( + List.of(0), + new IrGpuTypedNode(0, "GpuIrReturn", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrVariableRef", Map.of("name", "live"), Map.of()), + new IrGpuTypedNode(2, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(3), + "right", List.of(4) + )), + new IrGpuTypedNode(3, "GpuIrLiteral", Map.of("sourceText", "1"), Map.of()), + new IrGpuTypedNode(4, "GpuIrLiteral", Map.of("sourceText", "2"), Map.of()) + )); + + GpuIrOptimizationProposal proposal = new GpuIrTypedDeadCodePreviewProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertFalse(proposal.hasOptimizedArtifact()); + assertSame(original, proposal.originalArtifact()); + assertEquals("preview-candidates-recorded", proposal.proofArtifact().verdict()); + assertEquals("5", proposal.proofArtifact().fields().get("node.count")); + assertEquals("2", proposal.proofArtifact().fields().get("reachableNode.count")); + assertEquals("3", proposal.proofArtifact().fields().get("unreachableNode.count")); + assertEquals("{GpuIrBinary=1,GpuIrLiteral=2}", proposal.proofArtifact().fields().get("unreachableKind.counts")); + assertEquals("false", proposal.proofArtifact().fields().get("rewrite.proposed")); + assertEquals("true", proposal.proofArtifact().fields().get("previewOnly")); + assertEquals("true", proposal.proofArtifact().fields().get("proof.runtimeEquivalenceRequiredBeforeRewrite")); + assertEquals("true", proposal.proofArtifact().fields().get("proof.approvalRequiredBeforeRewrite")); + assertEquals("false", proposal.proofArtifact().fields().get("safety.sideEffectFreedomProven")); + assertEquals("typed-body-root-preview", proposal.proofArtifact().fields().get("safety.reachabilityScope")); + assertEquals("preview-only-no-rewrite", proposal.proofArtifact().fields().get("firstBlocker")); + assertTrue(proposal.proofArtifact().fields().get("firstUnreachableNode").contains("GpuIrBinary")); + assertTrue(proposal.diagnostics().get(0).contains("no rewrite was proposed")); + } + + @Test + void recordsMissingReferencesAndSideEffectingUnreachableBlockers() { + IrGpuArtifact original = artifact(typedBody( + List.of(99, 0), + new IrGpuTypedNode(0, "GpuIrReturn", Map.of(), Map.of("value", List.of(1, 42))), + new IrGpuTypedNode(1, "GpuIrLiteral", Map.of("sourceText", "1"), Map.of()), + new IrGpuTypedNode(2, "GpuIrHelperCall", Map.of("helper", "sideEffect"), Map.of()) + )); + + GpuIrOptimizationProposal proposal = new GpuIrTypedDeadCodePreviewProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertEquals("preview-candidates-recorded", proposal.proofArtifact().verdict()); + assertEquals("3", proposal.proofArtifact().fields().get("node.count")); + assertEquals("2", proposal.proofArtifact().fields().get("reachableNode.count")); + assertEquals("1", proposal.proofArtifact().fields().get("unreachableNode.count")); + assertEquals("1", proposal.proofArtifact().fields().get("blocked.missingRoot.count")); + assertEquals("1", proposal.proofArtifact().fields().get("blocked.missingChildReference.count")); + assertEquals("1", proposal.proofArtifact().fields().get("blocked.sideEffectingUnreachableNode.count")); + assertEquals("{GpuIrHelperCall=1}", proposal.proofArtifact().fields().get("unreachableKind.counts")); + } + + @Test + void previewStaysNoChangeEvenWhenMutationAllowed() { + IrGpuArtifact original = artifact(typedBody( + List.of(0), + new IrGpuTypedNode(0, "GpuIrReturn", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrLiteral", Map.of("sourceText", "1"), Map.of()), + new IrGpuTypedNode(2, "GpuIrLiteral", Map.of("sourceText", "2"), Map.of()) + )); + + GpuIrOptimizationSandwichReport report = GpuIrOptimizationSandwichRunner.alwaysValid() + .run(new GpuIrOptimizationProposalRequest(original, "diagnostic", true, Map.of()), + new GpuIrTypedDeadCodePreviewProposalProvider()); + + assertEquals(GpuIrOptimizationSandwichStatus.NO_CHANGE, report.status()); + assertSame(original, report.selectedArtifact()); + assertTrue(report.selectedOriginal()); + assertFalse(report.selectedOptimized()); + assertEquals("1", report.proposal().orElseThrow().proofArtifact().fields().get("unreachableNode.count")); + } + + @Test + void previewProviderIsServiceLoaded() { + List providers = ServiceLoader + .load(GpuIrOptimizationProposalProvider.class) + .stream() + .map(ServiceLoader.Provider::get) + .filter(GpuIrTypedDeadCodePreviewProposalProvider.class::isInstance) + .toList(); + + assertEquals(1, providers.size()); + } + + private static IrGpuTypedBody typedBody(List rootNodeIds, IrGpuTypedNode... nodes) { + return new IrGpuTypedBody(IrGpuTypedBody.FORMAT, rootNodeIds, List.of(nodes)); + } + + private static IrGpuArtifact artifact(IrGpuTypedBody typedBody) { + IrGpuMethodBody methodBody = new IrGpuMethodBody( + "entry", + "kernel", + "jtg_kernel", + "ir-text-v1", + "body\n return 1\n", + typedBody, + IrGpuBodyIndex.empty(), + List.of(), + IrGpuSourceLocation.unknown("kernel") + ); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + new IrGpuModule("kernel", "jtg_kernel", List.of(), List.of(), List.of(methodBody)), + List.of(IrGpuBackendOutput.openClSource("test.cl")), + "opencl", + "off" + ); + } +} diff --git a/ir-validation/src/test/java/net/sixik/ga_utils/javatogpu/irvalidation/GpuIrSafetyValidatorTest.java b/ir-validation/src/test/java/net/sixik/ga_utils/javatogpu/irvalidation/GpuIrSafetyValidatorTest.java index 6f941fde..32c7f0de 100644 --- a/ir-validation/src/test/java/net/sixik/ga_utils/javatogpu/irvalidation/GpuIrSafetyValidatorTest.java +++ b/ir-validation/src/test/java/net/sixik/ga_utils/javatogpu/irvalidation/GpuIrSafetyValidatorTest.java @@ -222,7 +222,7 @@ void rejectsMalformedHelperDependencyMetadataEntries() { } @Test - void rejectsMissingEntryPointParameterMetadataList() { + void acceptsMissingEntryPointParameterMetadataListAsEmpty() { GpuIrCompiledMethod method = method( new GpuIrMethod("kernel", List.of(new GpuIrReturn(null))), "jtg_kernel", @@ -231,21 +231,19 @@ void rejectsMissingEntryPointParameterMetadataList() { null ); - GpuIrPassException exception = assertThrows(GpuIrPassException.class, () -> validator.run(context(method))); - - assertTrue(exception.getMessage().contains("missing parameter metadata list")); + assertDoesNotThrow(() -> validator.run(context(method))); + assertTrue(method.parsedMethod().parameters().isEmpty()); } @Test - void rejectsNullEntryPointParameterMetadata() { - GpuIrCompiledMethod method = method( - new GpuIrMethod("kernel", List.of(new GpuIrReturn(null))), - Arrays.asList((ParsedGpuParameter) null) + void rejectsNullEntryPointParameterMetadataAtParsedMethodBoundary() { + assertThrows( + NullPointerException.class, + () -> method( + new GpuIrMethod("kernel", List.of(new GpuIrReturn(null))), + Arrays.asList((ParsedGpuParameter) null) + ) ); - - GpuIrPassException exception = assertThrows(GpuIrPassException.class, () -> validator.run(context(method))); - - assertTrue(exception.getMessage().contains("null parameter metadata")); } @Test @@ -429,24 +427,21 @@ void rejectsMalformedHelperParameterNames() { } @Test - void rejectsNullHelperParameterMetadata() { - GpuIrCompiledMethod nullParameterHelper = method( - new GpuIrMethod("nullParameterHelper", List.of(new GpuIrReturn(null))), - "jtg_null_parameter_helper", - List.of(), - "void", - Arrays.asList((ParsedGpuParameter) null) + void rejectsNullHelperParameterMetadataAtParsedMethodBoundary() { + assertThrows( + NullPointerException.class, + () -> method( + new GpuIrMethod("nullParameterHelper", List.of(new GpuIrReturn(null))), + "jtg_null_parameter_helper", + List.of(), + "void", + Arrays.asList((ParsedGpuParameter) null) + ) ); - GpuIrCompiledMethod method = method(new GpuIrMethod("kernel", List.of(new GpuIrReturn(null)))); - - assertTrue(assertThrows( - GpuIrPassException.class, - () -> validator.run(new GpuIrPassContext(method, List.of(nullParameterHelper), List.of(), true)) - ).getMessage().contains("null helper parameter metadata for jtg_null_parameter_helper")); } @Test - void rejectsMissingHelperParameterMetadataList() { + void acceptsMissingHelperParameterMetadataListAsEmpty() { GpuIrCompiledMethod missingParameterListHelper = method( new GpuIrMethod("missingParameterListHelper", List.of(new GpuIrReturn(null))), "jtg_missing_parameter_list_helper", @@ -456,10 +451,8 @@ void rejectsMissingHelperParameterMetadataList() { ); GpuIrCompiledMethod method = method(new GpuIrMethod("kernel", List.of(new GpuIrReturn(null)))); - assertTrue(assertThrows( - GpuIrPassException.class, - () -> validator.run(new GpuIrPassContext(method, List.of(missingParameterListHelper), List.of(), true)) - ).getMessage().contains("missing helper parameter metadata list for jtg_missing_parameter_list_helper")); + assertDoesNotThrow(() -> validator.run(new GpuIrPassContext(method, List.of(missingParameterListHelper), List.of(), true))); + assertTrue(missingParameterListHelper.parsedMethod().parameters().isEmpty()); } @Test diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java index 902e69b4..24f40e8e 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java @@ -729,6 +729,70 @@ private static String formatRuntimeIrOptimizerEvidence(GpuRuntimeIrOptimizationR builder.append("approvalTemplate.notApplicable.count=") .append(countApprovalTemplateStatus(irOptimizerReports, "not-applicable")) .append('\n'); + ConstantFoldingPreviewEvidence constantFoldingPreview = constantFoldingPreviewEvidence(irOptimizerReports); + builder.append("constantFoldingPreview.pass.count=").append(constantFoldingPreview.passCount()).append('\n'); + builder.append("constantFoldingPreview.candidate.count=").append(constantFoldingPreview.candidateCount()).append('\n'); + builder.append("constantFoldingPreview.skipped.nonPlainLiteral.count=") + .append(constantFoldingPreview.skippedNonPlainLiteralCount()).append('\n'); + builder.append("constantFoldingPreview.skipped.divideByZero.count=") + .append(constantFoldingPreview.skippedDivideByZeroCount()).append('\n'); + builder.append("constantFoldingPreview.skipped.nonEvenDivision.count=") + .append(constantFoldingPreview.skippedNonEvenDivisionCount()).append('\n'); + builder.append("constantFoldingPreview.skipped.unsupportedOperator.count=") + .append(constantFoldingPreview.skippedUnsupportedOperatorCount()).append('\n'); + builder.append("constantFoldingPreview.skipped.nonLiteralOperand.count=") + .append(constantFoldingPreview.skippedNonLiteralOperandCount()).append('\n'); + builder.append("constantFoldingPreview.runtimeEquivalenceRequiredBeforeRewrite=") + .append(constantFoldingPreview.runtimeEquivalenceRequiredBeforeRewrite()).append('\n'); + builder.append("constantFoldingPreview.approvalRequiredBeforeRewrite=") + .append(constantFoldingPreview.approvalRequiredBeforeRewrite()).append('\n'); + builder.append("constantFoldingPreview.integerOverflowProven=") + .append(constantFoldingPreview.integerOverflowProven()).append('\n'); + builder.append("constantFoldingPreview.floatingPointRoundingProven=") + .append(constantFoldingPreview.floatingPointRoundingProven()).append('\n'); + SafeLocalCsePreviewEvidence safeLocalCsePreview = safeLocalCsePreviewEvidence(irOptimizerReports); + builder.append("safeLocalCsePreview.pass.count=").append(safeLocalCsePreview.passCount()).append('\n'); + builder.append("safeLocalCsePreview.expression.count=") + .append(safeLocalCsePreview.expressionCount()).append('\n'); + builder.append("safeLocalCsePreview.candidateExpression.count=") + .append(safeLocalCsePreview.candidateExpressionCount()).append('\n'); + builder.append("safeLocalCsePreview.duplicateExpression.count=") + .append(safeLocalCsePreview.duplicateExpressionCount()).append('\n'); + builder.append("safeLocalCsePreview.equivalenceClass.count=") + .append(safeLocalCsePreview.equivalenceClassCount()).append('\n'); + builder.append("safeLocalCsePreview.blocked.unsupportedOperator.count=") + .append(safeLocalCsePreview.blockedUnsupportedOperatorCount()).append('\n'); + builder.append("safeLocalCsePreview.blocked.impureOperand.count=") + .append(safeLocalCsePreview.blockedImpureOperandCount()).append('\n'); + builder.append("safeLocalCsePreview.blocked.controlFlowBoundary.count=") + .append(safeLocalCsePreview.blockedControlFlowBoundaryCount()).append('\n'); + builder.append("safeLocalCsePreview.runtimeEquivalenceRequiredBeforeRewrite=") + .append(safeLocalCsePreview.runtimeEquivalenceRequiredBeforeRewrite()).append('\n'); + builder.append("safeLocalCsePreview.approvalRequiredBeforeRewrite=") + .append(safeLocalCsePreview.approvalRequiredBeforeRewrite()).append('\n'); + builder.append("safeLocalCsePreview.dominanceProven=") + .append(safeLocalCsePreview.dominanceProven()).append('\n'); + builder.append("safeLocalCsePreview.sideEffectFreedomProven=") + .append(safeLocalCsePreview.sideEffectFreedomProven()).append('\n'); + TypedDeadCodePreviewEvidence typedDeadCodePreview = typedDeadCodePreviewEvidence(irOptimizerReports); + builder.append("typedDeadCodePreview.pass.count=").append(typedDeadCodePreview.passCount()).append('\n'); + builder.append("typedDeadCodePreview.node.count=").append(typedDeadCodePreview.nodeCount()).append('\n'); + builder.append("typedDeadCodePreview.reachableNode.count=") + .append(typedDeadCodePreview.reachableNodeCount()).append('\n'); + builder.append("typedDeadCodePreview.unreachableNode.count=") + .append(typedDeadCodePreview.unreachableNodeCount()).append('\n'); + builder.append("typedDeadCodePreview.blocked.missingRoot.count=") + .append(typedDeadCodePreview.blockedMissingRootCount()).append('\n'); + builder.append("typedDeadCodePreview.blocked.missingChildReference.count=") + .append(typedDeadCodePreview.blockedMissingChildReferenceCount()).append('\n'); + builder.append("typedDeadCodePreview.blocked.sideEffectingUnreachableNode.count=") + .append(typedDeadCodePreview.blockedSideEffectingUnreachableNodeCount()).append('\n'); + builder.append("typedDeadCodePreview.runtimeEquivalenceRequiredBeforeRewrite=") + .append(typedDeadCodePreview.runtimeEquivalenceRequiredBeforeRewrite()).append('\n'); + builder.append("typedDeadCodePreview.approvalRequiredBeforeRewrite=") + .append(typedDeadCodePreview.approvalRequiredBeforeRewrite()).append('\n'); + builder.append("typedDeadCodePreview.sideEffectFreedomProven=") + .append(typedDeadCodePreview.sideEffectFreedomProven()).append('\n'); for (int index = 0; index < irOptimizerReports.size(); index++) { GpuRuntimeIrOptimizationPassReport passReport = irOptimizerReports.get(index); String prefix = "pass." + index + "."; @@ -810,6 +874,221 @@ private static long countApprovalTemplateStatus( .count(); } + private static ConstantFoldingPreviewEvidence constantFoldingPreviewEvidence( + List passReports + ) { + int passCount = 0; + int candidateCount = 0; + int skippedNonPlainLiteralCount = 0; + int skippedDivideByZeroCount = 0; + int skippedNonEvenDivisionCount = 0; + int skippedUnsupportedOperatorCount = 0; + int skippedNonLiteralOperandCount = 0; + boolean runtimeEquivalenceRequiredBeforeRewrite = false; + boolean approvalRequiredBeforeRewrite = false; + boolean integerOverflowProven = false; + boolean floatingPointRoundingProven = false; + for (GpuRuntimeIrOptimizationPassReport passReport : passReports) { + if (passReport == null || passReport.proofArtifact() == null) { + continue; + } + Map fields = passReport.proofArtifact().fields(); + String source = passReport.proofArtifact().source() == null ? "" : passReport.proofArtifact().source(); + if (!source.contains("constant-folding-preview") + && !passReport.optimizerVersion().contains("constant-folding-preview")) { + continue; + } + passCount++; + candidateCount += parseNonNegativeInt(fields.get("candidate.count")); + skippedNonPlainLiteralCount += parseNonNegativeInt(fields.get("skipped.nonPlainLiteral.count")); + skippedDivideByZeroCount += parseNonNegativeInt(fields.get("skipped.divideByZero.count")); + skippedNonEvenDivisionCount += parseNonNegativeInt(fields.get("skipped.nonEvenDivision.count")); + skippedUnsupportedOperatorCount += parseNonNegativeInt(fields.get("skipped.unsupportedOperator.count")); + skippedNonLiteralOperandCount += parseNonNegativeInt(fields.get("skipped.nonLiteralOperand.count")); + runtimeEquivalenceRequiredBeforeRewrite |= parseBoolean(fields.get("proof.runtimeEquivalenceRequiredBeforeRewrite")); + approvalRequiredBeforeRewrite |= parseBoolean(fields.get("proof.approvalRequiredBeforeRewrite")); + integerOverflowProven |= parseBoolean(fields.get("safety.integerOverflowProven")); + floatingPointRoundingProven |= parseBoolean(fields.get("safety.floatingPointRoundingProven")); + } + return new ConstantFoldingPreviewEvidence( + passCount, + candidateCount, + skippedNonPlainLiteralCount, + skippedDivideByZeroCount, + skippedNonEvenDivisionCount, + skippedUnsupportedOperatorCount, + skippedNonLiteralOperandCount, + runtimeEquivalenceRequiredBeforeRewrite, + approvalRequiredBeforeRewrite, + integerOverflowProven, + floatingPointRoundingProven + ); + } + + private static SafeLocalCsePreviewEvidence safeLocalCsePreviewEvidence( + List passReports + ) { + int passCount = 0; + int expressionCount = 0; + int candidateExpressionCount = 0; + int duplicateExpressionCount = 0; + int equivalenceClassCount = 0; + int blockedUnsupportedOperatorCount = 0; + int blockedImpureOperandCount = 0; + int blockedControlFlowBoundaryCount = 0; + boolean runtimeEquivalenceRequiredBeforeRewrite = false; + boolean approvalRequiredBeforeRewrite = false; + boolean dominanceProven = false; + boolean sideEffectFreedomProven = false; + for (GpuRuntimeIrOptimizationPassReport passReport : passReports) { + if (passReport == null || passReport.proofArtifact() == null) { + continue; + } + Map fields = passReport.proofArtifact().fields(); + String source = passReport.proofArtifact().source() == null ? "" : passReport.proofArtifact().source(); + String optimizerVersion = passReport.optimizerVersion() == null ? "" : passReport.optimizerVersion(); + if (!source.contains("safe-local-cse-preview") + && !optimizerVersion.contains("safe-local-cse-preview")) { + continue; + } + passCount++; + expressionCount += parseNonNegativeInt(fields.get("expression.count")); + candidateExpressionCount += parseNonNegativeInt(fields.get("candidateExpression.count")); + duplicateExpressionCount += parseNonNegativeInt(fields.get("duplicateExpression.count")); + equivalenceClassCount += parseNonNegativeInt(fields.get("equivalenceClass.count")); + blockedUnsupportedOperatorCount += parseNonNegativeInt(fields.get("blocked.unsupportedOperator.count")); + blockedImpureOperandCount += parseNonNegativeInt(fields.get("blocked.impureOperand.count")); + blockedControlFlowBoundaryCount += parseNonNegativeInt(fields.get("blocked.controlFlowBoundary.count")); + runtimeEquivalenceRequiredBeforeRewrite |= parseBoolean(fields.get("proof.runtimeEquivalenceRequiredBeforeRewrite")); + approvalRequiredBeforeRewrite |= parseBoolean(fields.get("proof.approvalRequiredBeforeRewrite")); + dominanceProven |= parseBoolean(fields.get("safety.dominanceProven")); + sideEffectFreedomProven |= parseBoolean(fields.get("safety.sideEffectFreedomProven")); + } + return new SafeLocalCsePreviewEvidence( + passCount, + expressionCount, + candidateExpressionCount, + duplicateExpressionCount, + equivalenceClassCount, + blockedUnsupportedOperatorCount, + blockedImpureOperandCount, + blockedControlFlowBoundaryCount, + runtimeEquivalenceRequiredBeforeRewrite, + approvalRequiredBeforeRewrite, + dominanceProven, + sideEffectFreedomProven + ); + } + + private static TypedDeadCodePreviewEvidence typedDeadCodePreviewEvidence( + List passReports + ) { + int passCount = 0; + int nodeCount = 0; + int reachableNodeCount = 0; + int unreachableNodeCount = 0; + int blockedMissingRootCount = 0; + int blockedMissingChildReferenceCount = 0; + int blockedSideEffectingUnreachableNodeCount = 0; + boolean runtimeEquivalenceRequiredBeforeRewrite = false; + boolean approvalRequiredBeforeRewrite = false; + boolean sideEffectFreedomProven = false; + for (GpuRuntimeIrOptimizationPassReport passReport : passReports) { + if (passReport == null || passReport.proofArtifact() == null) { + continue; + } + Map fields = passReport.proofArtifact().fields(); + String source = passReport.proofArtifact().source() == null ? "" : passReport.proofArtifact().source(); + String optimizerVersion = passReport.optimizerVersion() == null ? "" : passReport.optimizerVersion(); + if (!source.contains("typed-dead-code-preview") + && !optimizerVersion.contains("typed-dead-code-preview")) { + continue; + } + passCount++; + nodeCount += parseNonNegativeInt(fields.get("node.count")); + reachableNodeCount += parseNonNegativeInt(fields.get("reachableNode.count")); + unreachableNodeCount += parseNonNegativeInt(fields.get("unreachableNode.count")); + blockedMissingRootCount += parseNonNegativeInt(fields.get("blocked.missingRoot.count")); + blockedMissingChildReferenceCount += parseNonNegativeInt(fields.get("blocked.missingChildReference.count")); + blockedSideEffectingUnreachableNodeCount += parseNonNegativeInt(fields.get("blocked.sideEffectingUnreachableNode.count")); + runtimeEquivalenceRequiredBeforeRewrite |= parseBoolean(fields.get("proof.runtimeEquivalenceRequiredBeforeRewrite")); + approvalRequiredBeforeRewrite |= parseBoolean(fields.get("proof.approvalRequiredBeforeRewrite")); + sideEffectFreedomProven |= parseBoolean(fields.get("safety.sideEffectFreedomProven")); + } + return new TypedDeadCodePreviewEvidence( + passCount, + nodeCount, + reachableNodeCount, + unreachableNodeCount, + blockedMissingRootCount, + blockedMissingChildReferenceCount, + blockedSideEffectingUnreachableNodeCount, + runtimeEquivalenceRequiredBeforeRewrite, + approvalRequiredBeforeRewrite, + sideEffectFreedomProven + ); + } + + private static int parseNonNegativeInt(String value) { + try { + return Math.max(0, Integer.parseInt(value)); + } catch (RuntimeException ignored) { + return 0; + } + } + + private static boolean parseBoolean(String value) { + return "true".equalsIgnoreCase(value) + || "yes".equalsIgnoreCase(value) + || "enabled".equalsIgnoreCase(value) + || "required".equalsIgnoreCase(value); + } + + private record ConstantFoldingPreviewEvidence( + int passCount, + int candidateCount, + int skippedNonPlainLiteralCount, + int skippedDivideByZeroCount, + int skippedNonEvenDivisionCount, + int skippedUnsupportedOperatorCount, + int skippedNonLiteralOperandCount, + boolean runtimeEquivalenceRequiredBeforeRewrite, + boolean approvalRequiredBeforeRewrite, + boolean integerOverflowProven, + boolean floatingPointRoundingProven + ) { + } + + private record SafeLocalCsePreviewEvidence( + int passCount, + int expressionCount, + int candidateExpressionCount, + int duplicateExpressionCount, + int equivalenceClassCount, + int blockedUnsupportedOperatorCount, + int blockedImpureOperandCount, + int blockedControlFlowBoundaryCount, + boolean runtimeEquivalenceRequiredBeforeRewrite, + boolean approvalRequiredBeforeRewrite, + boolean dominanceProven, + boolean sideEffectFreedomProven + ) { + } + + private record TypedDeadCodePreviewEvidence( + int passCount, + int nodeCount, + int reachableNodeCount, + int unreachableNodeCount, + int blockedMissingRootCount, + int blockedMissingChildReferenceCount, + int blockedSideEffectingUnreachableNodeCount, + boolean runtimeEquivalenceRequiredBeforeRewrite, + boolean approvalRequiredBeforeRewrite, + boolean sideEffectFreedomProven + ) { + } + private static ApprovalTemplateEvidence approvalTemplateEvidence(GpuRuntimeIrOptimizationPassReport passReport) { if (passReport == null) { return ApprovalTemplateEvidence.notApplicable("pass-report-missing"); diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java index 93b2808d..721fb59e 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java @@ -96,6 +96,46 @@ int totalApprovalTemplateNotApplicableCount() { return entries.stream().mapToInt(Entry::approvalTemplateNotApplicableCount).sum(); } + int totalConstantFoldingPreviewPassCount() { + return entries.stream().mapToInt(Entry::constantFoldingPreviewPassCount).sum(); + } + + int totalConstantFoldingPreviewCandidateCount() { + return entries.stream().mapToInt(Entry::constantFoldingPreviewCandidateCount).sum(); + } + + int totalConstantFoldingPreviewSkippedCount() { + return entries.stream().mapToInt(Entry::constantFoldingPreviewSkippedCount).sum(); + } + + int totalSafeLocalCsePreviewPassCount() { + return entries.stream().mapToInt(Entry::safeLocalCsePreviewPassCount).sum(); + } + + int totalSafeLocalCsePreviewCandidateExpressionCount() { + return entries.stream().mapToInt(Entry::safeLocalCsePreviewCandidateExpressionCount).sum(); + } + + int totalSafeLocalCsePreviewDuplicateExpressionCount() { + return entries.stream().mapToInt(Entry::safeLocalCsePreviewDuplicateExpressionCount).sum(); + } + + int totalSafeLocalCsePreviewBlockedCount() { + return entries.stream().mapToInt(Entry::safeLocalCsePreviewBlockedCount).sum(); + } + + int totalTypedDeadCodePreviewPassCount() { + return entries.stream().mapToInt(Entry::typedDeadCodePreviewPassCount).sum(); + } + + int totalTypedDeadCodePreviewUnreachableNodeCount() { + return entries.stream().mapToInt(Entry::typedDeadCodePreviewUnreachableNodeCount).sum(); + } + + int totalTypedDeadCodePreviewBlockedCount() { + return entries.stream().mapToInt(Entry::typedDeadCodePreviewBlockedCount).sum(); + } + String providerSummary() { LinkedHashMap counts = new LinkedHashMap<>(); for (Entry entry : entries) { @@ -133,12 +173,27 @@ String toMarkdown() { markdown.append("- Rolled back count: `").append(totalRolledBackCount()).append("`\n"); markdown.append("- Approval templates pending: `").append(totalApprovalTemplatePendingCount()).append("`\n"); markdown.append("- Approval templates not applicable: `").append(totalApprovalTemplateNotApplicableCount()).append("`\n"); + markdown.append("- Constant folding preview passes: `").append(totalConstantFoldingPreviewPassCount()).append("`\n"); + markdown.append("- Constant folding preview candidates: `").append(totalConstantFoldingPreviewCandidateCount()).append("`\n"); + markdown.append("- Constant folding preview skipped blockers: `").append(totalConstantFoldingPreviewSkippedCount()).append("`\n"); + markdown.append("- Safe local CSE preview passes: `").append(totalSafeLocalCsePreviewPassCount()).append("`\n"); + markdown.append("- Safe local CSE preview candidate expressions: `") + .append(totalSafeLocalCsePreviewCandidateExpressionCount()).append("`\n"); + markdown.append("- Safe local CSE preview duplicate expressions: `") + .append(totalSafeLocalCsePreviewDuplicateExpressionCount()).append("`\n"); + markdown.append("- Safe local CSE preview blockers: `") + .append(totalSafeLocalCsePreviewBlockedCount()).append("`\n"); + markdown.append("- Typed dead-code preview passes: `").append(totalTypedDeadCodePreviewPassCount()).append("`\n"); + markdown.append("- Typed dead-code preview unreachable nodes: `") + .append(totalTypedDeadCodePreviewUnreachableNodeCount()).append("`\n"); + markdown.append("- Typed dead-code preview blockers: `") + .append(totalTypedDeadCodePreviewBlockedCount()).append("`\n"); markdown.append("- Providers: `").append(inline(providerSummary())).append("`\n\n"); if (entries.isEmpty()) { return markdown.toString(); } - markdown.append("| Kernel resource | Status | Passes | Proposal-only | Selected | Rolled back | Approval pending | Approval N/A | Providers |\n"); - markdown.append("| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | --- |\n"); + markdown.append("| Kernel resource | Status | Passes | Proposal-only | Selected | Rolled back | Approval pending | Approval N/A | CF candidates | CF skipped | CSE candidates | CSE duplicates | CSE blocked | TDC unreachable | TDC blocked | Providers |\n"); + markdown.append("| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- |\n"); for (Entry entry : entries) { markdown.append("| `").append(table(entry.kernelResource())).append("` | `") .append(table(entry.status())).append("` | `") @@ -148,6 +203,13 @@ String toMarkdown() { .append(entry.rolledBackCount()).append("` | `") .append(entry.approvalTemplatePendingCount()).append("` | `") .append(entry.approvalTemplateNotApplicableCount()).append("` | `") + .append(entry.constantFoldingPreviewCandidateCount()).append("` | `") + .append(entry.constantFoldingPreviewSkippedCount()).append("` | `") + .append(entry.safeLocalCsePreviewCandidateExpressionCount()).append("` | `") + .append(entry.safeLocalCsePreviewDuplicateExpressionCount()).append("` | `") + .append(entry.safeLocalCsePreviewBlockedCount()).append("` | `") + .append(entry.typedDeadCodePreviewUnreachableNodeCount()).append("` | `") + .append(entry.typedDeadCodePreviewBlockedCount()).append("` | `") .append(table(formatProviderCounts(entry.providerCounts()))).append("` |\n"); } markdown.append('\n'); @@ -264,6 +326,39 @@ record Entry( int rolledBackCount, int approvalTemplatePendingCount, int approvalTemplateNotApplicableCount, + int constantFoldingPreviewPassCount, + int constantFoldingPreviewCandidateCount, + int constantFoldingPreviewSkippedNonPlainLiteralCount, + int constantFoldingPreviewSkippedDivideByZeroCount, + int constantFoldingPreviewSkippedNonEvenDivisionCount, + int constantFoldingPreviewSkippedUnsupportedOperatorCount, + int constantFoldingPreviewSkippedNonLiteralOperandCount, + boolean constantFoldingPreviewRuntimeEquivalenceRequiredBeforeRewrite, + boolean constantFoldingPreviewApprovalRequiredBeforeRewrite, + boolean constantFoldingPreviewIntegerOverflowProven, + boolean constantFoldingPreviewFloatingPointRoundingProven, + int safeLocalCsePreviewPassCount, + int safeLocalCsePreviewExpressionCount, + int safeLocalCsePreviewCandidateExpressionCount, + int safeLocalCsePreviewDuplicateExpressionCount, + int safeLocalCsePreviewEquivalenceClassCount, + int safeLocalCsePreviewBlockedUnsupportedOperatorCount, + int safeLocalCsePreviewBlockedImpureOperandCount, + int safeLocalCsePreviewBlockedControlFlowBoundaryCount, + boolean safeLocalCsePreviewRuntimeEquivalenceRequiredBeforeRewrite, + boolean safeLocalCsePreviewApprovalRequiredBeforeRewrite, + boolean safeLocalCsePreviewDominanceProven, + boolean safeLocalCsePreviewSideEffectFreedomProven, + int typedDeadCodePreviewPassCount, + int typedDeadCodePreviewNodeCount, + int typedDeadCodePreviewReachableNodeCount, + int typedDeadCodePreviewUnreachableNodeCount, + int typedDeadCodePreviewBlockedMissingRootCount, + int typedDeadCodePreviewBlockedMissingChildReferenceCount, + int typedDeadCodePreviewBlockedSideEffectingUnreachableNodeCount, + boolean typedDeadCodePreviewRuntimeEquivalenceRequiredBeforeRewrite, + boolean typedDeadCodePreviewApprovalRequiredBeforeRewrite, + boolean typedDeadCodePreviewSideEffectFreedomProven, Map providerCounts ) { @@ -276,12 +371,56 @@ record Entry( rolledBackCount = Math.max(0, rolledBackCount); approvalTemplatePendingCount = Math.max(0, approvalTemplatePendingCount); approvalTemplateNotApplicableCount = Math.max(0, approvalTemplateNotApplicableCount); + constantFoldingPreviewPassCount = Math.max(0, constantFoldingPreviewPassCount); + constantFoldingPreviewCandidateCount = Math.max(0, constantFoldingPreviewCandidateCount); + constantFoldingPreviewSkippedNonPlainLiteralCount = Math.max(0, constantFoldingPreviewSkippedNonPlainLiteralCount); + constantFoldingPreviewSkippedDivideByZeroCount = Math.max(0, constantFoldingPreviewSkippedDivideByZeroCount); + constantFoldingPreviewSkippedNonEvenDivisionCount = Math.max(0, constantFoldingPreviewSkippedNonEvenDivisionCount); + constantFoldingPreviewSkippedUnsupportedOperatorCount = Math.max(0, constantFoldingPreviewSkippedUnsupportedOperatorCount); + constantFoldingPreviewSkippedNonLiteralOperandCount = Math.max(0, constantFoldingPreviewSkippedNonLiteralOperandCount); + safeLocalCsePreviewPassCount = Math.max(0, safeLocalCsePreviewPassCount); + safeLocalCsePreviewExpressionCount = Math.max(0, safeLocalCsePreviewExpressionCount); + safeLocalCsePreviewCandidateExpressionCount = Math.max(0, safeLocalCsePreviewCandidateExpressionCount); + safeLocalCsePreviewDuplicateExpressionCount = Math.max(0, safeLocalCsePreviewDuplicateExpressionCount); + safeLocalCsePreviewEquivalenceClassCount = Math.max(0, safeLocalCsePreviewEquivalenceClassCount); + safeLocalCsePreviewBlockedUnsupportedOperatorCount = Math.max(0, safeLocalCsePreviewBlockedUnsupportedOperatorCount); + safeLocalCsePreviewBlockedImpureOperandCount = Math.max(0, safeLocalCsePreviewBlockedImpureOperandCount); + safeLocalCsePreviewBlockedControlFlowBoundaryCount = Math.max(0, safeLocalCsePreviewBlockedControlFlowBoundaryCount); + typedDeadCodePreviewPassCount = Math.max(0, typedDeadCodePreviewPassCount); + typedDeadCodePreviewNodeCount = Math.max(0, typedDeadCodePreviewNodeCount); + typedDeadCodePreviewReachableNodeCount = Math.max(0, typedDeadCodePreviewReachableNodeCount); + typedDeadCodePreviewUnreachableNodeCount = Math.max(0, typedDeadCodePreviewUnreachableNodeCount); + typedDeadCodePreviewBlockedMissingRootCount = Math.max(0, typedDeadCodePreviewBlockedMissingRootCount); + typedDeadCodePreviewBlockedMissingChildReferenceCount = Math.max(0, typedDeadCodePreviewBlockedMissingChildReferenceCount); + typedDeadCodePreviewBlockedSideEffectingUnreachableNodeCount = Math.max(0, typedDeadCodePreviewBlockedSideEffectingUnreachableNodeCount); providerCounts = normalizeProviderCounts(providerCounts); } + int constantFoldingPreviewSkippedCount() { + return constantFoldingPreviewSkippedNonPlainLiteralCount + + constantFoldingPreviewSkippedDivideByZeroCount + + constantFoldingPreviewSkippedNonEvenDivisionCount + + constantFoldingPreviewSkippedUnsupportedOperatorCount + + constantFoldingPreviewSkippedNonLiteralOperandCount; + } + + int safeLocalCsePreviewBlockedCount() { + return safeLocalCsePreviewBlockedUnsupportedOperatorCount + + safeLocalCsePreviewBlockedImpureOperandCount + + safeLocalCsePreviewBlockedControlFlowBoundaryCount; + } + + int typedDeadCodePreviewBlockedCount() { + return typedDeadCodePreviewBlockedMissingRootCount + + typedDeadCodePreviewBlockedMissingChildReferenceCount + + typedDeadCodePreviewBlockedSideEffectingUnreachableNodeCount; + } + static Entry from(String kernelResource, Properties properties) { if (properties == null || properties.isEmpty()) { - return new Entry(kernelResource, "missing", 0, 0, 0, 0, 0, 0, Map.of()); + return new Entry(kernelResource, "missing", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + false, false, false, false, 0, 0, 0, 0, 0, 0, 0, 0, false, false, false, false, + 0, 0, 0, 0, 0, 0, 0, false, false, false, Map.of()); } return new Entry( kernelResource, @@ -292,10 +431,50 @@ static Entry from(String kernelResource, Properties properties) { parseInt(properties.getProperty("rolledBack.count"), 0), parseInt(properties.getProperty("approvalTemplate.pending.count"), 0), parseInt(properties.getProperty("approvalTemplate.notApplicable.count"), 0), + parseInt(properties.getProperty("constantFoldingPreview.pass.count"), 0), + parseInt(properties.getProperty("constantFoldingPreview.candidate.count"), 0), + parseInt(properties.getProperty("constantFoldingPreview.skipped.nonPlainLiteral.count"), 0), + parseInt(properties.getProperty("constantFoldingPreview.skipped.divideByZero.count"), 0), + parseInt(properties.getProperty("constantFoldingPreview.skipped.nonEvenDivision.count"), 0), + parseInt(properties.getProperty("constantFoldingPreview.skipped.unsupportedOperator.count"), 0), + parseInt(properties.getProperty("constantFoldingPreview.skipped.nonLiteralOperand.count"), 0), + parseBoolean(properties.getProperty("constantFoldingPreview.runtimeEquivalenceRequiredBeforeRewrite")), + parseBoolean(properties.getProperty("constantFoldingPreview.approvalRequiredBeforeRewrite")), + parseBoolean(properties.getProperty("constantFoldingPreview.integerOverflowProven")), + parseBoolean(properties.getProperty("constantFoldingPreview.floatingPointRoundingProven")), + parseInt(properties.getProperty("safeLocalCsePreview.pass.count"), 0), + parseInt(properties.getProperty("safeLocalCsePreview.expression.count"), 0), + parseInt(properties.getProperty("safeLocalCsePreview.candidateExpression.count"), 0), + parseInt(properties.getProperty("safeLocalCsePreview.duplicateExpression.count"), 0), + parseInt(properties.getProperty("safeLocalCsePreview.equivalenceClass.count"), 0), + parseInt(properties.getProperty("safeLocalCsePreview.blocked.unsupportedOperator.count"), 0), + parseInt(properties.getProperty("safeLocalCsePreview.blocked.impureOperand.count"), 0), + parseInt(properties.getProperty("safeLocalCsePreview.blocked.controlFlowBoundary.count"), 0), + parseBoolean(properties.getProperty("safeLocalCsePreview.runtimeEquivalenceRequiredBeforeRewrite")), + parseBoolean(properties.getProperty("safeLocalCsePreview.approvalRequiredBeforeRewrite")), + parseBoolean(properties.getProperty("safeLocalCsePreview.dominanceProven")), + parseBoolean(properties.getProperty("safeLocalCsePreview.sideEffectFreedomProven")), + parseInt(properties.getProperty("typedDeadCodePreview.pass.count"), 0), + parseInt(properties.getProperty("typedDeadCodePreview.node.count"), 0), + parseInt(properties.getProperty("typedDeadCodePreview.reachableNode.count"), 0), + parseInt(properties.getProperty("typedDeadCodePreview.unreachableNode.count"), 0), + parseInt(properties.getProperty("typedDeadCodePreview.blocked.missingRoot.count"), 0), + parseInt(properties.getProperty("typedDeadCodePreview.blocked.missingChildReference.count"), 0), + parseInt(properties.getProperty("typedDeadCodePreview.blocked.sideEffectingUnreachableNode.count"), 0), + parseBoolean(properties.getProperty("typedDeadCodePreview.runtimeEquivalenceRequiredBeforeRewrite")), + parseBoolean(properties.getProperty("typedDeadCodePreview.approvalRequiredBeforeRewrite")), + parseBoolean(properties.getProperty("typedDeadCodePreview.sideEffectFreedomProven")), parseProviderCounts(properties) ); } + private static boolean parseBoolean(String value) { + return "true".equalsIgnoreCase(value) + || "yes".equalsIgnoreCase(value) + || "enabled".equalsIgnoreCase(value) + || "required".equalsIgnoreCase(value); + } + private static String normalize(String value, String fallback) { return value == null || value.isBlank() ? fallback : value; } diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java index 802ab554..13293cf3 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java @@ -2044,10 +2044,19 @@ void dumpsRuntimeIrOptimizerEvidenceArtifactForProposalBridgeReports() { GpuRuntimeIrOptimizationProofArtifact.fromFields( "ir-optimizer.constant-folding-preview", "preview-candidates-recorded", - Map.of( - "candidate.count", "1", - "previewOnly", "true", - "rewrite.proposed", "false" + Map.ofEntries( + Map.entry("candidate.count", "1"), + Map.entry("previewOnly", "true"), + Map.entry("rewrite.proposed", "false"), + Map.entry("skipped.nonPlainLiteral.count", "2"), + Map.entry("skipped.divideByZero.count", "1"), + Map.entry("skipped.nonEvenDivision.count", "1"), + Map.entry("skipped.unsupportedOperator.count", "1"), + Map.entry("skipped.nonLiteralOperand.count", "1"), + Map.entry("proof.runtimeEquivalenceRequiredBeforeRewrite", "true"), + Map.entry("proof.approvalRequiredBeforeRewrite", "true"), + Map.entry("safety.integerOverflowProven", "false"), + Map.entry("safety.floatingPointRoundingProven", "false") ) ), List.of("constant folding preview recorded evidence; no rewrite was proposed") @@ -2059,9 +2068,66 @@ void dumpsRuntimeIrOptimizerEvidenceArtifactForProposalBridgeReports() { "proof:other", List.of("unrelated optimizer evidence") ); + GpuRuntimeIrOptimizationPassReport safeLocalCsePreviewReport = new GpuRuntimeIrOptimizationPassReport( + GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, + "javatogpu.ir-optimizer.safe-local-cse-preview:1", + GpuRuntimeIrOptimizationOutcome.SKIPPED, + "irgpu:sha256:original", + "irgpu:sha256:original", + "not-mutating", + "", + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.safe-local-cse-preview", + "preview-candidates-recorded", + Map.ofEntries( + Map.entry("expression.count", "5"), + Map.entry("candidateExpression.count", "3"), + Map.entry("duplicateExpression.count", "2"), + Map.entry("equivalenceClass.count", "1"), + Map.entry("blocked.unsupportedOperator.count", "1"), + Map.entry("blocked.impureOperand.count", "2"), + Map.entry("blocked.controlFlowBoundary.count", "3"), + Map.entry("previewOnly", "true"), + Map.entry("rewrite.proposed", "false"), + Map.entry("proof.runtimeEquivalenceRequiredBeforeRewrite", "true"), + Map.entry("proof.approvalRequiredBeforeRewrite", "true"), + Map.entry("safety.dominanceProven", "false"), + Map.entry("safety.sideEffectFreedomProven", "false") + ) + ), + List.of("safe local CSE preview recorded evidence; no rewrite was proposed") + ); + GpuRuntimeIrOptimizationPassReport typedDeadCodePreviewReport = new GpuRuntimeIrOptimizationPassReport( + GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, + "javatogpu.ir-optimizer.typed-dead-code-preview:1", + GpuRuntimeIrOptimizationOutcome.SKIPPED, + "irgpu:sha256:original", + "irgpu:sha256:original", + "not-mutating", + "", + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.typed-dead-code-preview", + "preview-candidates-recorded", + Map.ofEntries( + Map.entry("node.count", "8"), + Map.entry("reachableNode.count", "5"), + Map.entry("unreachableNode.count", "3"), + Map.entry("blocked.missingRoot.count", "1"), + Map.entry("blocked.missingChildReference.count", "2"), + Map.entry("blocked.sideEffectingUnreachableNode.count", "1"), + Map.entry("previewOnly", "true"), + Map.entry("rewrite.proposed", "false"), + Map.entry("proof.runtimeEquivalenceRequiredBeforeRewrite", "true"), + Map.entry("proof.approvalRequiredBeforeRewrite", "true"), + Map.entry("safety.sideEffectFreedomProven", "false") + ) + ), + List.of("typed dead-code preview recorded evidence; no rewrite was proposed") + ); GpuRuntimeIrOptimizationReport optimizationReport = new GpuRuntimeIrOptimizationReport( Optional.of(original), - List.of(noOpReport, canonicalizationReport, previewReport, unrelatedReport), + List.of(noOpReport, canonicalizationReport, previewReport, safeLocalCsePreviewReport, + typedDeadCodePreviewReport, unrelatedReport), GpuOptimizationStrategyDecision.none(request) ); GpuRuntimeCompileArtifactSnapshot snapshot = GpuRuntimeCompileArtifactSnapshot.from( @@ -2080,16 +2146,51 @@ void dumpsRuntimeIrOptimizerEvidenceArtifactForProposalBridgeReports() { assertTrue(evidence.contains("status=recorded")); assertTrue(evidence.contains("source=runtime-ir-optimizer")); assertTrue(evidence.contains("providerPrefix=javatogpu.ir-optimizer")); - assertTrue(evidence.contains("pass.count=3")); - assertTrue(evidence.contains("skipped.count=3")); + assertTrue(evidence.contains("pass.count=5")); + assertTrue(evidence.contains("skipped.count=5")); assertTrue(evidence.contains("applied.count=0")); assertTrue(evidence.contains("proposalOnly.count=1")); assertTrue(evidence.contains("selectedOptimized.count=0")); assertTrue(evidence.contains("approvalTemplate.pending.count=1")); - assertTrue(evidence.contains("approvalTemplate.notApplicable.count=2")); + assertTrue(evidence.contains("approvalTemplate.notApplicable.count=4")); + assertTrue(evidence.contains("constantFoldingPreview.pass.count=1")); + assertTrue(evidence.contains("constantFoldingPreview.candidate.count=1")); + assertTrue(evidence.contains("constantFoldingPreview.skipped.nonPlainLiteral.count=2")); + assertTrue(evidence.contains("constantFoldingPreview.skipped.divideByZero.count=1")); + assertTrue(evidence.contains("constantFoldingPreview.skipped.nonEvenDivision.count=1")); + assertTrue(evidence.contains("constantFoldingPreview.skipped.unsupportedOperator.count=1")); + assertTrue(evidence.contains("constantFoldingPreview.skipped.nonLiteralOperand.count=1")); + assertTrue(evidence.contains("constantFoldingPreview.runtimeEquivalenceRequiredBeforeRewrite=true")); + assertTrue(evidence.contains("constantFoldingPreview.approvalRequiredBeforeRewrite=true")); + assertTrue(evidence.contains("constantFoldingPreview.integerOverflowProven=false")); + assertTrue(evidence.contains("constantFoldingPreview.floatingPointRoundingProven=false")); + assertTrue(evidence.contains("safeLocalCsePreview.pass.count=1")); + assertTrue(evidence.contains("safeLocalCsePreview.expression.count=5")); + assertTrue(evidence.contains("safeLocalCsePreview.candidateExpression.count=3")); + assertTrue(evidence.contains("safeLocalCsePreview.duplicateExpression.count=2")); + assertTrue(evidence.contains("safeLocalCsePreview.equivalenceClass.count=1")); + assertTrue(evidence.contains("safeLocalCsePreview.blocked.unsupportedOperator.count=1")); + assertTrue(evidence.contains("safeLocalCsePreview.blocked.impureOperand.count=2")); + assertTrue(evidence.contains("safeLocalCsePreview.blocked.controlFlowBoundary.count=3")); + assertTrue(evidence.contains("safeLocalCsePreview.runtimeEquivalenceRequiredBeforeRewrite=true")); + assertTrue(evidence.contains("safeLocalCsePreview.approvalRequiredBeforeRewrite=true")); + assertTrue(evidence.contains("safeLocalCsePreview.dominanceProven=false")); + assertTrue(evidence.contains("safeLocalCsePreview.sideEffectFreedomProven=false")); + assertTrue(evidence.contains("typedDeadCodePreview.pass.count=1")); + assertTrue(evidence.contains("typedDeadCodePreview.node.count=8")); + assertTrue(evidence.contains("typedDeadCodePreview.reachableNode.count=5")); + assertTrue(evidence.contains("typedDeadCodePreview.unreachableNode.count=3")); + assertTrue(evidence.contains("typedDeadCodePreview.blocked.missingRoot.count=1")); + assertTrue(evidence.contains("typedDeadCodePreview.blocked.missingChildReference.count=2")); + assertTrue(evidence.contains("typedDeadCodePreview.blocked.sideEffectingUnreachableNode.count=1")); + assertTrue(evidence.contains("typedDeadCodePreview.runtimeEquivalenceRequiredBeforeRewrite=true")); + assertTrue(evidence.contains("typedDeadCodePreview.approvalRequiredBeforeRewrite=true")); + assertTrue(evidence.contains("typedDeadCodePreview.sideEffectFreedomProven=false")); assertTrue(evidence.contains("pass.0.passVersion=javatogpu.ir-optimizer.noop:1")); assertTrue(evidence.contains("pass.1.passVersion=javatogpu.ir-optimizer.text-canonicalization:1")); assertTrue(evidence.contains("pass.2.passVersion=javatogpu.ir-optimizer.constant-folding-preview:1")); + assertTrue(evidence.contains("pass.3.passVersion=javatogpu.ir-optimizer.safe-local-cse-preview:1")); + assertTrue(evidence.contains("pass.4.passVersion=javatogpu.ir-optimizer.typed-dead-code-preview:1")); assertTrue(evidence.contains("pass.1.proofStatus=proposal-only")); assertTrue(evidence.contains("pass.1.transformedIrIdentity=irgpu:sha256:canonical")); assertTrue(evidence.contains("pass.1.proofArtifact.source=ir-optimizer.text-canonicalization")); @@ -2103,6 +2204,12 @@ void dumpsRuntimeIrOptimizerEvidenceArtifactForProposalBridgeReports() { assertTrue(evidence.contains("pass.2.approvalTemplate.status=not-applicable")); assertTrue(evidence.contains("pass.2.approvalTemplate.firstBlocker=proposal-decision-not-proposed")); assertTrue(evidence.contains("pass.2.proofArtifact.field.previewOnly=true")); + assertTrue(evidence.contains("pass.3.approvalTemplate.status=not-applicable")); + assertTrue(evidence.contains("pass.3.approvalTemplate.firstBlocker=proposal-decision-not-proposed")); + assertTrue(evidence.contains("pass.3.proofArtifact.field.duplicateExpression.count=2")); + assertTrue(evidence.contains("pass.4.approvalTemplate.status=not-applicable")); + assertTrue(evidence.contains("pass.4.approvalTemplate.firstBlocker=proposal-decision-not-proposed")); + assertTrue(evidence.contains("pass.4.proofArtifact.field.unreachableNode.count=3")); assertFalse(evidence.contains("optimizer:other")); } diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java index 4559df39..174c7ba4 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java @@ -42,6 +42,39 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { rolledBack.count=1 approvalTemplate.pending.count=1 approvalTemplate.notApplicable.count=2 + constantFoldingPreview.pass.count=1 + constantFoldingPreview.candidate.count=3 + constantFoldingPreview.skipped.nonPlainLiteral.count=1 + constantFoldingPreview.skipped.divideByZero.count=1 + constantFoldingPreview.skipped.nonEvenDivision.count=1 + constantFoldingPreview.skipped.unsupportedOperator.count=1 + constantFoldingPreview.skipped.nonLiteralOperand.count=1 + constantFoldingPreview.runtimeEquivalenceRequiredBeforeRewrite=true + constantFoldingPreview.approvalRequiredBeforeRewrite=true + constantFoldingPreview.integerOverflowProven=false + constantFoldingPreview.floatingPointRoundingProven=false + safeLocalCsePreview.pass.count=1 + safeLocalCsePreview.expression.count=6 + safeLocalCsePreview.candidateExpression.count=4 + safeLocalCsePreview.duplicateExpression.count=2 + safeLocalCsePreview.equivalenceClass.count=1 + safeLocalCsePreview.blocked.unsupportedOperator.count=1 + safeLocalCsePreview.blocked.impureOperand.count=2 + safeLocalCsePreview.blocked.controlFlowBoundary.count=3 + safeLocalCsePreview.runtimeEquivalenceRequiredBeforeRewrite=true + safeLocalCsePreview.approvalRequiredBeforeRewrite=true + safeLocalCsePreview.dominanceProven=false + safeLocalCsePreview.sideEffectFreedomProven=false + typedDeadCodePreview.pass.count=1 + typedDeadCodePreview.node.count=8 + typedDeadCodePreview.reachableNode.count=5 + typedDeadCodePreview.unreachableNode.count=3 + typedDeadCodePreview.blocked.missingRoot.count=1 + typedDeadCodePreview.blocked.missingChildReference.count=2 + typedDeadCodePreview.blocked.sideEffectingUnreachableNode.count=1 + typedDeadCodePreview.runtimeEquivalenceRequiredBeforeRewrite=true + typedDeadCodePreview.approvalRequiredBeforeRewrite=true + typedDeadCodePreview.sideEffectFreedomProven=false pass.0.passVersion=ir-optimizer:no-op:1 pass.1.passVersion=ir-optimizer:text-canonicalization:1 pass.2.passVersion=ir-optimizer:text-canonicalization:1 @@ -59,6 +92,25 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { assertEquals(1, summary.entries().get(0).rolledBackCount()); assertEquals(1, summary.entries().get(0).approvalTemplatePendingCount()); assertEquals(2, summary.entries().get(0).approvalTemplateNotApplicableCount()); + assertEquals(1, summary.entries().get(0).constantFoldingPreviewPassCount()); + assertEquals(3, summary.entries().get(0).constantFoldingPreviewCandidateCount()); + assertEquals(5, summary.entries().get(0).constantFoldingPreviewSkippedCount()); + assertTrue(summary.entries().get(0).constantFoldingPreviewRuntimeEquivalenceRequiredBeforeRewrite()); + assertTrue(summary.entries().get(0).constantFoldingPreviewApprovalRequiredBeforeRewrite()); + assertEquals(1, summary.entries().get(0).safeLocalCsePreviewPassCount()); + assertEquals(6, summary.entries().get(0).safeLocalCsePreviewExpressionCount()); + assertEquals(4, summary.entries().get(0).safeLocalCsePreviewCandidateExpressionCount()); + assertEquals(2, summary.entries().get(0).safeLocalCsePreviewDuplicateExpressionCount()); + assertEquals(6, summary.entries().get(0).safeLocalCsePreviewBlockedCount()); + assertTrue(summary.entries().get(0).safeLocalCsePreviewRuntimeEquivalenceRequiredBeforeRewrite()); + assertTrue(summary.entries().get(0).safeLocalCsePreviewApprovalRequiredBeforeRewrite()); + assertEquals(1, summary.entries().get(0).typedDeadCodePreviewPassCount()); + assertEquals(8, summary.entries().get(0).typedDeadCodePreviewNodeCount()); + assertEquals(5, summary.entries().get(0).typedDeadCodePreviewReachableNodeCount()); + assertEquals(3, summary.entries().get(0).typedDeadCodePreviewUnreachableNodeCount()); + assertEquals(4, summary.entries().get(0).typedDeadCodePreviewBlockedCount()); + assertTrue(summary.entries().get(0).typedDeadCodePreviewRuntimeEquivalenceRequiredBeforeRewrite()); + assertTrue(summary.entries().get(0).typedDeadCodePreviewApprovalRequiredBeforeRewrite()); assertEquals("missing", summary.entries().get(1).status()); assertEquals(3, summary.totalPassCount()); assertEquals(2, summary.totalProposalOnlyCount()); @@ -66,6 +118,16 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { assertEquals(1, summary.totalRolledBackCount()); assertEquals(1, summary.totalApprovalTemplatePendingCount()); assertEquals(2, summary.totalApprovalTemplateNotApplicableCount()); + assertEquals(1, summary.totalConstantFoldingPreviewPassCount()); + assertEquals(3, summary.totalConstantFoldingPreviewCandidateCount()); + assertEquals(5, summary.totalConstantFoldingPreviewSkippedCount()); + assertEquals(1, summary.totalSafeLocalCsePreviewPassCount()); + assertEquals(4, summary.totalSafeLocalCsePreviewCandidateExpressionCount()); + assertEquals(2, summary.totalSafeLocalCsePreviewDuplicateExpressionCount()); + assertEquals(6, summary.totalSafeLocalCsePreviewBlockedCount()); + assertEquals(1, summary.totalTypedDeadCodePreviewPassCount()); + assertEquals(3, summary.totalTypedDeadCodePreviewUnreachableNodeCount()); + assertEquals(4, summary.totalTypedDeadCodePreviewBlockedCount()); assertEquals( "ir-optimizer:no-op:1=1, ir-optimizer:text-canonicalization:1=2", summary.providerSummary() @@ -74,8 +136,18 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { assertTrue(summary.toMarkdown().contains("- Proposal-only count: `2`")); assertTrue(summary.toMarkdown().contains("- Approval templates pending: `1`")); assertTrue(summary.toMarkdown().contains("- Approval templates not applicable: `2`")); + assertTrue(summary.toMarkdown().contains("- Constant folding preview passes: `1`")); + assertTrue(summary.toMarkdown().contains("- Constant folding preview candidates: `3`")); + assertTrue(summary.toMarkdown().contains("- Constant folding preview skipped blockers: `5`")); + assertTrue(summary.toMarkdown().contains("- Safe local CSE preview passes: `1`")); + assertTrue(summary.toMarkdown().contains("- Safe local CSE preview candidate expressions: `4`")); + assertTrue(summary.toMarkdown().contains("- Safe local CSE preview duplicate expressions: `2`")); + assertTrue(summary.toMarkdown().contains("- Safe local CSE preview blockers: `6`")); + assertTrue(summary.toMarkdown().contains("- Typed dead-code preview passes: `1`")); + assertTrue(summary.toMarkdown().contains("- Typed dead-code preview unreachable nodes: `3`")); + assertTrue(summary.toMarkdown().contains("- Typed dead-code preview blockers: `4`")); assertTrue(summary.toMarkdown().contains("- Providers: `ir-optimizer:no-op:1=1, ir-optimizer:text-canonicalization:1=2`")); - assertTrue(summary.toMarkdown().contains("| `kernel-a.cl` | `recorded` | `3` | `2` | `1` | `1` | `1` | `2` | `ir-optimizer:no-op:1=1, ir-optimizer:text-canonicalization:1=2` |")); - assertTrue(summary.toMarkdown().contains("| `kernel-b.cl` | `missing` | `0` | `0` | `0` | `0` | `0` | `0` | `none` |")); + assertTrue(summary.toMarkdown().contains("| `kernel-a.cl` | `recorded` | `3` | `2` | `1` | `1` | `1` | `2` | `3` | `5` | `4` | `2` | `6` | `3` | `4` | `ir-optimizer:no-op:1=1, ir-optimizer:text-canonicalization:1=2` |")); + assertTrue(summary.toMarkdown().contains("| `kernel-b.cl` | `missing` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `none` |")); } } diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java index f50ef190..027a96e4 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java @@ -1380,6 +1380,39 @@ void validationReporterWritesKernelLaunchAdvisoryReportAndWorkflowFragment() thr "proposalOnly.count=1", "selectedOptimized.count=0", "rolledBack.count=0", + "constantFoldingPreview.pass.count=1", + "constantFoldingPreview.candidate.count=2", + "constantFoldingPreview.skipped.nonPlainLiteral.count=1", + "constantFoldingPreview.skipped.divideByZero.count=1", + "constantFoldingPreview.skipped.nonEvenDivision.count=0", + "constantFoldingPreview.skipped.unsupportedOperator.count=0", + "constantFoldingPreview.skipped.nonLiteralOperand.count=0", + "constantFoldingPreview.runtimeEquivalenceRequiredBeforeRewrite=true", + "constantFoldingPreview.approvalRequiredBeforeRewrite=true", + "constantFoldingPreview.integerOverflowProven=false", + "constantFoldingPreview.floatingPointRoundingProven=false", + "safeLocalCsePreview.pass.count=1", + "safeLocalCsePreview.expression.count=5", + "safeLocalCsePreview.candidateExpression.count=3", + "safeLocalCsePreview.duplicateExpression.count=1", + "safeLocalCsePreview.equivalenceClass.count=1", + "safeLocalCsePreview.blocked.unsupportedOperator.count=1", + "safeLocalCsePreview.blocked.impureOperand.count=0", + "safeLocalCsePreview.blocked.controlFlowBoundary.count=1", + "safeLocalCsePreview.runtimeEquivalenceRequiredBeforeRewrite=true", + "safeLocalCsePreview.approvalRequiredBeforeRewrite=true", + "safeLocalCsePreview.dominanceProven=false", + "safeLocalCsePreview.sideEffectFreedomProven=false", + "typedDeadCodePreview.pass.count=1", + "typedDeadCodePreview.node.count=7", + "typedDeadCodePreview.reachableNode.count=5", + "typedDeadCodePreview.unreachableNode.count=2", + "typedDeadCodePreview.blocked.missingRoot.count=0", + "typedDeadCodePreview.blocked.missingChildReference.count=1", + "typedDeadCodePreview.blocked.sideEffectingUnreachableNode.count=1", + "typedDeadCodePreview.runtimeEquivalenceRequiredBeforeRewrite=true", + "typedDeadCodePreview.approvalRequiredBeforeRewrite=true", + "typedDeadCodePreview.sideEffectFreedomProven=false", "pass.0.passVersion=ir-optimizer:text-canonicalization:1", "" ) @@ -1430,6 +1463,17 @@ void validationReporterWritesKernelLaunchAdvisoryReportAndWorkflowFragment() thr assertTrue(reportMarkdown.contains("`workload/perlin.cl` | `non-preferred-multiple`")); assertTrue(reportMarkdown.contains("## Runtime IR Optimizer Evidence")); assertTrue(reportMarkdown.contains("- Proposal-only count: `1`")); + assertTrue(reportMarkdown.contains("- Constant folding preview passes: `1`")); + assertTrue(reportMarkdown.contains("- Constant folding preview candidates: `2`")); + assertTrue(reportMarkdown.contains("- Constant folding preview skipped blockers: `2`")); + assertTrue(reportMarkdown.contains("- Safe local CSE preview passes: `1`")); + assertTrue(reportMarkdown.contains("- Safe local CSE preview candidate expressions: `3`")); + assertTrue(reportMarkdown.contains("- Safe local CSE preview duplicate expressions: `1`")); + assertTrue(reportMarkdown.contains("- Safe local CSE preview blockers: `2`")); + assertTrue(reportMarkdown.contains("- Typed dead-code preview passes: `1`")); + assertTrue(reportMarkdown.contains("- Typed dead-code preview unreachable nodes: `2`")); + assertTrue(reportMarkdown.contains("- Typed dead-code preview blockers: `2`")); + assertTrue(reportMarkdown.contains("| `workload/perlin.cl` | `recorded` | `1` | `1` | `0` | `0` | `0` | `0` | `2` | `2` | `3` | `1` | `2` | `2` | `2` | `ir-optimizer:text-canonicalization:1=1` |")); assertTrue(reportMarkdown.contains("ir-optimizer:text-canonicalization:1=1")); assertTrue(summaryMarkdown.contains("## Kernel Launch Advisories")); assertTrue(summaryMarkdown.contains("- Blocking: `0`")); From a337a3bec6fdf69eab01ca804065bbab334143e8 Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Sun, 12 Jul 2026 14:01:14 +0300 Subject: [PATCH 16/32] wip --- ROADMAP.md | 1 + docs/IR-Optimizer.md | 2 +- docs/Runtime-Guide.md | 2 +- .../GpuRuntimeCompileArtifactDumper.java | 161 ++++++++++++++++++ ...enClRuntimeIrOptimizerEvidenceSummary.java | 154 +++++++++++++++++ .../GpuRuntimeCompileArtifactDumperTest.java | 5 + ...RuntimeIrOptimizerEvidenceSummaryTest.java | 63 +++++++ .../opencl/OpenClValidationReportTest.java | 5 + 8 files changed, 391 insertions(+), 2 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 4ef51895..b7dae0af 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -681,6 +681,7 @@ Current rule: `ir-validation` remains strictly read-only. Optimizer modules may OpenCL runtime validation now aggregates that preview evidence into `safeLocalCsePreview.*` fields in `runtime-ir-optimizer-evidence.properties` and surfaces pass, candidate-expression, duplicate-expression, and blocker counts in the `Runtime IR Optimizer Evidence` report section. `GpuIrTypedDeadCodePreviewProposalProvider` now adds the first typed dead-code proof surface by walking typed-body roots and recording node, reachable-node, unreachable-node, unreachable-kind, blocker, policy, proof, and safety evidence without proposing a rewrite. It explicitly records missing-root, missing-child-reference, and side-effecting-unreachable-node blockers plus required runtime-equivalence/approval gates and unresolved side-effect-freedom proof flags, so dead-code cleanup readiness can be measured without enabling optimized IR selection. OpenCL runtime validation now aggregates that preview evidence into `typedDeadCodePreview.*` fields in `runtime-ir-optimizer-evidence.properties` and surfaces pass, unreachable-node, and blocker counts in the `Runtime IR Optimizer Evidence` report section. + The same runtime evidence now includes `previewReadiness.*` aggregate fields over constant folding, safe-local CSE, and typed dead-code cleanup. The aggregate records overall status, recorded/candidate/blocked family counts, and a per-family status summary (`not-recorded`, `no-candidates`, `candidates-recorded`, `blocked-by-proof`, or `ready-for-runtime-equivalence-review`) so CI can track optimizer-family readiness without enabling production mutation or selected-IR replacement. - [x] Keep vendor optimization as a separate optional provider layer. `ir-vendor-optimizer` now exists as a separate optional module skeleton. It contributes vendor proposals only through `GpuIrVendorOptimizationProposalProvider` and `GpuIrVendorOptimizationProposalRegistry`, while the default runtime bridge keeps loading only backend-neutral proposal providers. Future vendor modules may consume target device profile, backend compiler feedback, resource metrics, driver quirks, launch advisories, and promotion evidence, but missing modules, missing matching device evidence, or disabled vendor adaptation must leave backend-neutral IR selection unchanged. - [x] Gate production optimizer selection through approval-bound artifacts. diff --git a/docs/IR-Optimizer.md b/docs/IR-Optimizer.md index 1bc53819..2f2324c8 100644 --- a/docs/IR-Optimizer.md +++ b/docs/IR-Optimizer.md @@ -29,7 +29,7 @@ The preview records blockers such as divide-by-zero, non-even division, unsuppor `GpuIrTypedDeadCodePreviewProposalProvider` is a third preview-only proof surface for future typed dead-code cleanup. It walks typed-body roots, records reachable versus unreachable node counts, unreachable-kind breakdown, missing root / missing child-reference blockers, and side-effecting unreachable-node blockers. It always returns `NO_CHANGE`, marks the proof as preview-only, and records that runtime equivalence, approval, and side-effect freedom must be proven before any future cleanup rewrite can graduate. -OpenCL validation aggregates these proof fields into `constantFoldingPreview.*`, `safeLocalCsePreview.*`, and `typedDeadCodePreview.*` summary fields inside `runtime-ir-optimizer-evidence.properties`. The generated `Runtime IR Optimizer Evidence` Markdown section shows constant-folding preview pass/candidate/blocker counts, safe-local-CSE pass/candidate-expression/duplicate-expression/blocker counts, and typed dead-code pass/unreachable-node/blocker counts. These fields are report-only and do not participate in source switching, selected-IR replacement, or production mutation gates. +OpenCL validation aggregates these proof fields into `constantFoldingPreview.*`, `safeLocalCsePreview.*`, and `typedDeadCodePreview.*` summary fields inside `runtime-ir-optimizer-evidence.properties`. The same artifact also records `previewReadiness.*` fields that summarize preview-family status across constant folding, safe-local CSE, and typed dead-code cleanup as `not-recorded`, `no-candidates`, `candidates-recorded`, `blocked-by-proof`, or `ready-for-runtime-equivalence-review`. The generated `Runtime IR Optimizer Evidence` Markdown section shows constant-folding preview pass/candidate/blocker counts, safe-local-CSE pass/candidate-expression/duplicate-expression/blocker counts, typed dead-code pass/unreachable-node/blocker counts, and the aggregate preview-readiness status. These fields are report-only and do not participate in source switching, selected-IR replacement, or production mutation gates. ## Immutable Proposal Flow diff --git a/docs/Runtime-Guide.md b/docs/Runtime-Guide.md index 006541e4..3f154567 100644 --- a/docs/Runtime-Guide.md +++ b/docs/Runtime-Guide.md @@ -233,7 +233,7 @@ OpenCL isolated runtime-equivalence checks write raw pipeline comparison cases i Non-analysis optimizer passes with runtime-equivalence payload evidence also produce `runtime-optimizer-family-equivalence-payload.properties` plus durable files under `runtime-optimizer-family-equivalence-payload/family--/pass-/`. Each pass directory contains `manifest.properties`, CPU/reference, pre-optimization, post-optimization, tolerance, failure-fixture, and diagnostics properties files. When proof fields contain structured case evidence, the component files include deterministic indexed fields for case names, inputs, raw outputs, tolerances, equivalence flags, and diagnostics. Missing components are materialized with `status=not-recorded`; they are never inferred from another pass. Artifact paths are normalized and must remain inside the kernel's `runtime-compile-artifacts` directory. These files are diagnostic evidence only and cannot enable source switching or production mutation. -When the optional IR optimizer bridge participates, each kernel directory may also receive `runtime-ir-optimizer-evidence.properties`. The artifact records only proposal/provider evidence from stable `javatogpu.ir-optimizer` sources: pass counts, proposal-only versus selected optimized counts, rollback counts, IR identities, proof fields, approval-template applicability, and diagnostics. Constant-folding preview proof is also aggregated into `constantFoldingPreview.*` fields for pass count, candidate count, skipped blockers, required runtime-equivalence/approval gates, and unresolved integer-overflow / floating-point-rounding safety flags. Safe-local-CSE preview proof is aggregated into `safeLocalCsePreview.*` fields for pass count, expression/candidate/duplicate/equivalence-class counts, unsupported-operator / impure-operand / control-flow-boundary blockers, required runtime-equivalence/approval gates, and unresolved dominance / side-effect-freedom proof flags. Typed dead-code preview proof is aggregated into `typedDeadCodePreview.*` fields for pass count, typed/reachable/unreachable node counts, missing-root / missing-child-reference / side-effecting-unreachable blockers, required runtime-equivalence/approval gates, and unresolved side-effect-freedom proof flags. `openClValidationReport` aggregates this into `Runtime IR Optimizer Evidence` for CI visibility, including constant-folding, safe-local-CSE, and typed dead-code preview candidate and blocker totals. The section is evidence-only and does not participate in history, source switching, selected-IR replacement, or production mutation gates. +When the optional IR optimizer bridge participates, each kernel directory may also receive `runtime-ir-optimizer-evidence.properties`. The artifact records only proposal/provider evidence from stable `javatogpu.ir-optimizer` sources: pass counts, proposal-only versus selected optimized counts, rollback counts, IR identities, proof fields, approval-template applicability, and diagnostics. Constant-folding preview proof is also aggregated into `constantFoldingPreview.*` fields for pass count, candidate count, skipped blockers, required runtime-equivalence/approval gates, and unresolved integer-overflow / floating-point-rounding safety flags. Safe-local-CSE preview proof is aggregated into `safeLocalCsePreview.*` fields for pass count, expression/candidate/duplicate/equivalence-class counts, unsupported-operator / impure-operand / control-flow-boundary blockers, required runtime-equivalence/approval gates, and unresolved dominance / side-effect-freedom proof flags. Typed dead-code preview proof is aggregated into `typedDeadCodePreview.*` fields for pass count, typed/reachable/unreachable node counts, missing-root / missing-child-reference / side-effecting-unreachable blockers, required runtime-equivalence/approval gates, and unresolved side-effect-freedom proof flags. The artifact also records `previewReadiness.*` fields with aggregate status, recorded/candidate/blocked family counts, and a per-family status summary so CI can distinguish no-candidate previews from proof-blocked previews and review-ready runtime-equivalence candidates. `openClValidationReport` aggregates this into `Runtime IR Optimizer Evidence` for CI visibility, including constant-folding, safe-local-CSE, typed dead-code preview totals, and preview-readiness status. The section is evidence-only and does not participate in history, source switching, selected-IR replacement, or production mutation gates. The same index records `familyBinding.*`. Binding remains `not-bound` unless exactly one optimizer family has passed runtime evidence whose comparison mode is explicitly family-specific (`optimizer-family::...`). Descriptor-versus-reconstructed-source evidence is pipeline validation and is deliberately rejected as optimizer-family proof. diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java index 24f40e8e..68121952 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java @@ -793,6 +793,19 @@ private static String formatRuntimeIrOptimizerEvidence(GpuRuntimeIrOptimizationR .append(typedDeadCodePreview.approvalRequiredBeforeRewrite()).append('\n'); builder.append("typedDeadCodePreview.sideEffectFreedomProven=") .append(typedDeadCodePreview.sideEffectFreedomProven()).append('\n'); + PreviewReadinessEvidence previewReadiness = previewReadinessEvidence( + constantFoldingPreview, + safeLocalCsePreview, + typedDeadCodePreview + ); + builder.append("previewReadiness.status=").append(previewReadiness.status()).append('\n'); + builder.append("previewReadiness.family.count=").append(previewReadiness.familyCount()).append('\n'); + builder.append("previewReadiness.candidateFamily.count=") + .append(previewReadiness.candidateFamilyCount()).append('\n'); + builder.append("previewReadiness.blockedFamily.count=") + .append(previewReadiness.blockedFamilyCount()).append('\n'); + builder.append("previewReadiness.familySummary=") + .append(safePropertyValue(previewReadiness.familySummary())).append('\n'); for (int index = 0; index < irOptimizerReports.size(); index++) { GpuRuntimeIrOptimizationPassReport passReport = irOptimizerReports.get(index); String prefix = "pass." + index + "."; @@ -1029,6 +1042,90 @@ private static TypedDeadCodePreviewEvidence typedDeadCodePreviewEvidence( ); } + private static PreviewReadinessEvidence previewReadinessEvidence( + ConstantFoldingPreviewEvidence constantFoldingPreview, + SafeLocalCsePreviewEvidence safeLocalCsePreview, + TypedDeadCodePreviewEvidence typedDeadCodePreview + ) { + PreviewFamilyReadiness constantFolding = new PreviewFamilyReadiness( + "constant-folding", + previewFamilyStatus( + constantFoldingPreview.passCount(), + constantFoldingPreview.candidateCount(), + constantFoldingPreview.skippedCount() + constantFoldingPreview.proofBlockerCount() + ), + constantFoldingPreview.candidateCount(), + constantFoldingPreview.skippedCount() + constantFoldingPreview.proofBlockerCount() + ); + PreviewFamilyReadiness safeLocalCse = new PreviewFamilyReadiness( + "safe-local-cse", + previewFamilyStatus( + safeLocalCsePreview.passCount(), + safeLocalCsePreview.duplicateExpressionCount(), + safeLocalCsePreview.blockedCount() + safeLocalCsePreview.proofBlockerCount() + ), + safeLocalCsePreview.duplicateExpressionCount(), + safeLocalCsePreview.blockedCount() + safeLocalCsePreview.proofBlockerCount() + ); + PreviewFamilyReadiness typedDeadCode = new PreviewFamilyReadiness( + "typed-dead-code", + previewFamilyStatus( + typedDeadCodePreview.passCount(), + typedDeadCodePreview.unreachableNodeCount(), + typedDeadCodePreview.blockedCount() + typedDeadCodePreview.proofBlockerCount() + ), + typedDeadCodePreview.unreachableNodeCount(), + typedDeadCodePreview.blockedCount() + typedDeadCodePreview.proofBlockerCount() + ); + List families = List.of(constantFolding, safeLocalCse, typedDeadCode); + int familyCount = (int) families.stream() + .filter(family -> !"not-recorded".equals(family.status())) + .count(); + int candidateFamilyCount = (int) families.stream() + .filter(family -> family.candidateCount() > 0) + .count(); + int blockedFamilyCount = (int) families.stream() + .filter(family -> "blocked-by-proof".equals(family.status())) + .count(); + String status = previewReadinessStatus(families); + String familySummary = families.stream() + .map(family -> family.family() + "=" + family.status()) + .collect(java.util.stream.Collectors.joining(", ")); + return new PreviewReadinessEvidence(status, familyCount, candidateFamilyCount, blockedFamilyCount, familySummary); + } + + private static String previewReadinessStatus(List families) { + if (families.stream().allMatch(family -> "not-recorded".equals(family.status()))) { + return "not-recorded"; + } + if (families.stream().anyMatch(family -> "blocked-by-proof".equals(family.status()))) { + return "blocked-by-proof"; + } + if (families.stream().anyMatch(family -> "ready-for-runtime-equivalence-review".equals(family.status()))) { + return "ready-for-runtime-equivalence-review"; + } + if (families.stream().anyMatch(family -> "candidates-recorded".equals(family.status()))) { + return "candidates-recorded"; + } + return "no-candidates"; + } + + private static String previewFamilyStatus(int passCount, int candidateCount, int blockerCount) { + if (passCount <= 0) { + return "not-recorded"; + } + if (candidateCount <= 0 && blockerCount <= 0) { + return "no-candidates"; + } + if (blockerCount > 0) { + return "blocked-by-proof"; + } + if (candidateCount > 0) { + return "ready-for-runtime-equivalence-review"; + } + return "candidates-recorded"; + } + private static int parseNonNegativeInt(String value) { try { return Math.max(0, Integer.parseInt(value)); @@ -1057,6 +1154,27 @@ private record ConstantFoldingPreviewEvidence( boolean integerOverflowProven, boolean floatingPointRoundingProven ) { + private int skippedCount() { + return skippedNonPlainLiteralCount + + skippedDivideByZeroCount + + skippedNonEvenDivisionCount + + skippedUnsupportedOperatorCount + + skippedNonLiteralOperandCount; + } + + private int proofBlockerCount() { + if (candidateCount <= 0) { + return 0; + } + int blockers = 0; + if (!integerOverflowProven) { + blockers++; + } + if (!floatingPointRoundingProven) { + blockers++; + } + return blockers; + } } private record SafeLocalCsePreviewEvidence( @@ -1073,6 +1191,25 @@ private record SafeLocalCsePreviewEvidence( boolean dominanceProven, boolean sideEffectFreedomProven ) { + private int blockedCount() { + return blockedUnsupportedOperatorCount + + blockedImpureOperandCount + + blockedControlFlowBoundaryCount; + } + + private int proofBlockerCount() { + if (duplicateExpressionCount <= 0) { + return 0; + } + int blockers = 0; + if (!dominanceProven) { + blockers++; + } + if (!sideEffectFreedomProven) { + blockers++; + } + return blockers; + } } private record TypedDeadCodePreviewEvidence( @@ -1086,6 +1223,30 @@ private record TypedDeadCodePreviewEvidence( boolean runtimeEquivalenceRequiredBeforeRewrite, boolean approvalRequiredBeforeRewrite, boolean sideEffectFreedomProven + ) { + private int blockedCount() { + return blockedMissingRootCount + + blockedMissingChildReferenceCount + + blockedSideEffectingUnreachableNodeCount; + } + + private int proofBlockerCount() { + if (unreachableNodeCount <= 0) { + return 0; + } + return sideEffectFreedomProven ? 0 : 1; + } + } + + private record PreviewFamilyReadiness(String family, String status, int candidateCount, int blockerCount) { + } + + private record PreviewReadinessEvidence( + String status, + int familyCount, + int candidateFamilyCount, + int blockedFamilyCount, + String familySummary ) { } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java index 721fb59e..4d4072bb 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java @@ -136,6 +136,115 @@ int totalTypedDeadCodePreviewBlockedCount() { return entries.stream().mapToInt(Entry::typedDeadCodePreviewBlockedCount).sum(); } + int totalPreviewReadinessFamilyCount() { + return (int) previewReadinessFamilies().stream() + .filter(family -> !"not-recorded".equals(family.status())) + .count(); + } + + int totalPreviewReadinessCandidateFamilyCount() { + return (int) previewReadinessFamilies().stream() + .filter(family -> family.candidateCount() > 0) + .count(); + } + + int totalPreviewReadinessBlockedFamilyCount() { + return (int) previewReadinessFamilies().stream() + .filter(family -> "blocked-by-proof".equals(family.status())) + .count(); + } + + String previewReadinessStatus() { + List families = previewReadinessFamilies(); + if (families.stream().allMatch(family -> "not-recorded".equals(family.status()))) { + return "not-recorded"; + } + if (families.stream().anyMatch(family -> "blocked-by-proof".equals(family.status()))) { + return "blocked-by-proof"; + } + if (families.stream().anyMatch(family -> "ready-for-runtime-equivalence-review".equals(family.status()))) { + return "ready-for-runtime-equivalence-review"; + } + if (families.stream().anyMatch(family -> "candidates-recorded".equals(family.status()))) { + return "candidates-recorded"; + } + return "no-candidates"; + } + + String previewReadinessFamilySummary() { + StringBuilder summary = new StringBuilder(); + for (PreviewFamilyReadiness family : previewReadinessFamilies()) { + if (!summary.isEmpty()) { + summary.append(", "); + } + summary.append(family.family()).append('=').append(family.status()); + } + return summary.toString(); + } + + private List previewReadinessFamilies() { + return List.of( + new PreviewFamilyReadiness( + "constant-folding", + previewFamilyStatus( + totalConstantFoldingPreviewPassCount(), + totalConstantFoldingPreviewCandidateCount(), + totalConstantFoldingPreviewSkippedCount() + totalConstantFoldingPreviewProofBlockerCount() + ), + totalConstantFoldingPreviewCandidateCount(), + totalConstantFoldingPreviewSkippedCount() + totalConstantFoldingPreviewProofBlockerCount() + ), + new PreviewFamilyReadiness( + "safe-local-cse", + previewFamilyStatus( + totalSafeLocalCsePreviewPassCount(), + totalSafeLocalCsePreviewDuplicateExpressionCount(), + totalSafeLocalCsePreviewBlockedCount() + totalSafeLocalCsePreviewProofBlockerCount() + ), + totalSafeLocalCsePreviewDuplicateExpressionCount(), + totalSafeLocalCsePreviewBlockedCount() + totalSafeLocalCsePreviewProofBlockerCount() + ), + new PreviewFamilyReadiness( + "typed-dead-code", + previewFamilyStatus( + totalTypedDeadCodePreviewPassCount(), + totalTypedDeadCodePreviewUnreachableNodeCount(), + totalTypedDeadCodePreviewBlockedCount() + totalTypedDeadCodePreviewProofBlockerCount() + ), + totalTypedDeadCodePreviewUnreachableNodeCount(), + totalTypedDeadCodePreviewBlockedCount() + totalTypedDeadCodePreviewProofBlockerCount() + ) + ); + } + + private int totalConstantFoldingPreviewProofBlockerCount() { + return entries.stream().mapToInt(Entry::constantFoldingPreviewProofBlockerCount).sum(); + } + + private int totalSafeLocalCsePreviewProofBlockerCount() { + return entries.stream().mapToInt(Entry::safeLocalCsePreviewProofBlockerCount).sum(); + } + + private int totalTypedDeadCodePreviewProofBlockerCount() { + return entries.stream().mapToInt(Entry::typedDeadCodePreviewProofBlockerCount).sum(); + } + + private static String previewFamilyStatus(int passCount, int candidateCount, int blockerCount) { + if (passCount <= 0) { + return "not-recorded"; + } + if (candidateCount <= 0 && blockerCount <= 0) { + return "no-candidates"; + } + if (blockerCount > 0) { + return "blocked-by-proof"; + } + if (candidateCount > 0) { + return "ready-for-runtime-equivalence-review"; + } + return "candidates-recorded"; + } + String providerSummary() { LinkedHashMap counts = new LinkedHashMap<>(); for (Entry entry : entries) { @@ -188,6 +297,13 @@ String toMarkdown() { .append(totalTypedDeadCodePreviewUnreachableNodeCount()).append("`\n"); markdown.append("- Typed dead-code preview blockers: `") .append(totalTypedDeadCodePreviewBlockedCount()).append("`\n"); + markdown.append("- Preview readiness status: `").append(previewReadinessStatus()).append("`\n"); + markdown.append("- Preview readiness families: `").append(inline(previewReadinessFamilySummary())).append("`\n"); + markdown.append("- Preview readiness recorded families: `").append(totalPreviewReadinessFamilyCount()).append("`\n"); + markdown.append("- Preview readiness candidate families: `") + .append(totalPreviewReadinessCandidateFamilyCount()).append("`\n"); + markdown.append("- Preview readiness blocked families: `") + .append(totalPreviewReadinessBlockedFamilyCount()).append("`\n"); markdown.append("- Providers: `").append(inline(providerSummary())).append("`\n\n"); if (entries.isEmpty()) { return markdown.toString(); @@ -317,6 +433,9 @@ private static String formatProviderCounts(Map counts) { return summary.toString(); } + private record PreviewFamilyReadiness(String family, String status, int candidateCount, int blockerCount) { + } + record Entry( String kernelResource, String status, @@ -416,6 +535,41 @@ int typedDeadCodePreviewBlockedCount() { + typedDeadCodePreviewBlockedSideEffectingUnreachableNodeCount; } + int constantFoldingPreviewProofBlockerCount() { + if (constantFoldingPreviewCandidateCount <= 0) { + return 0; + } + int blockers = 0; + if (!constantFoldingPreviewIntegerOverflowProven) { + blockers++; + } + if (!constantFoldingPreviewFloatingPointRoundingProven) { + blockers++; + } + return blockers; + } + + int safeLocalCsePreviewProofBlockerCount() { + if (safeLocalCsePreviewDuplicateExpressionCount <= 0) { + return 0; + } + int blockers = 0; + if (!safeLocalCsePreviewDominanceProven) { + blockers++; + } + if (!safeLocalCsePreviewSideEffectFreedomProven) { + blockers++; + } + return blockers; + } + + int typedDeadCodePreviewProofBlockerCount() { + if (typedDeadCodePreviewUnreachableNodeCount <= 0) { + return 0; + } + return typedDeadCodePreviewSideEffectFreedomProven ? 0 : 1; + } + static Entry from(String kernelResource, Properties properties) { if (properties == null || properties.isEmpty()) { return new Entry(kernelResource, "missing", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java index 13293cf3..020616b1 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java @@ -2186,6 +2186,11 @@ void dumpsRuntimeIrOptimizerEvidenceArtifactForProposalBridgeReports() { assertTrue(evidence.contains("typedDeadCodePreview.runtimeEquivalenceRequiredBeforeRewrite=true")); assertTrue(evidence.contains("typedDeadCodePreview.approvalRequiredBeforeRewrite=true")); assertTrue(evidence.contains("typedDeadCodePreview.sideEffectFreedomProven=false")); + assertTrue(evidence.contains("previewReadiness.status=blocked-by-proof")); + assertTrue(evidence.contains("previewReadiness.family.count=3")); + assertTrue(evidence.contains("previewReadiness.candidateFamily.count=3")); + assertTrue(evidence.contains("previewReadiness.blockedFamily.count=3")); + assertTrue(evidence.contains("previewReadiness.familySummary=constant-folding=blocked-by-proof, safe-local-cse=blocked-by-proof, typed-dead-code=blocked-by-proof")); assertTrue(evidence.contains("pass.0.passVersion=javatogpu.ir-optimizer.noop:1")); assertTrue(evidence.contains("pass.1.passVersion=javatogpu.ir-optimizer.text-canonicalization:1")); assertTrue(evidence.contains("pass.2.passVersion=javatogpu.ir-optimizer.constant-folding-preview:1")); diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java index 174c7ba4..d68fc888 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java @@ -128,6 +128,14 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { assertEquals(1, summary.totalTypedDeadCodePreviewPassCount()); assertEquals(3, summary.totalTypedDeadCodePreviewUnreachableNodeCount()); assertEquals(4, summary.totalTypedDeadCodePreviewBlockedCount()); + assertEquals(3, summary.totalPreviewReadinessFamilyCount()); + assertEquals(3, summary.totalPreviewReadinessCandidateFamilyCount()); + assertEquals(3, summary.totalPreviewReadinessBlockedFamilyCount()); + assertEquals("blocked-by-proof", summary.previewReadinessStatus()); + assertEquals( + "constant-folding=blocked-by-proof, safe-local-cse=blocked-by-proof, typed-dead-code=blocked-by-proof", + summary.previewReadinessFamilySummary() + ); assertEquals( "ir-optimizer:no-op:1=1, ir-optimizer:text-canonicalization:1=2", summary.providerSummary() @@ -146,8 +154,63 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { assertTrue(summary.toMarkdown().contains("- Typed dead-code preview passes: `1`")); assertTrue(summary.toMarkdown().contains("- Typed dead-code preview unreachable nodes: `3`")); assertTrue(summary.toMarkdown().contains("- Typed dead-code preview blockers: `4`")); + assertTrue(summary.toMarkdown().contains("- Preview readiness status: `blocked-by-proof`")); + assertTrue(summary.toMarkdown().contains("- Preview readiness families: `constant-folding=blocked-by-proof, safe-local-cse=blocked-by-proof, typed-dead-code=blocked-by-proof`")); + assertTrue(summary.toMarkdown().contains("- Preview readiness recorded families: `3`")); + assertTrue(summary.toMarkdown().contains("- Preview readiness candidate families: `3`")); + assertTrue(summary.toMarkdown().contains("- Preview readiness blocked families: `3`")); assertTrue(summary.toMarkdown().contains("- Providers: `ir-optimizer:no-op:1=1, ir-optimizer:text-canonicalization:1=2`")); assertTrue(summary.toMarkdown().contains("| `kernel-a.cl` | `recorded` | `3` | `2` | `1` | `1` | `1` | `2` | `3` | `5` | `4` | `2` | `6` | `3` | `4` | `ir-optimizer:no-op:1=1, ir-optimizer:text-canonicalization:1=2` |")); assertTrue(summary.toMarkdown().contains("| `kernel-b.cl` | `missing` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `none` |")); } + + @Test + void marksPreviewReadinessReadyWhenCandidatesHaveNoBlockers() throws Exception { + Path reportDirectory = temporaryDirectory.resolve("ready-reports").resolve("opencl"); + Files.createDirectories(reportDirectory); + Path workloadGate = reportDirectory.resolve("backend-source-promotion-workload-gate.properties"); + Files.writeString(workloadGate, """ + kernel.count=1 + kernel.0.sourceKernelResource=kernel-ready.cl + """); + Path artifactDirectory = reportDirectory + .resolve("runtime-compile-artifacts") + .resolve("kernel-ready"); + Files.createDirectories(artifactDirectory); + Files.writeString(artifactDirectory.resolve("backend-module.properties"), """ + resource=kernel-ready.cl + backendTarget=OPENCL + """); + Files.writeString(artifactDirectory.resolve( + GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT), """ + status=recorded + pass.count=3 + proposalOnly.count=3 + constantFoldingPreview.pass.count=1 + constantFoldingPreview.candidate.count=1 + constantFoldingPreview.integerOverflowProven=true + constantFoldingPreview.floatingPointRoundingProven=true + safeLocalCsePreview.pass.count=1 + safeLocalCsePreview.duplicateExpression.count=1 + safeLocalCsePreview.dominanceProven=true + safeLocalCsePreview.sideEffectFreedomProven=true + typedDeadCodePreview.pass.count=1 + typedDeadCodePreview.unreachableNode.count=1 + typedDeadCodePreview.sideEffectFreedomProven=true + pass.0.passVersion=ir-optimizer:constant-folding-preview:1 + pass.1.passVersion=ir-optimizer:safe-local-cse-preview:1 + pass.2.passVersion=ir-optimizer:typed-dead-code-preview:1 + """); + + OpenClRuntimeIrOptimizerEvidenceSummary summary = OpenClRuntimeIrOptimizerEvidenceSummary.read(workloadGate); + + assertEquals("ready-for-runtime-equivalence-review", summary.previewReadinessStatus()); + assertEquals(3, summary.totalPreviewReadinessFamilyCount()); + assertEquals(3, summary.totalPreviewReadinessCandidateFamilyCount()); + assertEquals(0, summary.totalPreviewReadinessBlockedFamilyCount()); + assertTrue(summary.toMarkdown().contains("- Preview readiness status: `ready-for-runtime-equivalence-review`")); + assertTrue(summary.toMarkdown().contains("constant-folding=ready-for-runtime-equivalence-review")); + assertTrue(summary.toMarkdown().contains("safe-local-cse=ready-for-runtime-equivalence-review")); + assertTrue(summary.toMarkdown().contains("typed-dead-code=ready-for-runtime-equivalence-review")); + } } diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java index 027a96e4..7137338c 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java @@ -1473,6 +1473,11 @@ void validationReporterWritesKernelLaunchAdvisoryReportAndWorkflowFragment() thr assertTrue(reportMarkdown.contains("- Typed dead-code preview passes: `1`")); assertTrue(reportMarkdown.contains("- Typed dead-code preview unreachable nodes: `2`")); assertTrue(reportMarkdown.contains("- Typed dead-code preview blockers: `2`")); + assertTrue(reportMarkdown.contains("- Preview readiness status: `blocked-by-proof`")); + assertTrue(reportMarkdown.contains("- Preview readiness families: `constant-folding=blocked-by-proof, safe-local-cse=blocked-by-proof, typed-dead-code=blocked-by-proof`")); + assertTrue(reportMarkdown.contains("- Preview readiness recorded families: `3`")); + assertTrue(reportMarkdown.contains("- Preview readiness candidate families: `3`")); + assertTrue(reportMarkdown.contains("- Preview readiness blocked families: `3`")); assertTrue(reportMarkdown.contains("| `workload/perlin.cl` | `recorded` | `1` | `1` | `0` | `0` | `0` | `0` | `2` | `2` | `3` | `1` | `2` | `2` | `2` | `ir-optimizer:text-canonicalization:1=1` |")); assertTrue(reportMarkdown.contains("ir-optimizer:text-canonicalization:1=1")); assertTrue(summaryMarkdown.contains("## Kernel Launch Advisories")); From 42579872708ee38174e3eaa94a381f5ceee98a26 Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Sun, 12 Jul 2026 14:10:59 +0300 Subject: [PATCH 17/32] wip --- ROADMAP.md | 1 + docs/IR-Optimizer.md | 2 +- docs/Runtime-Guide.md | 2 +- .../GpuRuntimeCompileArtifactDumper.java | 43 +++++++++++++++++++ ...enClRuntimeIrOptimizerEvidenceSummary.java | 30 +++++++++++++ .../GpuRuntimeCompileArtifactDumperTest.java | 8 ++++ ...RuntimeIrOptimizerEvidenceSummaryTest.java | 16 +++++++ .../opencl/OpenClValidationReportTest.java | 6 +++ 8 files changed, 106 insertions(+), 2 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index b7dae0af..926d3bbf 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -682,6 +682,7 @@ Current rule: `ir-validation` remains strictly read-only. Optimizer modules may `GpuIrTypedDeadCodePreviewProposalProvider` now adds the first typed dead-code proof surface by walking typed-body roots and recording node, reachable-node, unreachable-node, unreachable-kind, blocker, policy, proof, and safety evidence without proposing a rewrite. It explicitly records missing-root, missing-child-reference, and side-effecting-unreachable-node blockers plus required runtime-equivalence/approval gates and unresolved side-effect-freedom proof flags, so dead-code cleanup readiness can be measured without enabling optimized IR selection. OpenCL runtime validation now aggregates that preview evidence into `typedDeadCodePreview.*` fields in `runtime-ir-optimizer-evidence.properties` and surfaces pass, unreachable-node, and blocker counts in the `Runtime IR Optimizer Evidence` report section. The same runtime evidence now includes `previewReadiness.*` aggregate fields over constant folding, safe-local CSE, and typed dead-code cleanup. The aggregate records overall status, recorded/candidate/blocked family counts, and a per-family status summary (`not-recorded`, `no-candidates`, `candidates-recorded`, `blocked-by-proof`, or `ready-for-runtime-equivalence-review`) so CI can track optimizer-family readiness without enabling production mutation or selected-IR replacement. + `runtimeEquivalenceReview.*` now sits above `previewReadiness.*` as a fail-closed review gate. It records whether preview families are eligible for runtime-equivalence review, whether review evidence is required, the first blocker, the family summary, and explicitly disabled production mutation / selected-IR replacement so review readiness cannot be confused with production optimization enablement. - [x] Keep vendor optimization as a separate optional provider layer. `ir-vendor-optimizer` now exists as a separate optional module skeleton. It contributes vendor proposals only through `GpuIrVendorOptimizationProposalProvider` and `GpuIrVendorOptimizationProposalRegistry`, while the default runtime bridge keeps loading only backend-neutral proposal providers. Future vendor modules may consume target device profile, backend compiler feedback, resource metrics, driver quirks, launch advisories, and promotion evidence, but missing modules, missing matching device evidence, or disabled vendor adaptation must leave backend-neutral IR selection unchanged. - [x] Gate production optimizer selection through approval-bound artifacts. diff --git a/docs/IR-Optimizer.md b/docs/IR-Optimizer.md index 2f2324c8..18879557 100644 --- a/docs/IR-Optimizer.md +++ b/docs/IR-Optimizer.md @@ -29,7 +29,7 @@ The preview records blockers such as divide-by-zero, non-even division, unsuppor `GpuIrTypedDeadCodePreviewProposalProvider` is a third preview-only proof surface for future typed dead-code cleanup. It walks typed-body roots, records reachable versus unreachable node counts, unreachable-kind breakdown, missing root / missing child-reference blockers, and side-effecting unreachable-node blockers. It always returns `NO_CHANGE`, marks the proof as preview-only, and records that runtime equivalence, approval, and side-effect freedom must be proven before any future cleanup rewrite can graduate. -OpenCL validation aggregates these proof fields into `constantFoldingPreview.*`, `safeLocalCsePreview.*`, and `typedDeadCodePreview.*` summary fields inside `runtime-ir-optimizer-evidence.properties`. The same artifact also records `previewReadiness.*` fields that summarize preview-family status across constant folding, safe-local CSE, and typed dead-code cleanup as `not-recorded`, `no-candidates`, `candidates-recorded`, `blocked-by-proof`, or `ready-for-runtime-equivalence-review`. The generated `Runtime IR Optimizer Evidence` Markdown section shows constant-folding preview pass/candidate/blocker counts, safe-local-CSE pass/candidate-expression/duplicate-expression/blocker counts, typed dead-code pass/unreachable-node/blocker counts, and the aggregate preview-readiness status. These fields are report-only and do not participate in source switching, selected-IR replacement, or production mutation gates. +OpenCL validation aggregates these proof fields into `constantFoldingPreview.*`, `safeLocalCsePreview.*`, and `typedDeadCodePreview.*` summary fields inside `runtime-ir-optimizer-evidence.properties`. The same artifact also records `previewReadiness.*` fields that summarize preview-family status across constant folding, safe-local CSE, and typed dead-code cleanup as `not-recorded`, `no-candidates`, `candidates-recorded`, `blocked-by-proof`, or `ready-for-runtime-equivalence-review`. `runtimeEquivalenceReview.*` then exposes a fail-closed review gate over that aggregate: it can mark a preview family set as `review-ready`, but production mutation and selected-IR replacement remain disabled/manual-review-only. The generated `Runtime IR Optimizer Evidence` Markdown section shows constant-folding preview pass/candidate/blocker counts, safe-local-CSE pass/candidate-expression/duplicate-expression/blocker counts, typed dead-code pass/unreachable-node/blocker counts, aggregate preview-readiness status, and runtime-equivalence review eligibility. These fields are report-only and do not participate in source switching, selected-IR replacement, or production mutation gates. ## Immutable Proposal Flow diff --git a/docs/Runtime-Guide.md b/docs/Runtime-Guide.md index 3f154567..099abfad 100644 --- a/docs/Runtime-Guide.md +++ b/docs/Runtime-Guide.md @@ -233,7 +233,7 @@ OpenCL isolated runtime-equivalence checks write raw pipeline comparison cases i Non-analysis optimizer passes with runtime-equivalence payload evidence also produce `runtime-optimizer-family-equivalence-payload.properties` plus durable files under `runtime-optimizer-family-equivalence-payload/family--/pass-/`. Each pass directory contains `manifest.properties`, CPU/reference, pre-optimization, post-optimization, tolerance, failure-fixture, and diagnostics properties files. When proof fields contain structured case evidence, the component files include deterministic indexed fields for case names, inputs, raw outputs, tolerances, equivalence flags, and diagnostics. Missing components are materialized with `status=not-recorded`; they are never inferred from another pass. Artifact paths are normalized and must remain inside the kernel's `runtime-compile-artifacts` directory. These files are diagnostic evidence only and cannot enable source switching or production mutation. -When the optional IR optimizer bridge participates, each kernel directory may also receive `runtime-ir-optimizer-evidence.properties`. The artifact records only proposal/provider evidence from stable `javatogpu.ir-optimizer` sources: pass counts, proposal-only versus selected optimized counts, rollback counts, IR identities, proof fields, approval-template applicability, and diagnostics. Constant-folding preview proof is also aggregated into `constantFoldingPreview.*` fields for pass count, candidate count, skipped blockers, required runtime-equivalence/approval gates, and unresolved integer-overflow / floating-point-rounding safety flags. Safe-local-CSE preview proof is aggregated into `safeLocalCsePreview.*` fields for pass count, expression/candidate/duplicate/equivalence-class counts, unsupported-operator / impure-operand / control-flow-boundary blockers, required runtime-equivalence/approval gates, and unresolved dominance / side-effect-freedom proof flags. Typed dead-code preview proof is aggregated into `typedDeadCodePreview.*` fields for pass count, typed/reachable/unreachable node counts, missing-root / missing-child-reference / side-effecting-unreachable blockers, required runtime-equivalence/approval gates, and unresolved side-effect-freedom proof flags. The artifact also records `previewReadiness.*` fields with aggregate status, recorded/candidate/blocked family counts, and a per-family status summary so CI can distinguish no-candidate previews from proof-blocked previews and review-ready runtime-equivalence candidates. `openClValidationReport` aggregates this into `Runtime IR Optimizer Evidence` for CI visibility, including constant-folding, safe-local-CSE, typed dead-code preview totals, and preview-readiness status. The section is evidence-only and does not participate in history, source switching, selected-IR replacement, or production mutation gates. +When the optional IR optimizer bridge participates, each kernel directory may also receive `runtime-ir-optimizer-evidence.properties`. The artifact records only proposal/provider evidence from stable `javatogpu.ir-optimizer` sources: pass counts, proposal-only versus selected optimized counts, rollback counts, IR identities, proof fields, approval-template applicability, and diagnostics. Constant-folding preview proof is also aggregated into `constantFoldingPreview.*` fields for pass count, candidate count, skipped blockers, required runtime-equivalence/approval gates, and unresolved integer-overflow / floating-point-rounding safety flags. Safe-local-CSE preview proof is aggregated into `safeLocalCsePreview.*` fields for pass count, expression/candidate/duplicate/equivalence-class counts, unsupported-operator / impure-operand / control-flow-boundary blockers, required runtime-equivalence/approval gates, and unresolved dominance / side-effect-freedom proof flags. Typed dead-code preview proof is aggregated into `typedDeadCodePreview.*` fields for pass count, typed/reachable/unreachable node counts, missing-root / missing-child-reference / side-effecting-unreachable blockers, required runtime-equivalence/approval gates, and unresolved side-effect-freedom proof flags. The artifact also records `previewReadiness.*` fields with aggregate status, recorded/candidate/blocked family counts, and a per-family status summary so CI can distinguish no-candidate previews from proof-blocked previews and review-ready runtime-equivalence candidates. `runtimeEquivalenceReview.*` records the next fail-closed review boundary: status, eligibility, requirement, first blocker, family summary, disabled production mutation, disabled selected-IR replacement, and manual-review-only state. `openClValidationReport` aggregates this into `Runtime IR Optimizer Evidence` for CI visibility, including constant-folding, safe-local-CSE, typed dead-code preview totals, preview-readiness status, and runtime-equivalence review eligibility. The section is evidence-only and does not participate in history, source switching, selected-IR replacement, or production mutation gates. The same index records `familyBinding.*`. Binding remains `not-bound` unless exactly one optimizer family has passed runtime evidence whose comparison mode is explicitly family-specific (`optimizer-family::...`). Descriptor-versus-reconstructed-source evidence is pipeline validation and is deliberately rejected as optimizer-family proof. diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java index 68121952..69026dfb 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java @@ -806,6 +806,17 @@ private static String formatRuntimeIrOptimizerEvidence(GpuRuntimeIrOptimizationR .append(previewReadiness.blockedFamilyCount()).append('\n'); builder.append("previewReadiness.familySummary=") .append(safePropertyValue(previewReadiness.familySummary())).append('\n'); + RuntimeEquivalenceReviewEvidence runtimeEquivalenceReview = runtimeEquivalenceReviewEvidence(previewReadiness); + builder.append("runtimeEquivalenceReview.status=").append(runtimeEquivalenceReview.status()).append('\n'); + builder.append("runtimeEquivalenceReview.eligible=").append(runtimeEquivalenceReview.eligible()).append('\n'); + builder.append("runtimeEquivalenceReview.required=").append(runtimeEquivalenceReview.required()).append('\n'); + builder.append("runtimeEquivalenceReview.firstBlocker=") + .append(safePropertyValue(runtimeEquivalenceReview.firstBlocker())).append('\n'); + builder.append("runtimeEquivalenceReview.familySummary=") + .append(safePropertyValue(runtimeEquivalenceReview.familySummary())).append('\n'); + builder.append("runtimeEquivalenceReview.productionMutation=disabled\n"); + builder.append("runtimeEquivalenceReview.selectedIrReplacement=disabled\n"); + builder.append("runtimeEquivalenceReview.manualReviewOnly=true\n"); for (int index = 0; index < irOptimizerReports.size(); index++) { GpuRuntimeIrOptimizationPassReport passReport = irOptimizerReports.get(index); String prefix = "pass." + index + "."; @@ -1094,6 +1105,29 @@ private static PreviewReadinessEvidence previewReadinessEvidence( return new PreviewReadinessEvidence(status, familyCount, candidateFamilyCount, blockedFamilyCount, familySummary); } + private static RuntimeEquivalenceReviewEvidence runtimeEquivalenceReviewEvidence( + PreviewReadinessEvidence previewReadiness + ) { + boolean required = previewReadiness.candidateFamilyCount() > 0; + boolean eligible = "ready-for-runtime-equivalence-review".equals(previewReadiness.status()); + String status = eligible ? "review-ready" : "blocked"; + String firstBlocker = switch (previewReadiness.status()) { + case "ready-for-runtime-equivalence-review" -> "none"; + case "not-recorded" -> "preview-readiness-not-recorded"; + case "no-candidates" -> "preview-readiness-no-candidates"; + case "blocked-by-proof" -> "preview-readiness-blocked-by-proof"; + case "candidates-recorded" -> "preview-readiness-candidates-not-proof-clean"; + default -> "preview-readiness-unknown"; + }; + return new RuntimeEquivalenceReviewEvidence( + status, + eligible, + required, + firstBlocker, + previewReadiness.familySummary() + ); + } + private static String previewReadinessStatus(List families) { if (families.stream().allMatch(family -> "not-recorded".equals(family.status()))) { return "not-recorded"; @@ -1250,6 +1284,15 @@ private record PreviewReadinessEvidence( ) { } + private record RuntimeEquivalenceReviewEvidence( + String status, + boolean eligible, + boolean required, + String firstBlocker, + String familySummary + ) { + } + private static ApprovalTemplateEvidence approvalTemplateEvidence(GpuRuntimeIrOptimizationPassReport passReport) { if (passReport == null) { return ApprovalTemplateEvidence.notApplicable("pass-report-missing"); diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java index 4d4072bb..da445976 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java @@ -182,6 +182,29 @@ String previewReadinessFamilySummary() { return summary.toString(); } + String runtimeEquivalenceReviewStatus() { + return runtimeEquivalenceReviewEligible() ? "review-ready" : "blocked"; + } + + boolean runtimeEquivalenceReviewEligible() { + return "ready-for-runtime-equivalence-review".equals(previewReadinessStatus()); + } + + boolean runtimeEquivalenceReviewRequired() { + return totalPreviewReadinessCandidateFamilyCount() > 0; + } + + String runtimeEquivalenceReviewFirstBlocker() { + return switch (previewReadinessStatus()) { + case "ready-for-runtime-equivalence-review" -> "none"; + case "not-recorded" -> "preview-readiness-not-recorded"; + case "no-candidates" -> "preview-readiness-no-candidates"; + case "blocked-by-proof" -> "preview-readiness-blocked-by-proof"; + case "candidates-recorded" -> "preview-readiness-candidates-not-proof-clean"; + default -> "preview-readiness-unknown"; + }; + } + private List previewReadinessFamilies() { return List.of( new PreviewFamilyReadiness( @@ -304,6 +327,13 @@ String toMarkdown() { .append(totalPreviewReadinessCandidateFamilyCount()).append("`\n"); markdown.append("- Preview readiness blocked families: `") .append(totalPreviewReadinessBlockedFamilyCount()).append("`\n"); + markdown.append("- Runtime-equivalence review status: `").append(runtimeEquivalenceReviewStatus()).append("`\n"); + markdown.append("- Runtime-equivalence review eligible: `").append(runtimeEquivalenceReviewEligible()).append("`\n"); + markdown.append("- Runtime-equivalence review required: `").append(runtimeEquivalenceReviewRequired()).append("`\n"); + markdown.append("- Runtime-equivalence review first blocker: `") + .append(inline(runtimeEquivalenceReviewFirstBlocker())).append("`\n"); + markdown.append("- Runtime-equivalence review production mutation: `disabled`\n"); + markdown.append("- Runtime-equivalence review selected IR replacement: `disabled`\n"); markdown.append("- Providers: `").append(inline(providerSummary())).append("`\n\n"); if (entries.isEmpty()) { return markdown.toString(); diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java index 020616b1..eaf0f170 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java @@ -2191,6 +2191,14 @@ void dumpsRuntimeIrOptimizerEvidenceArtifactForProposalBridgeReports() { assertTrue(evidence.contains("previewReadiness.candidateFamily.count=3")); assertTrue(evidence.contains("previewReadiness.blockedFamily.count=3")); assertTrue(evidence.contains("previewReadiness.familySummary=constant-folding=blocked-by-proof, safe-local-cse=blocked-by-proof, typed-dead-code=blocked-by-proof")); + assertTrue(evidence.contains("runtimeEquivalenceReview.status=blocked")); + assertTrue(evidence.contains("runtimeEquivalenceReview.eligible=false")); + assertTrue(evidence.contains("runtimeEquivalenceReview.required=true")); + assertTrue(evidence.contains("runtimeEquivalenceReview.firstBlocker=preview-readiness-blocked-by-proof")); + assertTrue(evidence.contains("runtimeEquivalenceReview.familySummary=constant-folding=blocked-by-proof, safe-local-cse=blocked-by-proof, typed-dead-code=blocked-by-proof")); + assertTrue(evidence.contains("runtimeEquivalenceReview.productionMutation=disabled")); + assertTrue(evidence.contains("runtimeEquivalenceReview.selectedIrReplacement=disabled")); + assertTrue(evidence.contains("runtimeEquivalenceReview.manualReviewOnly=true")); assertTrue(evidence.contains("pass.0.passVersion=javatogpu.ir-optimizer.noop:1")); assertTrue(evidence.contains("pass.1.passVersion=javatogpu.ir-optimizer.text-canonicalization:1")); assertTrue(evidence.contains("pass.2.passVersion=javatogpu.ir-optimizer.constant-folding-preview:1")); diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java index d68fc888..c112ad63 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java @@ -136,6 +136,9 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { "constant-folding=blocked-by-proof, safe-local-cse=blocked-by-proof, typed-dead-code=blocked-by-proof", summary.previewReadinessFamilySummary() ); + assertEquals("blocked", summary.runtimeEquivalenceReviewStatus()); + assertTrue(summary.runtimeEquivalenceReviewRequired()); + assertEquals("preview-readiness-blocked-by-proof", summary.runtimeEquivalenceReviewFirstBlocker()); assertEquals( "ir-optimizer:no-op:1=1, ir-optimizer:text-canonicalization:1=2", summary.providerSummary() @@ -159,6 +162,12 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { assertTrue(summary.toMarkdown().contains("- Preview readiness recorded families: `3`")); assertTrue(summary.toMarkdown().contains("- Preview readiness candidate families: `3`")); assertTrue(summary.toMarkdown().contains("- Preview readiness blocked families: `3`")); + assertTrue(summary.toMarkdown().contains("- Runtime-equivalence review status: `blocked`")); + assertTrue(summary.toMarkdown().contains("- Runtime-equivalence review eligible: `false`")); + assertTrue(summary.toMarkdown().contains("- Runtime-equivalence review required: `true`")); + assertTrue(summary.toMarkdown().contains("- Runtime-equivalence review first blocker: `preview-readiness-blocked-by-proof`")); + assertTrue(summary.toMarkdown().contains("- Runtime-equivalence review production mutation: `disabled`")); + assertTrue(summary.toMarkdown().contains("- Runtime-equivalence review selected IR replacement: `disabled`")); assertTrue(summary.toMarkdown().contains("- Providers: `ir-optimizer:no-op:1=1, ir-optimizer:text-canonicalization:1=2`")); assertTrue(summary.toMarkdown().contains("| `kernel-a.cl` | `recorded` | `3` | `2` | `1` | `1` | `1` | `2` | `3` | `5` | `4` | `2` | `6` | `3` | `4` | `ir-optimizer:no-op:1=1, ir-optimizer:text-canonicalization:1=2` |")); assertTrue(summary.toMarkdown().contains("| `kernel-b.cl` | `missing` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `none` |")); @@ -205,6 +214,10 @@ void marksPreviewReadinessReadyWhenCandidatesHaveNoBlockers() throws Exception { OpenClRuntimeIrOptimizerEvidenceSummary summary = OpenClRuntimeIrOptimizerEvidenceSummary.read(workloadGate); assertEquals("ready-for-runtime-equivalence-review", summary.previewReadinessStatus()); + assertEquals("review-ready", summary.runtimeEquivalenceReviewStatus()); + assertTrue(summary.runtimeEquivalenceReviewEligible()); + assertTrue(summary.runtimeEquivalenceReviewRequired()); + assertEquals("none", summary.runtimeEquivalenceReviewFirstBlocker()); assertEquals(3, summary.totalPreviewReadinessFamilyCount()); assertEquals(3, summary.totalPreviewReadinessCandidateFamilyCount()); assertEquals(0, summary.totalPreviewReadinessBlockedFamilyCount()); @@ -212,5 +225,8 @@ void marksPreviewReadinessReadyWhenCandidatesHaveNoBlockers() throws Exception { assertTrue(summary.toMarkdown().contains("constant-folding=ready-for-runtime-equivalence-review")); assertTrue(summary.toMarkdown().contains("safe-local-cse=ready-for-runtime-equivalence-review")); assertTrue(summary.toMarkdown().contains("typed-dead-code=ready-for-runtime-equivalence-review")); + assertTrue(summary.toMarkdown().contains("- Runtime-equivalence review status: `review-ready`")); + assertTrue(summary.toMarkdown().contains("- Runtime-equivalence review eligible: `true`")); + assertTrue(summary.toMarkdown().contains("- Runtime-equivalence review first blocker: `none`")); } } diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java index 7137338c..3b29c324 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java @@ -1478,6 +1478,12 @@ void validationReporterWritesKernelLaunchAdvisoryReportAndWorkflowFragment() thr assertTrue(reportMarkdown.contains("- Preview readiness recorded families: `3`")); assertTrue(reportMarkdown.contains("- Preview readiness candidate families: `3`")); assertTrue(reportMarkdown.contains("- Preview readiness blocked families: `3`")); + assertTrue(reportMarkdown.contains("- Runtime-equivalence review status: `blocked`")); + assertTrue(reportMarkdown.contains("- Runtime-equivalence review eligible: `false`")); + assertTrue(reportMarkdown.contains("- Runtime-equivalence review required: `true`")); + assertTrue(reportMarkdown.contains("- Runtime-equivalence review first blocker: `preview-readiness-blocked-by-proof`")); + assertTrue(reportMarkdown.contains("- Runtime-equivalence review production mutation: `disabled`")); + assertTrue(reportMarkdown.contains("- Runtime-equivalence review selected IR replacement: `disabled`")); assertTrue(reportMarkdown.contains("| `workload/perlin.cl` | `recorded` | `1` | `1` | `0` | `0` | `0` | `0` | `2` | `2` | `3` | `1` | `2` | `2` | `2` | `ir-optimizer:text-canonicalization:1=1` |")); assertTrue(reportMarkdown.contains("ir-optimizer:text-canonicalization:1=1")); assertTrue(summaryMarkdown.contains("## Kernel Launch Advisories")); From 9ad4b316a7654ddd760362172ab761cb392b4794 Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Sun, 12 Jul 2026 16:14:59 +0300 Subject: [PATCH 18/32] wip --- ROADMAP.md | 1 + docs/IR-Optimizer.md | 2 +- docs/Runtime-Guide.md | 2 +- .../GpuRuntimeCompileArtifactDumper.java | 71 +++++++++++++++++ ...enClRuntimeIrOptimizerEvidenceSummary.java | 79 ++++++++++++++++++- .../GpuRuntimeCompileArtifactDumperTest.java | 13 +++ ...RuntimeIrOptimizerEvidenceSummaryTest.java | 37 ++++++++- .../opencl/OpenClValidationReportTest.java | 8 +- 8 files changed, 205 insertions(+), 8 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 926d3bbf..2c24b118 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -683,6 +683,7 @@ Current rule: `ir-validation` remains strictly read-only. Optimizer modules may OpenCL runtime validation now aggregates that preview evidence into `typedDeadCodePreview.*` fields in `runtime-ir-optimizer-evidence.properties` and surfaces pass, unreachable-node, and blocker counts in the `Runtime IR Optimizer Evidence` report section. The same runtime evidence now includes `previewReadiness.*` aggregate fields over constant folding, safe-local CSE, and typed dead-code cleanup. The aggregate records overall status, recorded/candidate/blocked family counts, and a per-family status summary (`not-recorded`, `no-candidates`, `candidates-recorded`, `blocked-by-proof`, or `ready-for-runtime-equivalence-review`) so CI can track optimizer-family readiness without enabling production mutation or selected-IR replacement. `runtimeEquivalenceReview.*` now sits above `previewReadiness.*` as a fail-closed review gate. It records whether preview families are eligible for runtime-equivalence review, whether review evidence is required, the first blocker, the family summary, and explicitly disabled production mutation / selected-IR replacement so review readiness cannot be confused with production optimization enablement. + `reviewPackage.*` now sits above runtime-equivalence review as the manual-review bundle boundary. It records package status, requirement/completeness, first blocker, proposal-pass count, pending approval count, original/optimized IR and proof-summary requirements, and repeats manual-review-only plus disabled production mutation / selected-IR replacement guardrails so review packaging remains evidence-only. - [x] Keep vendor optimization as a separate optional provider layer. `ir-vendor-optimizer` now exists as a separate optional module skeleton. It contributes vendor proposals only through `GpuIrVendorOptimizationProposalProvider` and `GpuIrVendorOptimizationProposalRegistry`, while the default runtime bridge keeps loading only backend-neutral proposal providers. Future vendor modules may consume target device profile, backend compiler feedback, resource metrics, driver quirks, launch advisories, and promotion evidence, but missing modules, missing matching device evidence, or disabled vendor adaptation must leave backend-neutral IR selection unchanged. - [x] Gate production optimizer selection through approval-bound artifacts. diff --git a/docs/IR-Optimizer.md b/docs/IR-Optimizer.md index 18879557..0b0ee899 100644 --- a/docs/IR-Optimizer.md +++ b/docs/IR-Optimizer.md @@ -29,7 +29,7 @@ The preview records blockers such as divide-by-zero, non-even division, unsuppor `GpuIrTypedDeadCodePreviewProposalProvider` is a third preview-only proof surface for future typed dead-code cleanup. It walks typed-body roots, records reachable versus unreachable node counts, unreachable-kind breakdown, missing root / missing child-reference blockers, and side-effecting unreachable-node blockers. It always returns `NO_CHANGE`, marks the proof as preview-only, and records that runtime equivalence, approval, and side-effect freedom must be proven before any future cleanup rewrite can graduate. -OpenCL validation aggregates these proof fields into `constantFoldingPreview.*`, `safeLocalCsePreview.*`, and `typedDeadCodePreview.*` summary fields inside `runtime-ir-optimizer-evidence.properties`. The same artifact also records `previewReadiness.*` fields that summarize preview-family status across constant folding, safe-local CSE, and typed dead-code cleanup as `not-recorded`, `no-candidates`, `candidates-recorded`, `blocked-by-proof`, or `ready-for-runtime-equivalence-review`. `runtimeEquivalenceReview.*` then exposes a fail-closed review gate over that aggregate: it can mark a preview family set as `review-ready`, but production mutation and selected-IR replacement remain disabled/manual-review-only. The generated `Runtime IR Optimizer Evidence` Markdown section shows constant-folding preview pass/candidate/blocker counts, safe-local-CSE pass/candidate-expression/duplicate-expression/blocker counts, typed dead-code pass/unreachable-node/blocker counts, aggregate preview-readiness status, and runtime-equivalence review eligibility. These fields are report-only and do not participate in source switching, selected-IR replacement, or production mutation gates. +OpenCL validation aggregates these proof fields into `constantFoldingPreview.*`, `safeLocalCsePreview.*`, and `typedDeadCodePreview.*` summary fields inside `runtime-ir-optimizer-evidence.properties`. The same artifact also records `previewReadiness.*` fields that summarize preview-family status across constant folding, safe-local CSE, and typed dead-code cleanup as `not-recorded`, `no-candidates`, `candidates-recorded`, `blocked-by-proof`, or `ready-for-runtime-equivalence-review`. `runtimeEquivalenceReview.*` then exposes a fail-closed review gate over that aggregate: it can mark a preview family set as `review-ready`, but production mutation and selected-IR replacement remain disabled/manual-review-only. `reviewPackage.*` sits above that gate as the manual-review bundle boundary: it records whether a package is required, whether it is complete, the first blocker, proposal-pass and pending-approval counts, and repeats the disabled production mutation / selected-IR replacement guardrails. The generated `Runtime IR Optimizer Evidence` Markdown section shows constant-folding preview pass/candidate/blocker counts, safe-local-CSE pass/candidate-expression/duplicate-expression/blocker counts, typed dead-code pass/unreachable-node/blocker counts, aggregate preview-readiness status, runtime-equivalence review eligibility, and review-package status. These fields are report-only and do not participate in source switching, selected-IR replacement, or production mutation gates. ## Immutable Proposal Flow diff --git a/docs/Runtime-Guide.md b/docs/Runtime-Guide.md index 099abfad..d3660b5c 100644 --- a/docs/Runtime-Guide.md +++ b/docs/Runtime-Guide.md @@ -233,7 +233,7 @@ OpenCL isolated runtime-equivalence checks write raw pipeline comparison cases i Non-analysis optimizer passes with runtime-equivalence payload evidence also produce `runtime-optimizer-family-equivalence-payload.properties` plus durable files under `runtime-optimizer-family-equivalence-payload/family--/pass-/`. Each pass directory contains `manifest.properties`, CPU/reference, pre-optimization, post-optimization, tolerance, failure-fixture, and diagnostics properties files. When proof fields contain structured case evidence, the component files include deterministic indexed fields for case names, inputs, raw outputs, tolerances, equivalence flags, and diagnostics. Missing components are materialized with `status=not-recorded`; they are never inferred from another pass. Artifact paths are normalized and must remain inside the kernel's `runtime-compile-artifacts` directory. These files are diagnostic evidence only and cannot enable source switching or production mutation. -When the optional IR optimizer bridge participates, each kernel directory may also receive `runtime-ir-optimizer-evidence.properties`. The artifact records only proposal/provider evidence from stable `javatogpu.ir-optimizer` sources: pass counts, proposal-only versus selected optimized counts, rollback counts, IR identities, proof fields, approval-template applicability, and diagnostics. Constant-folding preview proof is also aggregated into `constantFoldingPreview.*` fields for pass count, candidate count, skipped blockers, required runtime-equivalence/approval gates, and unresolved integer-overflow / floating-point-rounding safety flags. Safe-local-CSE preview proof is aggregated into `safeLocalCsePreview.*` fields for pass count, expression/candidate/duplicate/equivalence-class counts, unsupported-operator / impure-operand / control-flow-boundary blockers, required runtime-equivalence/approval gates, and unresolved dominance / side-effect-freedom proof flags. Typed dead-code preview proof is aggregated into `typedDeadCodePreview.*` fields for pass count, typed/reachable/unreachable node counts, missing-root / missing-child-reference / side-effecting-unreachable blockers, required runtime-equivalence/approval gates, and unresolved side-effect-freedom proof flags. The artifact also records `previewReadiness.*` fields with aggregate status, recorded/candidate/blocked family counts, and a per-family status summary so CI can distinguish no-candidate previews from proof-blocked previews and review-ready runtime-equivalence candidates. `runtimeEquivalenceReview.*` records the next fail-closed review boundary: status, eligibility, requirement, first blocker, family summary, disabled production mutation, disabled selected-IR replacement, and manual-review-only state. `openClValidationReport` aggregates this into `Runtime IR Optimizer Evidence` for CI visibility, including constant-folding, safe-local-CSE, typed dead-code preview totals, preview-readiness status, and runtime-equivalence review eligibility. The section is evidence-only and does not participate in history, source switching, selected-IR replacement, or production mutation gates. +When the optional IR optimizer bridge participates, each kernel directory may also receive `runtime-ir-optimizer-evidence.properties`. The artifact records only proposal/provider evidence from stable `javatogpu.ir-optimizer` sources: pass counts, proposal-only versus selected optimized counts, rollback counts, IR identities, proof fields, approval-template applicability, and diagnostics. Constant-folding preview proof is also aggregated into `constantFoldingPreview.*` fields for pass count, candidate count, skipped blockers, required runtime-equivalence/approval gates, and unresolved integer-overflow / floating-point-rounding safety flags. Safe-local-CSE preview proof is aggregated into `safeLocalCsePreview.*` fields for pass count, expression/candidate/duplicate/equivalence-class counts, unsupported-operator / impure-operand / control-flow-boundary blockers, required runtime-equivalence/approval gates, and unresolved dominance / side-effect-freedom proof flags. Typed dead-code preview proof is aggregated into `typedDeadCodePreview.*` fields for pass count, typed/reachable/unreachable node counts, missing-root / missing-child-reference / side-effecting-unreachable blockers, required runtime-equivalence/approval gates, and unresolved side-effect-freedom proof flags. The artifact also records `previewReadiness.*` fields with aggregate status, recorded/candidate/blocked family counts, and a per-family status summary so CI can distinguish no-candidate previews from proof-blocked previews and review-ready runtime-equivalence candidates. `runtimeEquivalenceReview.*` records the next fail-closed review boundary: status, eligibility, requirement, first blocker, family summary, disabled production mutation, disabled selected-IR replacement, and manual-review-only state. `reviewPackage.*` records the manual-review package boundary above that review gate, including package status, requirement/completeness, first blocker, proposal-pass count, pending approval count, original/optimized IR and proof-summary requirements, manual-review-only state, and disabled production mutation / selected-IR replacement. `openClValidationReport` aggregates this into `Runtime IR Optimizer Evidence` for CI visibility, including constant-folding, safe-local-CSE, typed dead-code preview totals, preview-readiness status, runtime-equivalence review eligibility, and review-package status. The section is evidence-only and does not participate in history, source switching, selected-IR replacement, or production mutation gates. The same index records `familyBinding.*`. Binding remains `not-bound` unless exactly one optimizer family has passed runtime evidence whose comparison mode is explicitly family-specific (`optimizer-family::...`). Descriptor-versus-reconstructed-source evidence is pipeline validation and is deliberately rejected as optimizer-family proof. diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java index 69026dfb..f66355c7 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java @@ -817,6 +817,22 @@ private static String formatRuntimeIrOptimizerEvidence(GpuRuntimeIrOptimizationR builder.append("runtimeEquivalenceReview.productionMutation=disabled\n"); builder.append("runtimeEquivalenceReview.selectedIrReplacement=disabled\n"); builder.append("runtimeEquivalenceReview.manualReviewOnly=true\n"); + ReviewPackageEvidence reviewPackage = reviewPackageEvidence(irOptimizerReports, runtimeEquivalenceReview); + builder.append("reviewPackage.status=").append(reviewPackage.status()).append('\n'); + builder.append("reviewPackage.required=").append(reviewPackage.required()).append('\n'); + builder.append("reviewPackage.complete=").append(reviewPackage.complete()).append('\n'); + builder.append("reviewPackage.firstBlocker=") + .append(safePropertyValue(reviewPackage.firstBlocker())).append('\n'); + builder.append("reviewPackage.proposalPass.count=").append(reviewPackage.proposalPassCount()).append('\n'); + builder.append("reviewPackage.pendingApproval.count=").append(reviewPackage.pendingApprovalCount()).append('\n'); + builder.append("reviewPackage.runtimeEquivalence.status=") + .append(safePropertyValue(reviewPackage.runtimeEquivalenceStatus())).append('\n'); + builder.append("reviewPackage.originalIrRequired=true\n"); + builder.append("reviewPackage.optimizedIrRequired=true\n"); + builder.append("reviewPackage.proofSummaryRequired=true\n"); + builder.append("reviewPackage.manualReviewOnly=true\n"); + builder.append("reviewPackage.productionMutation=disabled\n"); + builder.append("reviewPackage.selectedIrReplacement=disabled\n"); for (int index = 0; index < irOptimizerReports.size(); index++) { GpuRuntimeIrOptimizationPassReport passReport = irOptimizerReports.get(index); String prefix = "pass." + index + "."; @@ -1128,6 +1144,50 @@ private static RuntimeEquivalenceReviewEvidence runtimeEquivalenceReviewEvidence ); } + private static ReviewPackageEvidence reviewPackageEvidence( + List passReports, + RuntimeEquivalenceReviewEvidence runtimeEquivalenceReview + ) { + int proposalPassCount = 0; + int pendingApprovalCount = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : passReports) { + if (passReport == null) { + continue; + } + ApprovalTemplateEvidence approvalTemplate = approvalTemplateEvidence(passReport); + if (approvalTemplate.applicable()) { + proposalPassCount++; + } + if ("pending".equals(approvalTemplate.status())) { + pendingApprovalCount++; + } + } + boolean required = runtimeEquivalenceReview.required() || proposalPassCount > 0; + boolean complete = false; + String firstBlocker; + if (!required) { + firstBlocker = "review-package-not-required"; + } else if (!runtimeEquivalenceReview.eligible()) { + firstBlocker = runtimeEquivalenceReview.firstBlocker(); + } else if (pendingApprovalCount > 0) { + firstBlocker = "approval-template-pending"; + } else if (proposalPassCount <= 0) { + firstBlocker = "optimized-ir-proposal-missing"; + } else { + firstBlocker = "manual-review-required"; + } + String status = required ? "pending-manual-review" : "not-required"; + return new ReviewPackageEvidence( + status, + required, + complete, + firstBlocker, + proposalPassCount, + pendingApprovalCount, + runtimeEquivalenceReview.status() + ); + } + private static String previewReadinessStatus(List families) { if (families.stream().allMatch(family -> "not-recorded".equals(family.status()))) { return "not-recorded"; @@ -1293,6 +1353,17 @@ private record RuntimeEquivalenceReviewEvidence( ) { } + private record ReviewPackageEvidence( + String status, + boolean required, + boolean complete, + String firstBlocker, + int proposalPassCount, + int pendingApprovalCount, + String runtimeEquivalenceStatus + ) { + } + private static ApprovalTemplateEvidence approvalTemplateEvidence(GpuRuntimeIrOptimizationPassReport passReport) { if (passReport == null) { return ApprovalTemplateEvidence.notApplicable("pass-report-missing"); diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java index da445976..bdfb2f3b 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java @@ -205,6 +205,43 @@ String runtimeEquivalenceReviewFirstBlocker() { }; } + int totalReviewPackageRequiredCount() { + return (int) entries.stream().filter(Entry::reviewPackageRequired).count(); + } + + int totalReviewPackageCompleteCount() { + return (int) entries.stream().filter(Entry::reviewPackageComplete).count(); + } + + int totalReviewPackageProposalPassCount() { + return entries.stream().mapToInt(Entry::reviewPackageProposalPassCount).sum(); + } + + int totalReviewPackagePendingApprovalCount() { + return entries.stream().mapToInt(Entry::reviewPackagePendingApprovalCount).sum(); + } + + String reviewPackageStatus() { + if (entries.isEmpty() || entries.stream().allMatch(entry -> "missing".equals(entry.status()))) { + return "not-recorded"; + } + if (totalReviewPackageRequiredCount() <= 0) { + return "not-required"; + } + return totalReviewPackageCompleteCount() == totalReviewPackageRequiredCount() + ? "complete" + : "pending-manual-review"; + } + + String reviewPackageFirstBlocker() { + for (Entry entry : entries) { + if (entry.reviewPackageRequired() && !entry.reviewPackageComplete()) { + return entry.reviewPackageFirstBlocker(); + } + } + return "none"; + } + private List previewReadinessFamilies() { return List.of( new PreviewFamilyReadiness( @@ -334,12 +371,23 @@ String toMarkdown() { .append(inline(runtimeEquivalenceReviewFirstBlocker())).append("`\n"); markdown.append("- Runtime-equivalence review production mutation: `disabled`\n"); markdown.append("- Runtime-equivalence review selected IR replacement: `disabled`\n"); + markdown.append("- Review package status: `").append(reviewPackageStatus()).append("`\n"); + markdown.append("- Review package required kernels: `").append(totalReviewPackageRequiredCount()).append("`\n"); + markdown.append("- Review package complete kernels: `").append(totalReviewPackageCompleteCount()).append("`\n"); + markdown.append("- Review package proposal passes: `").append(totalReviewPackageProposalPassCount()).append("`\n"); + markdown.append("- Review package pending approvals: `") + .append(totalReviewPackagePendingApprovalCount()).append("`\n"); + markdown.append("- Review package first blocker: `") + .append(inline(reviewPackageFirstBlocker())).append("`\n"); + markdown.append("- Review package manual review only: `true`\n"); + markdown.append("- Review package production mutation: `disabled`\n"); + markdown.append("- Review package selected IR replacement: `disabled`\n"); markdown.append("- Providers: `").append(inline(providerSummary())).append("`\n\n"); if (entries.isEmpty()) { return markdown.toString(); } - markdown.append("| Kernel resource | Status | Passes | Proposal-only | Selected | Rolled back | Approval pending | Approval N/A | CF candidates | CF skipped | CSE candidates | CSE duplicates | CSE blocked | TDC unreachable | TDC blocked | Providers |\n"); - markdown.append("| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- |\n"); + markdown.append("| Kernel resource | Status | Passes | Proposal-only | Selected | Rolled back | Approval pending | Approval N/A | CF candidates | CF skipped | CSE candidates | CSE duplicates | CSE blocked | TDC unreachable | TDC blocked | Review package | Review blocker | Providers |\n"); + markdown.append("| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- | --- | --- |\n"); for (Entry entry : entries) { markdown.append("| `").append(table(entry.kernelResource())).append("` | `") .append(table(entry.status())).append("` | `") @@ -356,6 +404,8 @@ String toMarkdown() { .append(entry.safeLocalCsePreviewBlockedCount()).append("` | `") .append(entry.typedDeadCodePreviewUnreachableNodeCount()).append("` | `") .append(entry.typedDeadCodePreviewBlockedCount()).append("` | `") + .append(table(entry.reviewPackageStatus())).append("` | `") + .append(table(entry.reviewPackageFirstBlocker())).append("` | `") .append(table(formatProviderCounts(entry.providerCounts()))).append("` |\n"); } markdown.append('\n'); @@ -508,6 +558,14 @@ record Entry( boolean typedDeadCodePreviewRuntimeEquivalenceRequiredBeforeRewrite, boolean typedDeadCodePreviewApprovalRequiredBeforeRewrite, boolean typedDeadCodePreviewSideEffectFreedomProven, + String reviewPackageStatus, + boolean reviewPackageRequired, + boolean reviewPackageComplete, + String reviewPackageFirstBlocker, + int reviewPackageProposalPassCount, + int reviewPackagePendingApprovalCount, + String reviewPackageRuntimeEquivalenceStatus, + boolean reviewPackageManualReviewOnly, Map providerCounts ) { @@ -542,6 +600,11 @@ record Entry( typedDeadCodePreviewBlockedMissingRootCount = Math.max(0, typedDeadCodePreviewBlockedMissingRootCount); typedDeadCodePreviewBlockedMissingChildReferenceCount = Math.max(0, typedDeadCodePreviewBlockedMissingChildReferenceCount); typedDeadCodePreviewBlockedSideEffectingUnreachableNodeCount = Math.max(0, typedDeadCodePreviewBlockedSideEffectingUnreachableNodeCount); + reviewPackageStatus = normalize(reviewPackageStatus, "not-recorded"); + reviewPackageFirstBlocker = normalize(reviewPackageFirstBlocker, "none"); + reviewPackageProposalPassCount = Math.max(0, reviewPackageProposalPassCount); + reviewPackagePendingApprovalCount = Math.max(0, reviewPackagePendingApprovalCount); + reviewPackageRuntimeEquivalenceStatus = normalize(reviewPackageRuntimeEquivalenceStatus, "unknown"); providerCounts = normalizeProviderCounts(providerCounts); } @@ -604,7 +667,9 @@ static Entry from(String kernelResource, Properties properties) { if (properties == null || properties.isEmpty()) { return new Entry(kernelResource, "missing", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false, false, false, false, 0, 0, 0, 0, 0, 0, 0, 0, false, false, false, false, - 0, 0, 0, 0, 0, 0, 0, false, false, false, Map.of()); + 0, 0, 0, 0, 0, 0, 0, false, false, false, + "not-recorded", false, false, "review-package-not-recorded", 0, 0, "unknown", false, + Map.of()); } return new Entry( kernelResource, @@ -648,6 +713,14 @@ static Entry from(String kernelResource, Properties properties) { parseBoolean(properties.getProperty("typedDeadCodePreview.runtimeEquivalenceRequiredBeforeRewrite")), parseBoolean(properties.getProperty("typedDeadCodePreview.approvalRequiredBeforeRewrite")), parseBoolean(properties.getProperty("typedDeadCodePreview.sideEffectFreedomProven")), + properties.getProperty("reviewPackage.status", "not-recorded"), + parseBoolean(properties.getProperty("reviewPackage.required")), + parseBoolean(properties.getProperty("reviewPackage.complete")), + properties.getProperty("reviewPackage.firstBlocker", "none"), + parseInt(properties.getProperty("reviewPackage.proposalPass.count"), 0), + parseInt(properties.getProperty("reviewPackage.pendingApproval.count"), 0), + properties.getProperty("reviewPackage.runtimeEquivalence.status", "unknown"), + parseBoolean(properties.getProperty("reviewPackage.manualReviewOnly")), parseProviderCounts(properties) ); } diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java index eaf0f170..cba6bbd4 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java @@ -2199,6 +2199,19 @@ void dumpsRuntimeIrOptimizerEvidenceArtifactForProposalBridgeReports() { assertTrue(evidence.contains("runtimeEquivalenceReview.productionMutation=disabled")); assertTrue(evidence.contains("runtimeEquivalenceReview.selectedIrReplacement=disabled")); assertTrue(evidence.contains("runtimeEquivalenceReview.manualReviewOnly=true")); + assertTrue(evidence.contains("reviewPackage.status=pending-manual-review")); + assertTrue(evidence.contains("reviewPackage.required=true")); + assertTrue(evidence.contains("reviewPackage.complete=false")); + assertTrue(evidence.contains("reviewPackage.firstBlocker=preview-readiness-blocked-by-proof")); + assertTrue(evidence.contains("reviewPackage.proposalPass.count=1")); + assertTrue(evidence.contains("reviewPackage.pendingApproval.count=1")); + assertTrue(evidence.contains("reviewPackage.runtimeEquivalence.status=blocked")); + assertTrue(evidence.contains("reviewPackage.originalIrRequired=true")); + assertTrue(evidence.contains("reviewPackage.optimizedIrRequired=true")); + assertTrue(evidence.contains("reviewPackage.proofSummaryRequired=true")); + assertTrue(evidence.contains("reviewPackage.manualReviewOnly=true")); + assertTrue(evidence.contains("reviewPackage.productionMutation=disabled")); + assertTrue(evidence.contains("reviewPackage.selectedIrReplacement=disabled")); assertTrue(evidence.contains("pass.0.passVersion=javatogpu.ir-optimizer.noop:1")); assertTrue(evidence.contains("pass.1.passVersion=javatogpu.ir-optimizer.text-canonicalization:1")); assertTrue(evidence.contains("pass.2.passVersion=javatogpu.ir-optimizer.constant-folding-preview:1")); diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java index c112ad63..16940295 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java @@ -75,6 +75,14 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { typedDeadCodePreview.runtimeEquivalenceRequiredBeforeRewrite=true typedDeadCodePreview.approvalRequiredBeforeRewrite=true typedDeadCodePreview.sideEffectFreedomProven=false + reviewPackage.status=pending-manual-review + reviewPackage.required=true + reviewPackage.complete=false + reviewPackage.firstBlocker=preview-readiness-blocked-by-proof + reviewPackage.proposalPass.count=1 + reviewPackage.pendingApproval.count=1 + reviewPackage.runtimeEquivalence.status=blocked + reviewPackage.manualReviewOnly=true pass.0.passVersion=ir-optimizer:no-op:1 pass.1.passVersion=ir-optimizer:text-canonicalization:1 pass.2.passVersion=ir-optimizer:text-canonicalization:1 @@ -139,6 +147,12 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { assertEquals("blocked", summary.runtimeEquivalenceReviewStatus()); assertTrue(summary.runtimeEquivalenceReviewRequired()); assertEquals("preview-readiness-blocked-by-proof", summary.runtimeEquivalenceReviewFirstBlocker()); + assertEquals("pending-manual-review", summary.reviewPackageStatus()); + assertEquals(1, summary.totalReviewPackageRequiredCount()); + assertEquals(0, summary.totalReviewPackageCompleteCount()); + assertEquals(1, summary.totalReviewPackageProposalPassCount()); + assertEquals(1, summary.totalReviewPackagePendingApprovalCount()); + assertEquals("preview-readiness-blocked-by-proof", summary.reviewPackageFirstBlocker()); assertEquals( "ir-optimizer:no-op:1=1, ir-optimizer:text-canonicalization:1=2", summary.providerSummary() @@ -168,9 +182,18 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { assertTrue(summary.toMarkdown().contains("- Runtime-equivalence review first blocker: `preview-readiness-blocked-by-proof`")); assertTrue(summary.toMarkdown().contains("- Runtime-equivalence review production mutation: `disabled`")); assertTrue(summary.toMarkdown().contains("- Runtime-equivalence review selected IR replacement: `disabled`")); + assertTrue(summary.toMarkdown().contains("- Review package status: `pending-manual-review`")); + assertTrue(summary.toMarkdown().contains("- Review package required kernels: `1`")); + assertTrue(summary.toMarkdown().contains("- Review package complete kernels: `0`")); + assertTrue(summary.toMarkdown().contains("- Review package proposal passes: `1`")); + assertTrue(summary.toMarkdown().contains("- Review package pending approvals: `1`")); + assertTrue(summary.toMarkdown().contains("- Review package first blocker: `preview-readiness-blocked-by-proof`")); + assertTrue(summary.toMarkdown().contains("- Review package manual review only: `true`")); + assertTrue(summary.toMarkdown().contains("- Review package production mutation: `disabled`")); + assertTrue(summary.toMarkdown().contains("- Review package selected IR replacement: `disabled`")); assertTrue(summary.toMarkdown().contains("- Providers: `ir-optimizer:no-op:1=1, ir-optimizer:text-canonicalization:1=2`")); - assertTrue(summary.toMarkdown().contains("| `kernel-a.cl` | `recorded` | `3` | `2` | `1` | `1` | `1` | `2` | `3` | `5` | `4` | `2` | `6` | `3` | `4` | `ir-optimizer:no-op:1=1, ir-optimizer:text-canonicalization:1=2` |")); - assertTrue(summary.toMarkdown().contains("| `kernel-b.cl` | `missing` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `none` |")); + assertTrue(summary.toMarkdown().contains("| `kernel-a.cl` | `recorded` | `3` | `2` | `1` | `1` | `1` | `2` | `3` | `5` | `4` | `2` | `6` | `3` | `4` | `pending-manual-review` | `preview-readiness-blocked-by-proof` | `ir-optimizer:no-op:1=1, ir-optimizer:text-canonicalization:1=2` |")); + assertTrue(summary.toMarkdown().contains("| `kernel-b.cl` | `missing` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `not-recorded` | `review-package-not-recorded` | `none` |")); } @Test @@ -206,6 +229,14 @@ void marksPreviewReadinessReadyWhenCandidatesHaveNoBlockers() throws Exception { typedDeadCodePreview.pass.count=1 typedDeadCodePreview.unreachableNode.count=1 typedDeadCodePreview.sideEffectFreedomProven=true + reviewPackage.status=pending-manual-review + reviewPackage.required=true + reviewPackage.complete=false + reviewPackage.firstBlocker=optimized-ir-proposal-missing + reviewPackage.proposalPass.count=0 + reviewPackage.pendingApproval.count=0 + reviewPackage.runtimeEquivalence.status=review-ready + reviewPackage.manualReviewOnly=true pass.0.passVersion=ir-optimizer:constant-folding-preview:1 pass.1.passVersion=ir-optimizer:safe-local-cse-preview:1 pass.2.passVersion=ir-optimizer:typed-dead-code-preview:1 @@ -218,6 +249,8 @@ void marksPreviewReadinessReadyWhenCandidatesHaveNoBlockers() throws Exception { assertTrue(summary.runtimeEquivalenceReviewEligible()); assertTrue(summary.runtimeEquivalenceReviewRequired()); assertEquals("none", summary.runtimeEquivalenceReviewFirstBlocker()); + assertEquals("pending-manual-review", summary.reviewPackageStatus()); + assertEquals("optimized-ir-proposal-missing", summary.reviewPackageFirstBlocker()); assertEquals(3, summary.totalPreviewReadinessFamilyCount()); assertEquals(3, summary.totalPreviewReadinessCandidateFamilyCount()); assertEquals(0, summary.totalPreviewReadinessBlockedFamilyCount()); diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java index 3b29c324..d41c01de 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java @@ -1484,7 +1484,13 @@ void validationReporterWritesKernelLaunchAdvisoryReportAndWorkflowFragment() thr assertTrue(reportMarkdown.contains("- Runtime-equivalence review first blocker: `preview-readiness-blocked-by-proof`")); assertTrue(reportMarkdown.contains("- Runtime-equivalence review production mutation: `disabled`")); assertTrue(reportMarkdown.contains("- Runtime-equivalence review selected IR replacement: `disabled`")); - assertTrue(reportMarkdown.contains("| `workload/perlin.cl` | `recorded` | `1` | `1` | `0` | `0` | `0` | `0` | `2` | `2` | `3` | `1` | `2` | `2` | `2` | `ir-optimizer:text-canonicalization:1=1` |")); + assertTrue(reportMarkdown.contains("- Review package status: `not-required`")); + assertTrue(reportMarkdown.contains("- Review package required kernels: `0`")); + assertTrue(reportMarkdown.contains("- Review package first blocker: `none`")); + assertTrue(reportMarkdown.contains("- Review package manual review only: `true`")); + assertTrue(reportMarkdown.contains("- Review package production mutation: `disabled`")); + assertTrue(reportMarkdown.contains("- Review package selected IR replacement: `disabled`")); + assertTrue(reportMarkdown.contains("| `workload/perlin.cl` | `recorded` | `1` | `1` | `0` | `0` | `0` | `0` | `2` | `2` | `3` | `1` | `2` | `2` | `2` | `not-recorded` | `none` | `ir-optimizer:text-canonicalization:1=1` |")); assertTrue(reportMarkdown.contains("ir-optimizer:text-canonicalization:1=1")); assertTrue(summaryMarkdown.contains("## Kernel Launch Advisories")); assertTrue(summaryMarkdown.contains("- Blocking: `0`")); From 3d1e2a41e9c6a55d9c5302908c57bb8fd63d1989 Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Sun, 12 Jul 2026 16:34:04 +0300 Subject: [PATCH 19/32] Add OpenCL runtime IR optimizer evidence validator --- ROADMAP.md | 1 + docs/Runtime-Guide.md | 2 +- docs/Validation-and-Operations.md | 3 + processor/build.gradle | 13 ++ ...untimeIrOptimizerEvidenceValidatorCli.java | 160 ++++++++++++++++++ ...meIrOptimizerEvidenceValidatorCliTest.java | 118 +++++++++++++ 6 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCli.java create mode 100644 processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCliTest.java diff --git a/ROADMAP.md b/ROADMAP.md index 2c24b118..bda40338 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -684,6 +684,7 @@ Current rule: `ir-validation` remains strictly read-only. Optimizer modules may The same runtime evidence now includes `previewReadiness.*` aggregate fields over constant folding, safe-local CSE, and typed dead-code cleanup. The aggregate records overall status, recorded/candidate/blocked family counts, and a per-family status summary (`not-recorded`, `no-candidates`, `candidates-recorded`, `blocked-by-proof`, or `ready-for-runtime-equivalence-review`) so CI can track optimizer-family readiness without enabling production mutation or selected-IR replacement. `runtimeEquivalenceReview.*` now sits above `previewReadiness.*` as a fail-closed review gate. It records whether preview families are eligible for runtime-equivalence review, whether review evidence is required, the first blocker, the family summary, and explicitly disabled production mutation / selected-IR replacement so review readiness cannot be confused with production optimization enablement. `reviewPackage.*` now sits above runtime-equivalence review as the manual-review bundle boundary. It records package status, requirement/completeness, first blocker, proposal-pass count, pending approval count, original/optimized IR and proof-summary requirements, and repeats manual-review-only plus disabled production mutation / selected-IR replacement guardrails so review packaging remains evidence-only. + `validateOpenClRuntimeIrOptimizerEvidence` now validates those raw runtime evidence files in the OpenCL operational/NVIDIA routines, failing CI if review-package guardrails disappear, if production mutation or selected-IR replacement becomes enabled, or if a required package is marked complete/missing a blocker before an activation design exists. - [x] Keep vendor optimization as a separate optional provider layer. `ir-vendor-optimizer` now exists as a separate optional module skeleton. It contributes vendor proposals only through `GpuIrVendorOptimizationProposalProvider` and `GpuIrVendorOptimizationProposalRegistry`, while the default runtime bridge keeps loading only backend-neutral proposal providers. Future vendor modules may consume target device profile, backend compiler feedback, resource metrics, driver quirks, launch advisories, and promotion evidence, but missing modules, missing matching device evidence, or disabled vendor adaptation must leave backend-neutral IR selection unchanged. - [x] Gate production optimizer selection through approval-bound artifacts. diff --git a/docs/Runtime-Guide.md b/docs/Runtime-Guide.md index d3660b5c..457887bc 100644 --- a/docs/Runtime-Guide.md +++ b/docs/Runtime-Guide.md @@ -233,7 +233,7 @@ OpenCL isolated runtime-equivalence checks write raw pipeline comparison cases i Non-analysis optimizer passes with runtime-equivalence payload evidence also produce `runtime-optimizer-family-equivalence-payload.properties` plus durable files under `runtime-optimizer-family-equivalence-payload/family--/pass-/`. Each pass directory contains `manifest.properties`, CPU/reference, pre-optimization, post-optimization, tolerance, failure-fixture, and diagnostics properties files. When proof fields contain structured case evidence, the component files include deterministic indexed fields for case names, inputs, raw outputs, tolerances, equivalence flags, and diagnostics. Missing components are materialized with `status=not-recorded`; they are never inferred from another pass. Artifact paths are normalized and must remain inside the kernel's `runtime-compile-artifacts` directory. These files are diagnostic evidence only and cannot enable source switching or production mutation. -When the optional IR optimizer bridge participates, each kernel directory may also receive `runtime-ir-optimizer-evidence.properties`. The artifact records only proposal/provider evidence from stable `javatogpu.ir-optimizer` sources: pass counts, proposal-only versus selected optimized counts, rollback counts, IR identities, proof fields, approval-template applicability, and diagnostics. Constant-folding preview proof is also aggregated into `constantFoldingPreview.*` fields for pass count, candidate count, skipped blockers, required runtime-equivalence/approval gates, and unresolved integer-overflow / floating-point-rounding safety flags. Safe-local-CSE preview proof is aggregated into `safeLocalCsePreview.*` fields for pass count, expression/candidate/duplicate/equivalence-class counts, unsupported-operator / impure-operand / control-flow-boundary blockers, required runtime-equivalence/approval gates, and unresolved dominance / side-effect-freedom proof flags. Typed dead-code preview proof is aggregated into `typedDeadCodePreview.*` fields for pass count, typed/reachable/unreachable node counts, missing-root / missing-child-reference / side-effecting-unreachable blockers, required runtime-equivalence/approval gates, and unresolved side-effect-freedom proof flags. The artifact also records `previewReadiness.*` fields with aggregate status, recorded/candidate/blocked family counts, and a per-family status summary so CI can distinguish no-candidate previews from proof-blocked previews and review-ready runtime-equivalence candidates. `runtimeEquivalenceReview.*` records the next fail-closed review boundary: status, eligibility, requirement, first blocker, family summary, disabled production mutation, disabled selected-IR replacement, and manual-review-only state. `reviewPackage.*` records the manual-review package boundary above that review gate, including package status, requirement/completeness, first blocker, proposal-pass count, pending approval count, original/optimized IR and proof-summary requirements, manual-review-only state, and disabled production mutation / selected-IR replacement. `openClValidationReport` aggregates this into `Runtime IR Optimizer Evidence` for CI visibility, including constant-folding, safe-local-CSE, typed dead-code preview totals, preview-readiness status, runtime-equivalence review eligibility, and review-package status. The section is evidence-only and does not participate in history, source switching, selected-IR replacement, or production mutation gates. +When the optional IR optimizer bridge participates, each kernel directory may also receive `runtime-ir-optimizer-evidence.properties`. The artifact records only proposal/provider evidence from stable `javatogpu.ir-optimizer` sources: pass counts, proposal-only versus selected optimized counts, rollback counts, IR identities, proof fields, approval-template applicability, and diagnostics. Constant-folding preview proof is also aggregated into `constantFoldingPreview.*` fields for pass count, candidate count, skipped blockers, required runtime-equivalence/approval gates, and unresolved integer-overflow / floating-point-rounding safety flags. Safe-local-CSE preview proof is aggregated into `safeLocalCsePreview.*` fields for pass count, expression/candidate/duplicate/equivalence-class counts, unsupported-operator / impure-operand / control-flow-boundary blockers, required runtime-equivalence/approval gates, and unresolved dominance / side-effect-freedom proof flags. Typed dead-code preview proof is aggregated into `typedDeadCodePreview.*` fields for pass count, typed/reachable/unreachable node counts, missing-root / missing-child-reference / side-effecting-unreachable blockers, required runtime-equivalence/approval gates, and unresolved side-effect-freedom proof flags. The artifact also records `previewReadiness.*` fields with aggregate status, recorded/candidate/blocked family counts, and a per-family status summary so CI can distinguish no-candidate previews from proof-blocked previews and review-ready runtime-equivalence candidates. `runtimeEquivalenceReview.*` records the next fail-closed review boundary: status, eligibility, requirement, first blocker, family summary, disabled production mutation, disabled selected-IR replacement, and manual-review-only state. `reviewPackage.*` records the manual-review package boundary above that review gate, including package status, requirement/completeness, first blocker, proposal-pass count, pending approval count, original/optimized IR and proof-summary requirements, manual-review-only state, and disabled production mutation / selected-IR replacement. `openClValidationReport` aggregates this into `Runtime IR Optimizer Evidence` for CI visibility, including constant-folding, safe-local-CSE, typed dead-code preview totals, preview-readiness status, runtime-equivalence review eligibility, and review-package status. `validateOpenClRuntimeIrOptimizerEvidence` then validates the raw runtime evidence files and fails CI if those fail-closed guardrails are missing, completed early, or production-enabling. The section and validator are evidence-only and do not participate in history, source switching, selected-IR replacement, or production mutation gates. The same index records `familyBinding.*`. Binding remains `not-bound` unless exactly one optimizer family has passed runtime evidence whose comparison mode is explicitly family-specific (`optimizer-family::...`). Descriptor-versus-reconstructed-source evidence is pipeline validation and is deliberately rejected as optimizer-family proof. diff --git a/docs/Validation-and-Operations.md b/docs/Validation-and-Operations.md index b8399685..3fe9a07a 100644 --- a/docs/Validation-and-Operations.md +++ b/docs/Validation-and-Operations.md @@ -60,6 +60,7 @@ Important buckets include: - `:processor:openClProductionActivationTokenNegativeTest` - `:processor:openClOptimizerFamilyPayloadFixtureTest` - `:processor:openClValidationReport` +- `:processor:validateOpenClRuntimeIrOptimizerEvidence` You usually do not need to run buckets one by one unless you are narrowing down a failure. @@ -101,6 +102,8 @@ Use `activate` only after manifest validation succeeds. The workflow then writes The vendor workflow also runs `openClOptimizerFamilyPayloadFixtureTest` after the main validation bucket. It must produce `fixture-summary.properties` with two complete families and fourteen durable files. This fixture proves the nested artifact contract is uploadable and path-safe; it does not alter real-workload optimizer-family counts or production readiness. +`validateOpenClRuntimeIrOptimizerEvidence` checks `runtime-ir-optimizer-evidence.properties` under `runtime-compile-artifacts`. It fails if runtime-equivalence review or review-package guardrails stop being fail-closed, if production mutation or selected-IR replacement becomes enabled, if a required review package has no blocker, or if a package is marked complete before manual-review activation exists. The operational and NVIDIA routines run this validator after `openClValidationReport`. + ## Optional IR Validation For stricter compiler diagnostics or CI evidence, add `javatogpu-ir-validation` and start with diagnostic mode: diff --git a/processor/build.gradle b/processor/build.gradle index bca79318..8452da88 100644 --- a/processor/build.gradle +++ b/processor/build.gradle @@ -78,6 +78,7 @@ def openClPromotionGitSha = providers.gradleProperty('openClPromotionGitSha') .orElse('unknown') def openClKernelLaunchAdvisoryDriftFile = layout.buildDirectory.file('reports/opencl/runtime-launch-advisory-drift.properties').get().asFile def openClCompilerResourceDriftFile = layout.buildDirectory.file('reports/opencl/runtime-compiler-resource-drift.properties').get().asFile +def openClRuntimeIrOptimizerEvidenceDirectory = layout.buildDirectory.dir('reports/opencl/runtime-compile-artifacts').get().asFile def openClValidationHistoryFile = layout.buildDirectory.file('reports/opencl/validation-history.properties').get().asFile def openClValidationHistoryMarkdownFile = layout.buildDirectory.file('reports/opencl/validation-history.md').get().asFile def openClValidationHistoryCacheDirectory = layout.buildDirectory.dir('opencl-validation-history-cache').get().asFile @@ -650,6 +651,16 @@ tasks.register('validateOpenClCompilerResourceDrift', JavaExec) { inputs.file(openClCompilerResourceDriftFile) } +tasks.register('validateOpenClRuntimeIrOptimizerEvidence', JavaExec) { + group = 'verification' + description = 'Fails OpenCL CI when runtime IR optimizer evidence loses fail-closed review-package guardrails.' + dependsOn 'openClValidationReport' + classpath = sourceSets.main.runtimeClasspath + mainClass = 'net.sixik.ga_utils.javatogpu.runtime.opencl.OpenClRuntimeIrOptimizerEvidenceValidatorCli' + args openClRuntimeIrOptimizerEvidenceDirectory.absolutePath + inputs.dir(openClRuntimeIrOptimizerEvidenceDirectory) +} + tasks.register('prepareOpenClKernelLaunchAdvisoryNegativeFixture', JavaExec) { group = 'verification' description = 'Mutates only the restored validation-history baseline to force a per-kernel drift regression.' @@ -997,6 +1008,7 @@ tasks.register('openClNvidiaRoutine') { dependsOn 'openClLongRunningStabilityTest' dependsOn 'benchmarkTest' finalizedBy 'openClValidationReport' + finalizedBy 'validateOpenClRuntimeIrOptimizerEvidence' } tasks.register('openClOperationalRoutine') { @@ -1011,6 +1023,7 @@ tasks.register('openClOperationalRoutine') { dependsOn 'openClLongRunningStabilityTest' dependsOn 'benchmarkTest' finalizedBy 'openClValidationReport' + finalizedBy 'validateOpenClRuntimeIrOptimizerEvidence' } tasks.register('benchmarkTest', Test) { diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCli.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCli.java new file mode 100644 index 00000000..858573c8 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCli.java @@ -0,0 +1,160 @@ +package net.sixik.ga_utils.javatogpu.runtime.opencl; + +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileArtifactDumper; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Properties; + +/** Command-line entrypoint for validating fail-closed runtime IR optimizer evidence guardrails. */ +public final class OpenClRuntimeIrOptimizerEvidenceValidatorCli { + + private static final String ARTIFACT_FILE_NAME = + GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT; + + private OpenClRuntimeIrOptimizerEvidenceValidatorCli() { + } + + public static void main(String[] args) throws IOException { + if (args.length != 1 || args[0] == null || args[0].isBlank()) { + throw new IllegalArgumentException("Expected runtime IR optimizer evidence file or artifact directory"); + } + Path artifactPath = Path.of(args[0]); + List artifacts = evidenceArtifacts(artifactPath); + if (artifacts.isEmpty()) { + throw new IllegalStateException("Missing runtime IR optimizer evidence artifact under " + artifactPath); + } + + int recorded = 0; + int notRequired = 0; + int pendingManualReview = 0; + for (Path artifact : artifacts) { + Properties properties = loadProperties(artifact); + String status = properties.getProperty("status", "unknown"); + if (!"recorded".equals(status)) { + throw new IllegalStateException("Runtime IR optimizer evidence must be recorded for " + + artifact + ": status=" + status); + } + validateGuardrails(artifact, properties); + String reviewPackageStatus = properties.getProperty("reviewPackage.status", "not-recorded"); + if ("not-required".equals(reviewPackageStatus)) { + notRequired++; + } else if ("pending-manual-review".equals(reviewPackageStatus)) { + pendingManualReview++; + } + recorded++; + } + + System.out.println("Runtime IR optimizer evidence OK: recorded=" + recorded + + ", reviewPackage.notRequired=" + notRequired + + ", reviewPackage.pendingManualReview=" + pendingManualReview); + } + + private static List evidenceArtifacts(Path path) throws IOException { + if (Files.isRegularFile(path)) { + return ARTIFACT_FILE_NAME.equals(path.getFileName().toString()) ? List.of(path) : List.of(); + } + if (!Files.isDirectory(path)) { + return List.of(); + } + try (java.util.stream.Stream paths = Files.walk(path)) { + return paths + .filter(Files::isRegularFile) + .filter(candidate -> ARTIFACT_FILE_NAME.equals(candidate.getFileName().toString())) + .sorted() + .toList(); + } + } + + private static Properties loadProperties(Path path) throws IOException { + Properties properties = new Properties(); + try (InputStream inputStream = Files.newInputStream(path)) { + properties.load(inputStream); + } + return properties; + } + + private static void validateGuardrails(Path artifact, Properties properties) { + requireValue(artifact, properties, "runtimeEquivalenceReview.productionMutation", "disabled"); + requireValue(artifact, properties, "runtimeEquivalenceReview.selectedIrReplacement", "disabled"); + requireValue(artifact, properties, "runtimeEquivalenceReview.manualReviewOnly", "true"); + requireValue(artifact, properties, "reviewPackage.manualReviewOnly", "true"); + requireValue(artifact, properties, "reviewPackage.productionMutation", "disabled"); + requireValue(artifact, properties, "reviewPackage.selectedIrReplacement", "disabled"); + requireValue(artifact, properties, "reviewPackage.originalIrRequired", "true"); + requireValue(artifact, properties, "reviewPackage.optimizedIrRequired", "true"); + requireValue(artifact, properties, "reviewPackage.proofSummaryRequired", "true"); + + String reviewPackageStatus = requirePresent(artifact, properties, "reviewPackage.status"); + boolean required = parseBoolean(requirePresent(artifact, properties, "reviewPackage.required")); + boolean complete = parseBoolean(requirePresent(artifact, properties, "reviewPackage.complete")); + String firstBlocker = requirePresent(artifact, properties, "reviewPackage.firstBlocker"); + int proposalPassCount = parseNonNegativeInt(artifact, properties, "reviewPackage.proposalPass.count"); + int pendingApprovalCount = parseNonNegativeInt(artifact, properties, "reviewPackage.pendingApproval.count"); + + if (!List.of("not-required", "pending-manual-review").contains(reviewPackageStatus)) { + throw new IllegalStateException("Unsupported review package status for " + artifact + + ": " + reviewPackageStatus); + } + if (complete) { + throw new IllegalStateException("Runtime IR optimizer review packages must remain incomplete/manual-review-only for " + + artifact); + } + if (!required && !"not-required".equals(reviewPackageStatus)) { + throw new IllegalStateException("Non-required review package must use not-required status for " + artifact + + ": status=" + reviewPackageStatus); + } + if (required && !"pending-manual-review".equals(reviewPackageStatus)) { + throw new IllegalStateException("Required review package must stay pending-manual-review for " + artifact + + ": status=" + reviewPackageStatus); + } + if (required && "none".equals(firstBlocker)) { + throw new IllegalStateException("Required review package must expose a first blocker for " + artifact); + } + if (!required && !"review-package-not-required".equals(firstBlocker)) { + throw new IllegalStateException("Non-required review package must expose review-package-not-required for " + + artifact + ": " + firstBlocker); + } + if (pendingApprovalCount > proposalPassCount) { + throw new IllegalStateException("Pending approval count exceeds proposal pass count for " + artifact + + ": pending=" + pendingApprovalCount + ", proposalPasses=" + proposalPassCount); + } + } + + private static String requirePresent(Path artifact, Properties properties, String key) { + String value = properties.getProperty(key); + if (value == null || value.isBlank()) { + throw new IllegalStateException("Missing runtime IR optimizer evidence key for " + artifact + ": " + key); + } + return value; + } + + private static void requireValue(Path artifact, Properties properties, String key, String expected) { + String actual = requirePresent(artifact, properties, key); + if (!expected.equals(actual)) { + throw new IllegalStateException("Unexpected runtime IR optimizer evidence value for " + + artifact + ": " + key + "=" + actual + ", expected=" + expected); + } + } + + private static boolean parseBoolean(String value) { + return "true".equalsIgnoreCase(value); + } + + private static int parseNonNegativeInt(Path artifact, Properties properties, String key) { + String value = requirePresent(artifact, properties, key); + try { + int parsed = Integer.parseInt(value); + if (parsed < 0) { + throw new NumberFormatException("negative"); + } + return parsed; + } catch (NumberFormatException exception) { + throw new IllegalStateException("Invalid non-negative integer runtime IR optimizer evidence key for " + + artifact + ": " + key + "=" + value, exception); + } + } +} diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCliTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCliTest.java new file mode 100644 index 00000000..c3e0e7bd --- /dev/null +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCliTest.java @@ -0,0 +1,118 @@ +package net.sixik.ga_utils.javatogpu.runtime.opencl; + +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileArtifactDumper; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class OpenClRuntimeIrOptimizerEvidenceValidatorCliTest { + + @TempDir + Path temporaryDirectory; + + @Test + void acceptsFailClosedReviewPackageEvidenceFile() throws Exception { + Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + Files.writeString(artifact, failClosedEvidence("pending-manual-review", "true", + "preview-readiness-blocked-by-proof", "1", "1")); + + assertDoesNotThrow(() -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main( + new String[]{artifact.toString()} + )); + } + + @Test + void acceptsArtifactDirectory() throws Exception { + Path artifactDirectory = temporaryDirectory.resolve("runtime-compile-artifacts").resolve("kernel-a"); + Files.createDirectories(artifactDirectory); + Files.writeString( + artifactDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT), + failClosedEvidence("not-required", "false", "review-package-not-required", "0", "0") + ); + + assertDoesNotThrow(() -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main( + new String[]{temporaryDirectory.toString()} + )); + } + + @Test + void rejectsMissingArtifact() { + assertThrows( + IllegalStateException.class, + () -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main( + new String[]{temporaryDirectory.resolve("missing").toString()} + ) + ); + } + + @Test + void rejectsProductionMutationGuardrailRegression() throws Exception { + Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + Files.writeString(artifact, failClosedEvidence("pending-manual-review", "true", + "manual-review-required", "1", "0") + .replace("reviewPackage.productionMutation=disabled", "reviewPackage.productionMutation=enabled")); + + assertThrows( + IllegalStateException.class, + () -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main(new String[]{artifact.toString()}) + ); + } + + @Test + void rejectsCompletedReviewPackage() throws Exception { + Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + Files.writeString(artifact, failClosedEvidence("pending-manual-review", "true", + "manual-review-required", "1", "0") + .replace("reviewPackage.complete=false", "reviewPackage.complete=true")); + + assertThrows( + IllegalStateException.class, + () -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main(new String[]{artifact.toString()}) + ); + } + + @Test + void rejectsRequiredPackageWithoutBlocker() throws Exception { + Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + Files.writeString(artifact, failClosedEvidence("pending-manual-review", "true", "none", "1", "0")); + + assertThrows( + IllegalStateException.class, + () -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main(new String[]{artifact.toString()}) + ); + } + + private static String failClosedEvidence( + String status, + String required, + String firstBlocker, + String proposalPassCount, + String pendingApprovalCount + ) { + return String.join("\n", + "status=recorded", + "runtimeEquivalenceReview.productionMutation=disabled", + "runtimeEquivalenceReview.selectedIrReplacement=disabled", + "runtimeEquivalenceReview.manualReviewOnly=true", + "reviewPackage.status=" + status, + "reviewPackage.required=" + required, + "reviewPackage.complete=false", + "reviewPackage.firstBlocker=" + firstBlocker, + "reviewPackage.proposalPass.count=" + proposalPassCount, + "reviewPackage.pendingApproval.count=" + pendingApprovalCount, + "reviewPackage.runtimeEquivalence.status=blocked", + "reviewPackage.originalIrRequired=true", + "reviewPackage.optimizedIrRequired=true", + "reviewPackage.proofSummaryRequired=true", + "reviewPackage.manualReviewOnly=true", + "reviewPackage.productionMutation=disabled", + "reviewPackage.selectedIrReplacement=disabled", + "" + ); + } +} From 71c2d4f22a84e11a82fd83887e95e4a2fa78c50a Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Sun, 12 Jul 2026 21:30:35 +0300 Subject: [PATCH 20/32] wip: read-only peephole plan validation telemetry propagation --- ROADMAP.md | 7 +- docs/IR-Optimizer.md | 2 + docs/Runtime-Guide.md | 2 + ...dSourcePromotionWorkloadGateFormatter.java | 96 +++ ...BackendSourcePromotionWorkloadSummary.java | 305 ++++++++++ ...ctionPromotionExplainabilityFormatter.java | 189 ++++++ ...ductionPromotionExplainabilitySummary.java | 79 +++ .../runtime/GpuRuntimeClampPeepholeRule.java | 127 ++++ .../runtime/GpuRuntimeDotPeepholeRule.java | 191 ++++++ .../runtime/GpuRuntimeIrPeepholePass.java | 127 +++- .../GpuRuntimeIrPeepholeReplacementPlan.java | 108 ++++ ...meIrPeepholeReplacementPlanValidation.java | 117 ++++ .../GpuRuntimeIrPeepholeRuleContext.java | 24 +- .../GpuRuntimeIrPeepholeRuleRegistry.java | 53 +- .../GpuRuntimeIrPeepholeRuleReport.java | 75 +++ .../runtime/GpuRuntimeIrTypedNodeGraph.java | 172 ++++++ .../runtime/GpuRuntimeMadFmaPeepholeRule.java | 95 ++- .../runtime/GpuRuntimeMixPeepholeRule.java | 191 ++++++ .../GpuRuntimeOptimizerDriftArtifact.java | 339 +++++++++++ .../runtime/GpuRuntimeStepPeepholeRule.java | 175 ++++++ .../opencl/OpenClValidationReporter.java | 76 +++ ...rcePromotionWorkloadGateFormatterTest.java | 54 ++ ...endSourcePromotionWorkloadSummaryTest.java | 17 +- .../GpuRuntimeCompileArtifactDumperTest.java | 114 ++++ .../GpuRuntimeIrOptimizerRegistryTest.java | 551 ++++++++++++++++++ .../opencl/OpenClValidationReportTest.java | 8 +- 26 files changed, 3250 insertions(+), 44 deletions(-) create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeClampPeepholeRule.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeDotPeepholeRule.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeReplacementPlan.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeReplacementPlanValidation.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrTypedNodeGraph.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeMixPeepholeRule.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeStepPeepholeRule.java diff --git a/ROADMAP.md b/ROADMAP.md index bda40338..99b78162 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -445,6 +445,7 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i Build proof-backed local expression-tree rewrites such as `a + t * (b - a) -> mix(a, b, t)`, `a * b + c -> mad/fma(a, b, c)`, `min(max(x, lo), hi) -> clamp(x, lo, hi)`, ternary step forms -> `step(edge, x)`, and small fixed-width dot-product patterns. Mutation must stay fail-closed until structural replacement, typed-graph validation, runtime-equivalence, rollback, and A1/A2 evidence are stable. - [ ] Add a safe IR pattern-matching and rewrite visitor for peephole passes. The current typed preflight safely detects the initial `mad/fma` candidate shape without editing nodes. Next, add reusable typed-node traversal/replacement APIs that can prove complete-pattern replacement before editing anything. Partial matches must produce diagnostics only. Rule implementations should remain isolated (`mix`, `mad/fma`, `clamp`, `step`, `dot`) and report applied/skipped/blocked counts per rule so optimizer drift artifacts can detect regressions. + `GpuRuntimeIrPeepholeReplacementPlan` now adds the first read-only typed replacement-planning contract. The `mad/fma` rule records root, covered, and input node ids plus complete/partial blocker state for detected `a*b+c` / `c+a*b` shapes, `clamp` records read-only complete plans for `min(max(x, lo), hi)` typed call shapes, `step` records read-only complete plans for simple `x < edge ? 0 : 1` / `x >= edge ? 1 : 0` ternary shapes, `dot` records read-only plans for fixed-width additive multiply trees such as `(a0*b0)+(a1*b1)`, and `mix` records read-only complete plans for `a + t * (b - a)` / `t * (b - a) + a`. The pass still reports `rewrite-engine-not-implemented` and never mutates selected IR. All current peephole families now have diagnostic matchers; the remaining work is shared traversal/rewrite mechanics and proof gates, not family visibility. - [x] Add Java-source fast-math policy metadata for runtime optimizer decisions. `@GPUOptimize(fastMath = false)` is now the production-safe default policy surface. Java-source frontend artifacts persist the explicit annotation source or `default-strict`, runtime optimization requests expose the resolved policy, and optimizer hooks can declare `requiresFastMath()` so the registry skips them without invocation unless the method explicitly opts in. This does not enable production mutation by itself. - [x] Preserve fast-math policy through the ASM frontend. @@ -454,7 +455,7 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i - [ ] Add proof, runtime-equivalence, and rollback gates for peephole rewrites. Peephole rewrites must emit `GpuRuntimeIrOptimizationProofArtifact` fields with optimizer family (`peephole`, `instcombine`, or per-rule families), rule counters, fast-math policy, original/transformed IR identities, and compact/durable runtime-equivalence payload evidence. Failed proofs, incomplete pattern rewrites, runtime-equivalence failures, or rollback diagnostics must keep the original IR selected. - [ ] Surface peephole telemetry in optimizer drift and promotion artifacts. - `runtime-optimizer-drift.properties`, workload gates, validation history, and production-promotion explainability should show applied/skipped/blocked counts for `mix`, `mad/fma`, `clamp`, `step`, and `dot`, plus first blocker diagnostics. Production promotion must remain disabled until these rule families have stable A1/A2 runtime evidence and backend/vendor confidence. + `runtime-optimizer-drift.properties` now surfaces read-only replacement-plan evidence with aggregate complete/partial counts, first incomplete-plan blocker, and compact `optimizerRule.*` summaries from the existing `rule.N.*` proof fields, so CI can detect `mad/fma`, `clamp`, `step`, `dot`, and `mix` planning drift before any rewrite engine exists. Workload gates, compact workload summaries, OpenCL validation Markdown/history, I3 summaries, and production-promotion explainability now carry those replacement-plan counters and per-rule summaries without affecting readiness or production decisions. Production promotion must remain disabled until these rule families have stable A1/A2 runtime evidence and backend/vendor confidence. #### I3.5 Vendor/device-specific optimization strategy @@ -566,7 +567,7 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i - [x] Gate production optimizer profile behind A1/A2. Runtime compile artifacts now include a fail-closed `production-optimizer-gate.properties` diagnostic artifact plus `runtime-production-mutation-safety.properties`, which makes the user/CI-facing answer explicit: runtime IR can participate in diagnostics and backend handoff tracking while production mutation remains disabled unless the production gate is accepted and a transformed optimized IR is selected. Runtime IR selection now also enforces that contract directly: production-like profiles such as `production`, `vendor-tuned`, and `runtime-tuned` select the original IR when the production IR acceptance gate is blocked, report `fallbackDecision=production-ir-gate-blocked`, and mark the optimized IR as rejected instead of letting diagnostic optimized IR reach production lowering. A production-enabled fixture covers the opposite path: when runtime equivalence passes, fallback evidence is clean, the strategy is evidence-backed/non-advisory, the vendor baseline is promotion-eligible, at least one accepted optimizer proof artifact is attached, production source switching is enabled, and `productionPromotion.decisionMode=production-enabled`, the selected runtime IR remains optimized and the artifacts report `productionMutationEnabled=true`. Real workload promotion gates and OpenCL validation report/history surface per-kernel production mutation state beside the selected runtime IR stage, so diagnostic handoff cannot be mistaken for enabled production mutation. Current NVIDIA/AMD/Intel strategies therefore stay diagnostic-only until A1/A2 evidence is strong enough for promotion. - [x] Add CI artifact checks for runtime optimizer drift. - Runtime artifact dumps now include `runtime-optimizer-drift.properties`, a compact CI-facing summary with optimizer pass counts, applied/skipped/rolled-back/failed counts, proof-artifact accepted/blocking counts, optimizer-family promotion-readiness summaries, fallback decision, selected runtime IR stage/identity, optimized-IR rejection state, selected strategy/profile, vendor baseline status, promotion eligibility, and production gate status so validation history can detect optimizer behavior drift across runs. Optimizer families now report compact `optimizerFamily.*` evidence (`count`, `promotionReady.count`, and per-family pass/proof/rollback/failure summary), allowing A1/A2 promotion review to distinguish accepted proof candidates from blocked or rolled-back families without enabling production mutation. Real workload promotion gates now also carry per-kernel `runtimeIrHandoff.*` and `runtimeOptimizerDrift.*` proof counters; OpenCL validation report/history and the vendor workflow summary surface the selected runtime IR stage (`original` / `optimized`) plus accepted/blocking proof and optimizer-family readiness evidence beside source-switching evidence for each workload kernel. + Runtime artifact dumps now include `runtime-optimizer-drift.properties`, a compact CI-facing summary with optimizer pass counts, applied/skipped/rolled-back/failed counts, proof-artifact accepted/blocking counts, read-only replacement-plan complete/partial counts, first replacement-plan blocker, compact optimizer-rule summaries, optimizer-family promotion-readiness summaries, fallback decision, selected runtime IR stage/identity, optimized-IR rejection state, selected strategy/profile, vendor baseline status, promotion eligibility, and production gate status so validation history can detect optimizer behavior drift across runs. Optimizer families now report compact `optimizerFamily.*` evidence (`count`, `promotionReady.count`, and per-family pass/proof/rollback/failure summary), allowing A1/A2 promotion review to distinguish accepted proof candidates from blocked or rolled-back families without enabling production mutation. Real workload promotion gates now also carry per-kernel `runtimeIrHandoff.*` and `runtimeOptimizerDrift.*` proof, replacement-plan, and optimizer-rule counters; OpenCL validation report/history, I3 summary, production-promotion explainability, and the vendor workflow summary surface the selected runtime IR stage (`original` / `optimized`) plus accepted/blocking proof, read-only replacement-plan completeness, optimizer-rule evidence, and optimizer-family readiness evidence beside source-switching evidence for each workload kernel. - [x] Add an I3 readiness summary artifact. Runtime dumps now include `i3-readiness-summary.properties`, which aggregates backend source reconstruction/parity, source-ready state, runtime equivalence, source-promotion gate status, runtime IR handoff, production optimizer gate status, production mutation safety, and blockers into one compact CI-facing artifact. OpenCL validation reports also write `i3-readiness-workload-summary.properties`, a workload-level CI summary that counts `review-ready` / `blocked` / `production-enabled` kernels, records aggregate `sourceReady.count` / `sourceReady.all`, and keeps per-kernel runtime IR stage, source-ready state, source-promotion status, optimizer gate status, production-mutation flag, and first diagnostic for drift tracking across vendor runners. - [x] Add I3 readiness workload baseline comparison tasks. @@ -597,7 +598,7 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i - [x] Allow third-party validators to extend the existing IR Validator model. `GpuIrValidationProvider` remains a ServiceLoader SPI, while `GpuIrValidationReportEntry` now carries provider alias, extension id/version, rule id, severity, source anchor, method identity, entry-point state, and arbitrary provider fields. `GpuIrValidationRunner` automatically enriches legacy four-field entries with the actual provider metadata and Java/ASM source anchor, so downstream validators do not need to duplicate compiler location logic. `GpuCompilerProcessor` persists the enriched fields in the existing validation properties artifact without breaking the established provider alias contract. The built-in optional `ir-validation` provider now exposes stable id `javatogpu.ir-validation`, version `1`, aggregate rule id, and derived INFO/WARNING/ERROR severity. - [x] Allow third-party optimizer and peephole passes without core forks. - External libraries can contribute full `GpuRuntimeIrOptimizationPass` implementations or typed-body `GpuRuntimeIrPeepholeRule` implementations through ServiceLoader. `GpuRuntimeIrPeepholeRuleRegistry` provides deterministic ordering, duplicate rule-id and extension-id rejection, phase/capability/permission validation, advisory failure isolation, and production-profile fail-closed behavior. Rule reports preserve rule id/version, extension id/version, method, candidate/proposal counts, proof status, diagnostics, and custom fields. The built-in MAD/FMA detector now uses the same public rule SPI. The current pass remains diagnostic/proposal-only: rules cannot bypass fast-math policy, proof requirements, production authorization, rollback, runtime-equivalence, or final IR selection gates. + External libraries can contribute full `GpuRuntimeIrOptimizationPass` implementations or typed-body `GpuRuntimeIrPeepholeRule` implementations through ServiceLoader. `GpuRuntimeIrPeepholeRuleRegistry` provides deterministic ordering, duplicate rule-id and extension-id rejection, phase/capability/permission validation, advisory failure isolation, and production-profile fail-closed behavior. Rule reports preserve rule id/version, extension id/version, method, candidate/proposal counts, proof status, diagnostics, and custom fields. The built-in matcher set now shares `GpuRuntimeIrTypedNodeGraph` for read-only typed-node traversal and emits `GpuRuntimeIrPeepholeReplacementPlanValidation` evidence so future structural rewrites have a fail-closed graph/plan boundary before any rewrite builder exists. Drift artifacts, workload gates, OpenCL validation reports/history, and production-promotion explainability now surface plan-validation total/valid/invalid counts and first blockers as evidence-only telemetry. The current pass remains diagnostic/proposal-only: rules cannot bypass fast-math policy, proof requirements, production authorization, rollback, runtime-equivalence, or final IR selection gates. - [x] Add optional approval-bound IR optimizer modules. `ir-optimizer` and `ir-vendor-optimizer` now exist as optional modules that keep `GpuIrValidationProvider` strictly read-only and expose optimization only through separate `MUTATION_PROPOSAL` provider contracts. The backend-neutral runtime bridge records proposal/provider evidence without loading vendor providers by default, while vendor-specific providers must opt into `GpuIrVendorOptimizationProposalProvider` / `GpuIrVendorOptimizationProposalRegistry`. Optimizer proposal evidence now includes approval-template status: real immutable rewrites can emit pending templates under `META-INF/javatogpu/ir-optimization-approvals/`, while preview/no-change proposals record `not-applicable` blockers. Approval artifacts are bound to optimizer id/version, original and optimized `IrGpu` identities, backend target, optimization profile, proof verdict, and rollback requirement; they remain manual-review-only and do not authorize production mutation or selected-IR replacement by themselves. Runtime OpenCL validation now surfaces this evidence through `runtime-ir-optimizer-evidence.properties` and the `Runtime IR Optimizer Evidence` report section, including approval-template pending/not-applicable counts. - [x] Add the third-party backend/device policy foundation. diff --git a/docs/IR-Optimizer.md b/docs/IR-Optimizer.md index 0b0ee899..e3eee3fe 100644 --- a/docs/IR-Optimizer.md +++ b/docs/IR-Optimizer.md @@ -68,6 +68,8 @@ The default bridge is intentionally proposal-only. It can surface canonicalizati Runtime artifact dumping records bridge/provider evidence in `runtime-ir-optimizer-evidence.properties`. The artifact is filtered by the stable `javatogpu.ir-optimizer` provider prefix and includes pass counts, outcomes, proposal-only/selected counts, original/transformed IR identities, proof artifact fields, and diagnostics. This is an evidence surface only; it does not authorize optimized IR selection or production mutation by itself. +Runtime peephole/InstCombine proof is also kept read-only. `GpuRuntimeIrPeepholeReplacementPlan` records future structural replacement intent, including root node, covered node ids, input node ids, replacement kind, completeness, and first blocker. `GpuRuntimeIrTypedNodeGraph` is the shared read-only traversal view for typed peephole rules, and `GpuRuntimeIrPeepholeReplacementPlanValidation` checks each emitted plan against that graph before it is surfaced. Validation fields such as `replacementPlan.validation.invalid.count` and `rule.N.replacementPlan.validation.firstBlocker` fail closed on missing roots, uncovered roots, or missing covered/input nodes; they still do not build replacement nodes or authorize mutation. The current matcher set covers `mad/fma` for `a*b+c` / `c+a*b`, `clamp` for `min(max(x, lo), hi)`, `step` for simple `x < edge ? 0 : 1` / `x >= edge ? 1 : 0` ternaries, `dot` for additive multiply trees such as `(a0*b0)+(a1*b1)`, and `mix` for `a + t * (b - a)` / `t * (b - a) + a`. `runtime-optimizer-drift.properties` aggregates complete/partial replacement-plan counts, structural plan-validation total/valid/invalid counts, first incomplete/invalid blockers, and compact `optimizerRule.*` summaries for CI drift detection. Workload gates, validation reports/history, I3 summaries, and production-promotion explainability carry those counters forward as evidence-only telemetry. The pass still reports `rewrite-engine-not-implemented` and never mutates the selected IR. + The same evidence artifact now records `approvalTemplate.*` fields for each optimizer pass. Real proposal rewrites with distinct original/optimized identities and accepted proof metadata are marked `pending`; preview and no-change proposals are marked `not-applicable` with a first blocker such as `proposal-decision-not-proposed`. ## Vendor Optimizer SPI diff --git a/docs/Runtime-Guide.md b/docs/Runtime-Guide.md index 457887bc..90b8e087 100644 --- a/docs/Runtime-Guide.md +++ b/docs/Runtime-Guide.md @@ -235,6 +235,8 @@ Non-analysis optimizer passes with runtime-equivalence payload evidence also pro When the optional IR optimizer bridge participates, each kernel directory may also receive `runtime-ir-optimizer-evidence.properties`. The artifact records only proposal/provider evidence from stable `javatogpu.ir-optimizer` sources: pass counts, proposal-only versus selected optimized counts, rollback counts, IR identities, proof fields, approval-template applicability, and diagnostics. Constant-folding preview proof is also aggregated into `constantFoldingPreview.*` fields for pass count, candidate count, skipped blockers, required runtime-equivalence/approval gates, and unresolved integer-overflow / floating-point-rounding safety flags. Safe-local-CSE preview proof is aggregated into `safeLocalCsePreview.*` fields for pass count, expression/candidate/duplicate/equivalence-class counts, unsupported-operator / impure-operand / control-flow-boundary blockers, required runtime-equivalence/approval gates, and unresolved dominance / side-effect-freedom proof flags. Typed dead-code preview proof is aggregated into `typedDeadCodePreview.*` fields for pass count, typed/reachable/unreachable node counts, missing-root / missing-child-reference / side-effecting-unreachable blockers, required runtime-equivalence/approval gates, and unresolved side-effect-freedom proof flags. The artifact also records `previewReadiness.*` fields with aggregate status, recorded/candidate/blocked family counts, and a per-family status summary so CI can distinguish no-candidate previews from proof-blocked previews and review-ready runtime-equivalence candidates. `runtimeEquivalenceReview.*` records the next fail-closed review boundary: status, eligibility, requirement, first blocker, family summary, disabled production mutation, disabled selected-IR replacement, and manual-review-only state. `reviewPackage.*` records the manual-review package boundary above that review gate, including package status, requirement/completeness, first blocker, proposal-pass count, pending approval count, original/optimized IR and proof-summary requirements, manual-review-only state, and disabled production mutation / selected-IR replacement. `openClValidationReport` aggregates this into `Runtime IR Optimizer Evidence` for CI visibility, including constant-folding, safe-local-CSE, typed dead-code preview totals, preview-readiness status, runtime-equivalence review eligibility, and review-package status. `validateOpenClRuntimeIrOptimizerEvidence` then validates the raw runtime evidence files and fails CI if those fail-closed guardrails are missing, completed early, or production-enabling. The section and validator are evidence-only and do not participate in history, source switching, selected-IR replacement, or production mutation gates. +Runtime peephole/InstCombine remains diagnostic-only. `GpuRuntimeIrPeepholeReplacementPlan` records read-only typed replacement plans for current rule families: `mad/fma`, `clamp`, `step`, `dot`, and `mix`. Built-in rules now use `GpuRuntimeIrTypedNodeGraph` as the shared read-only typed-node traversal surface, and each emitted plan is checked by `GpuRuntimeIrPeepholeReplacementPlanValidation` before proof fields are exported. Proof fields expose the candidate root node, covered nodes, input nodes, completeness, first blocker, and structural validation counters such as `replacementPlan.validation.invalid.count` / `rule.N.replacementPlan.validation.firstBlocker`. `runtime-optimizer-drift.properties` aggregates the complete/partial replacement-plan counts, plan-validation total/valid/invalid counts, first incomplete/invalid blockers, and compact `optimizerRule.*` summaries from existing `rule.N.*` proof fields so CI can detect planning drift while the rewrite engine remains absent. Workload gates, compact workload summaries, OpenCL validation Markdown/history, I3 summaries, and production-promotion explainability surface the same counters as evidence-only telemetry. These plans describe what a future structural rewrite would need to replace; they do not edit typed bodies, select optimized IR, or enable production mutation. + The same index records `familyBinding.*`. Binding remains `not-bound` unless exactly one optimizer family has passed runtime evidence whose comparison mode is explicitly family-specific (`optimizer-family::...`). Descriptor-versus-reconstructed-source evidence is pipeline validation and is deliberately rejected as optimizer-family proof. Run `./gradlew :processor:openClOptimizerFamilyPayloadFixtureTest` to materialize the CI contract fixture under `processor/build/reports/opencl/optimizer-family-payload-fixture/`. The fixture contains complete CSE and auto-vectorization payload trees plus `fixture-summary.properties`, including one structured raw case per family. It validates artifact layout and upload behavior, not production rewrite readiness. diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatter.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatter.java index 39cbc91c..ff7ff711 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatter.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatter.java @@ -454,6 +454,15 @@ private static void copyRuntimeOptimizerDriftProperties(Properties source, Prope target.setProperty("runtimeOptimizerDrift.proofArtifact.count", "0"); target.setProperty("runtimeOptimizerDrift.proofArtifact.accepted.count", "0"); target.setProperty("runtimeOptimizerDrift.proofArtifact.blocking.count", "0"); + target.setProperty("runtimeOptimizerDrift.replacementPlan.complete.count", "0"); + target.setProperty("runtimeOptimizerDrift.replacementPlan.partial.count", "0"); + target.setProperty("runtimeOptimizerDrift.replacementPlan.firstBlocker", "none"); + target.setProperty("runtimeOptimizerDrift.replacementPlan.validation.count", "0"); + target.setProperty("runtimeOptimizerDrift.replacementPlan.validation.valid.count", "0"); + target.setProperty("runtimeOptimizerDrift.replacementPlan.validation.invalid.count", "0"); + target.setProperty("runtimeOptimizerDrift.replacementPlan.validation.firstBlocker", "none"); + target.setProperty("runtimeOptimizerDrift.optimizerRule.count", "0"); + target.setProperty("runtimeOptimizerDrift.optimizerRule.summary", "none"); target.setProperty("runtimeOptimizerDrift.fallbackDecision", target.getProperty("runtimeIrHandoff.fallbackDecision", "none")); target.setProperty("runtimeOptimizerDrift.selectedRuntimeIrStage", target.getProperty("runtimeIrHandoff.selectedStage", "original")); target.setProperty("runtimeOptimizerDrift.selectedRuntimeIrIdentity", target.getProperty("runtimeIrHandoff.selected.identity", "unknown")); @@ -474,6 +483,16 @@ private static void copyRuntimeOptimizerDriftProperties(Properties source, Prope copyRuntimeOptimizerDriftProperty(source, target, "proofArtifact.count"); copyRuntimeOptimizerDriftProperty(source, target, "proofArtifact.accepted.count"); copyRuntimeOptimizerDriftProperty(source, target, "proofArtifact.blocking.count"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementPlan.complete.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementPlan.partial.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementPlan.firstBlocker", "none"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementPlan.validation.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementPlan.validation.valid.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementPlan.validation.invalid.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementPlan.validation.firstBlocker", "none"); + copyRuntimeOptimizerDriftProperty(source, target, "optimizerRule.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "optimizerRule.summary", "none"); + copyIndexedPropertyGroup(source, target, "runtimeOptimizerDrift.optimizerRule", "optimizerRule"); copyRuntimeOptimizerDriftProperty(source, target, "optimizerFamily.count"); copyRuntimeOptimizerDriftProperty(source, target, "optimizerFamily.promotionReady.count"); copyRuntimeOptimizerDriftProperty(source, target, "optimizerFamily.summary"); @@ -494,6 +513,38 @@ private static void copyRuntimeOptimizerDriftProperty(Properties source, Propert target.setProperty("runtimeOptimizerDrift." + key, source.getProperty(key, "unknown")); } + private static void copyRuntimeOptimizerDriftProperty( + Properties source, + Properties target, + String key, + String defaultValue + ) { + target.setProperty("runtimeOptimizerDrift." + key, source.getProperty(key, defaultValue)); + } + + private static void copyIndexedPropertyGroup( + Properties source, + Properties target, + String targetKeyPrefix, + String sourceKeyPrefix + ) { + int count = parsePositiveInt(source.getProperty(sourceKeyPrefix + ".count", "0")); + target.setProperty(targetKeyPrefix + ".count", Integer.toString(count)); + String sourceIndexedPrefix = sourceKeyPrefix + "."; + String targetIndexedPrefix = targetKeyPrefix + "."; + for (String key : source.stringPropertyNames().stream().sorted().toList()) { + if (!key.startsWith(sourceIndexedPrefix)) { + continue; + } + String suffix = key.substring(sourceIndexedPrefix.length()); + int index = indexedPropertyIndex(suffix); + if (index < 0 || index >= count) { + continue; + } + target.setProperty(targetIndexedPrefix + suffix, source.getProperty(key, "unknown")); + } + } + private static void copyOptimizerFamilyEquivalencePayloadProperties(Properties source, Properties target) { if (source == null || source.isEmpty()) { target.setProperty("optimizerFamilyPayload.status", "not-recorded"); @@ -769,6 +820,16 @@ private static void appendRuntimeOptimizerDrift(StringBuilder builder, String pr builder.append(prefix).append("runtimeOptimizerDrift.proofArtifact.count=").append(entry.getProperty("runtimeOptimizerDrift.proofArtifact.count", "0")).append('\n'); builder.append(prefix).append("runtimeOptimizerDrift.proofArtifact.accepted.count=").append(entry.getProperty("runtimeOptimizerDrift.proofArtifact.accepted.count", "0")).append('\n'); builder.append(prefix).append("runtimeOptimizerDrift.proofArtifact.blocking.count=").append(entry.getProperty("runtimeOptimizerDrift.proofArtifact.blocking.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementPlan.complete.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementPlan.complete.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementPlan.partial.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementPlan.partial.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementPlan.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.replacementPlan.firstBlocker", "none")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementPlan.validation.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementPlan.validation.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementPlan.validation.valid.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementPlan.validation.valid.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementPlan.validation.invalid.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementPlan.validation.invalid.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementPlan.validation.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.replacementPlan.validation.firstBlocker", "none")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.optimizerRule.count=").append(entry.getProperty("runtimeOptimizerDrift.optimizerRule.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.optimizerRule.summary=").append(entry.getProperty("runtimeOptimizerDrift.optimizerRule.summary", "none")).append('\n'); + appendIndexedPropertyGroup(builder, prefix, entry, "runtimeOptimizerDrift.optimizerRule"); builder.append(prefix).append("runtimeOptimizerDrift.optimizerFamily.count=").append(entry.getProperty("runtimeOptimizerDrift.optimizerFamily.count", "0")).append('\n'); builder.append(prefix).append("runtimeOptimizerDrift.optimizerFamily.promotionReady.count=").append(entry.getProperty("runtimeOptimizerDrift.optimizerFamily.promotionReady.count", "0")).append('\n'); builder.append(prefix).append("runtimeOptimizerDrift.optimizerFamily.summary=").append(entry.getProperty("runtimeOptimizerDrift.optimizerFamily.summary", "none")).append('\n'); @@ -887,6 +948,41 @@ private static void appendRuntimeExtensionParticipation(StringBuilder builder, S } } + private static void appendIndexedPropertyGroup( + StringBuilder builder, + String kernelPrefix, + Properties entry, + String keyPrefix + ) { + String indexedPrefix = keyPrefix + "."; + int count = parsePositiveInt(entry.getProperty(keyPrefix + ".count", "0")); + for (String key : entry.stringPropertyNames().stream().sorted().toList()) { + if (!key.startsWith(indexedPrefix)) { + continue; + } + String suffix = key.substring(indexedPrefix.length()); + int index = indexedPropertyIndex(suffix); + if (index < 0 || index >= count) { + continue; + } + builder.append(kernelPrefix).append(key).append('=').append(entry.getProperty(key, "unknown")).append('\n'); + } + } + + private static int indexedPropertyIndex(String suffix) { + if (suffix == null || suffix.isBlank() || !Character.isDigit(suffix.charAt(0))) { + return -1; + } + int end = 0; + while (end < suffix.length() && Character.isDigit(suffix.charAt(end))) { + end++; + } + if (end == suffix.length() || suffix.charAt(end) != '.') { + return -1; + } + return parsePositiveInt(suffix.substring(0, end)); + } + private static void appendIndexedProperties( StringBuilder builder, String kernelPrefix, diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummary.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummary.java index 5f3c7c6e..51edc5e1 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummary.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummary.java @@ -24,6 +24,16 @@ public record GpuBackendSourcePromotionWorkloadSummary( int optimizerProofArtifactCount, int optimizerAcceptedProofArtifactCount, int optimizerBlockingProofArtifactCount, + int optimizerReplacementPlanCompleteCount, + int optimizerReplacementPlanPartialCount, + String optimizerReplacementPlanFirstBlockers, + int optimizerReplacementPlanValidationCount, + int optimizerReplacementPlanValidationValidCount, + int optimizerReplacementPlanValidationInvalidCount, + String optimizerReplacementPlanValidationFirstBlockers, + int optimizerRuleCount, + String optimizerRuleSummary, + String optimizerRuleDetails, int optimizerFamilyCount, int optimizerFamilyPromotionReadyCount, String optimizerFamilySummary, @@ -57,6 +67,16 @@ public static GpuBackendSourcePromotionWorkloadSummary notRecorded() { 0, 0, 0, + "", + 0, + 0, + 0, + "", + 0, + "none", + "none", + 0, + 0, "none", 0, "false", @@ -102,6 +122,16 @@ public static GpuBackendSourcePromotionWorkloadSummary fromProperties(Properties sumKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.proofArtifact.count"), sumKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.proofArtifact.accepted.count"), sumKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.proofArtifact.blocking.count"), + sumKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.replacementPlan.complete.count"), + sumKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.replacementPlan.partial.count"), + summarizeReplacementPlanFirstBlockers(properties, kernelCount), + sumKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.replacementPlan.validation.count"), + sumKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.replacementPlan.validation.valid.count"), + sumKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.replacementPlan.validation.invalid.count"), + summarizeKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.replacementPlan.validation.firstBlocker"), + sumKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.optimizerRule.count"), + summarizeKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.optimizerRule.summary"), + summarizeOptimizerRules(properties, kernelCount), sumKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.optimizerFamily.count"), sumKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.optimizerFamily.promotionReady.count"), summarizeOptimizerFamilies(properties, kernelCount), @@ -149,6 +179,8 @@ public String historyStatus() { + optimizerFamilyPayloadCompleteCount + ", optimizerPayloadCompleteAll=" + optimizerFamilyPayloadCompleteAll + + optimizerReplacementPlanEvidenceText() + + optimizerRuleSummaryText() + optimizerFamilySummaryText() + runtimeExtensionParticipationEvidenceText() + sourceSwitchingEvidenceText() @@ -176,6 +208,8 @@ public String historyStatus() { + optimizerFamilyPayloadCompleteCount + ", optimizerPayloadCompleteAll=" + optimizerFamilyPayloadCompleteAll + + optimizerReplacementPlanEvidenceText() + + optimizerRuleSummaryText() + optimizerFamilySummaryText() + runtimeExtensionParticipationEvidenceText() + sourceSwitchingEvidenceText() @@ -228,6 +262,83 @@ private String optimizerFamilySummaryText() { : ", optimizerFamilySummary=" + optimizerFamilySummary; } + private String optimizerRuleSummaryText() { + if (optimizerRuleCount == 0 + && (optimizerRuleSummary == null || optimizerRuleSummary.isBlank() || "none".equals(optimizerRuleSummary)) + && (optimizerRuleDetails == null || optimizerRuleDetails.isBlank() || "none".equals(optimizerRuleDetails))) { + return ""; + } + StringBuilder builder = new StringBuilder(", optimizerRules=").append(optimizerRuleCount); + if (optimizerRuleSummary != null && !optimizerRuleSummary.isBlank() && !"none".equals(optimizerRuleSummary)) { + builder.append(", optimizerRuleSummary=").append(optimizerRuleSummary); + } + if (optimizerRuleDetails != null && !optimizerRuleDetails.isBlank() && !"none".equals(optimizerRuleDetails)) { + builder.append(", optimizerRuleDetails=").append(optimizerRuleDetails); + } + return builder.toString(); + } + + private String optimizerReplacementPlanEvidenceText() { + if (optimizerReplacementPlanCompleteCount == 0 + && optimizerReplacementPlanPartialCount == 0 + && optimizerReplacementPlanValidationCount == 0 + && optimizerReplacementPlanValidationValidCount == 0 + && optimizerReplacementPlanValidationInvalidCount == 0 + && optimizerReplacementPlanFirstBlockers.isBlank() + && optimizerReplacementPlanValidationFirstBlockers.isBlank()) { + return ""; + } + StringBuilder builder = new StringBuilder(", optimizerReplacementPlans=complete=") + .append(optimizerReplacementPlanCompleteCount) + .append("/partial=") + .append(optimizerReplacementPlanPartialCount); + if (optimizerReplacementPlanValidationCount > 0 + || optimizerReplacementPlanValidationValidCount > 0 + || optimizerReplacementPlanValidationInvalidCount > 0 + || !optimizerReplacementPlanValidationFirstBlockers.isBlank()) { + builder.append("/validation=valid=") + .append(optimizerReplacementPlanValidationValidCount) + .append("/total=") + .append(optimizerReplacementPlanValidationCount) + .append("/invalid=") + .append(optimizerReplacementPlanValidationInvalidCount); + } + if (!optimizerReplacementPlanValidationFirstBlockers.isBlank()) { + builder.append("/validationFirstBlockers=").append(optimizerReplacementPlanValidationFirstBlockers); + } + if (!optimizerReplacementPlanFirstBlockers.isBlank()) { + builder.append("/firstBlockers=").append(optimizerReplacementPlanFirstBlockers); + } + return builder.toString(); + } + + private static String summarizeReplacementPlanFirstBlockers(Properties properties, int kernelCount) { + Map blockerCounts = new LinkedHashMap<>(); + for (int index = 0; index < kernelCount; index++) { + String blocker = properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.replacementPlan.firstBlocker", + "" + ); + if (blocker.isBlank() || "none".equals(blocker) || "unknown".equals(blocker)) { + continue; + } + blockerCounts.merge(blocker, 1, Integer::sum); + } + return summarizeCounts(blockerCounts); + } + + private static String summarizeKernelProperty(Properties properties, int kernelCount, String propertyName) { + Map counts = new LinkedHashMap<>(); + for (int index = 0; index < kernelCount; index++) { + String value = properties.getProperty("kernel." + index + "." + propertyName, ""); + if (value.isBlank() || "none".equals(value) || "unknown".equals(value)) { + continue; + } + counts.merge(value, 1, Integer::sum); + } + return summarizeCounts(counts); + } + private static String summarizeOptimizerFamilies(Properties properties, int kernelCount) { Map families = new LinkedHashMap<>(); for (int index = 0; index < kernelCount; index++) { @@ -449,6 +560,45 @@ private static String summarizeKernelEvidence(Properties properties, int kernelC .append(properties.getProperty("kernel." + index + ".runtimeOptimizerDrift.proofArtifact.accepted.count", "0")) .append("/blockingProof=") .append(properties.getProperty("kernel." + index + ".runtimeOptimizerDrift.proofArtifact.blocking.count", "0")) + .append("/replacementPlanComplete=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.replacementPlan.complete.count", + "0" + )) + .append("/replacementPlanPartial=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.replacementPlan.partial.count", + "0" + )) + .append("/replacementPlanFirstBlocker=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.replacementPlan.firstBlocker", + "none" + )) + .append("/replacementPlanValidationValid=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.replacementPlan.validation.valid.count", + "0" + )) + .append("/replacementPlanValidationTotal=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.replacementPlan.validation.count", + "0" + )) + .append("/replacementPlanValidationInvalid=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.replacementPlan.validation.invalid.count", + "0" + )) + .append("/replacementPlanValidationFirstBlocker=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.replacementPlan.validation.firstBlocker", + "none" + )) + .append("/optimizerRules=") + .append(properties.getProperty("kernel." + index + ".runtimeOptimizerDrift.optimizerRule.count", "0")) + .append("/optimizerRuleDetails=") + .append(summarizeKernelOptimizerRules(properties, index)) .append("/optimizerFamilies=") .append(properties.getProperty("kernel." + index + ".runtimeOptimizerDrift.optimizerFamily.count", "0")) .append("/promotionReadyFamilies=") @@ -561,6 +711,92 @@ private static String allKernelBooleanProperty(Properties properties, int kernel return Boolean.toString(kernelCount > 0 && countKernelBooleanProperty(properties, kernelCount, propertyName) == kernelCount); } + private static String summarizeOptimizerRules(Properties properties, int kernelCount) { + Map rules = new LinkedHashMap<>(); + for (int kernelIndex = 0; kernelIndex < kernelCount; kernelIndex++) { + collectKernelOptimizerRules(properties, kernelIndex, rules); + } + return formatOptimizerRules(rules); + } + + private static String summarizeKernelOptimizerRules(Properties properties, int kernelIndex) { + Map rules = new LinkedHashMap<>(); + collectKernelOptimizerRules(properties, kernelIndex, rules); + String summary = formatOptimizerRules(rules); + return summary.isBlank() ? "none" : summary; + } + + private static void collectKernelOptimizerRules( + Properties properties, + int kernelIndex, + Map rules + ) { + String basePrefix = "kernel." + kernelIndex + ".runtimeOptimizerDrift.optimizerRule."; + int ruleCount = parsePositiveInt(properties.getProperty(basePrefix + "count", "0")); + for (int ruleIndex = 0; ruleIndex < ruleCount; ruleIndex++) { + String prefix = basePrefix + ruleIndex + "."; + String id = properties.getProperty(prefix + "id", ""); + if (id.isBlank()) { + continue; + } + OptimizerRuleAggregate existing = rules.getOrDefault(id, OptimizerRuleAggregate.empty(id)); + rules.put(id, existing.add( + parsePositiveInt(properties.getProperty(prefix + "candidate.count", "0")), + parsePositiveInt(properties.getProperty(prefix + "proposal.count", "0")), + parsePositiveInt(properties.getProperty(prefix + "applied.count", "0")), + parsePositiveInt(properties.getProperty(prefix + "skipped.count", "0")), + parsePositiveInt(properties.getProperty(prefix + "blocked.count", "0")), + parsePositiveInt(properties.getProperty(prefix + "replacementPlan.count", "0")), + parsePositiveInt(properties.getProperty(prefix + "replacementPlan.complete.count", "0")), + parsePositiveInt(properties.getProperty(prefix + "replacementPlan.partial.count", "0")), + parsePositiveInt(properties.getProperty(prefix + "replacementPlan.validation.count", "0")), + parsePositiveInt(properties.getProperty(prefix + "replacementPlan.validation.valid.count", "0")), + parsePositiveInt(properties.getProperty(prefix + "replacementPlan.validation.invalid.count", "0")), + properties.getProperty(prefix + "replacementPlan.validation.firstBlocker", "none"), + properties.getProperty(prefix + "firstBlocker", properties.getProperty(prefix + "replacementPlan.firstBlocker", "none")) + )); + } + } + + private static String formatOptimizerRules(Map rules) { + if (rules.isEmpty()) { + return "none"; + } + StringBuilder builder = new StringBuilder(); + for (OptimizerRuleAggregate rule : rules.values()) { + if (!builder.isEmpty()) { + builder.append(", "); + } + builder.append(rule.id()) + .append("[candidates=") + .append(rule.candidateCount()) + .append(", proposals=") + .append(rule.proposalCount()) + .append(", applied=") + .append(rule.appliedCount()) + .append(", skipped=") + .append(rule.skippedCount()) + .append(", blocked=") + .append(rule.blockedCount()) + .append(", replacementPlans=") + .append(rule.replacementPlanCount()) + .append(", completePlans=") + .append(rule.replacementPlanCompleteCount()) + .append(", partialPlans=") + .append(rule.replacementPlanPartialCount()) + .append(", planValidations=") + .append(rule.replacementPlanValidationCount()) + .append(", invalidPlanValidations=") + .append(rule.replacementPlanValidationInvalidCount()) + .append(", planValidationFirstBlocker=") + .append(rule.replacementPlanValidationFirstBlocker()) + .append(", firstBlocker=") + .append(rule.firstBlocker()) + .append(']'); + } + return builder.toString(); + } + private static String allKernelBooleanPropertyWhenCountPresent( Properties properties, int kernelCount, @@ -625,4 +861,73 @@ private boolean promotionReady() { return acceptedProofCount > 0 && blockingProofCount == 0 && rolledBackCount == 0 && failedCount == 0; } } + + private record OptimizerRuleAggregate( + String id, + int candidateCount, + int proposalCount, + int appliedCount, + int skippedCount, + int blockedCount, + int replacementPlanCount, + int replacementPlanCompleteCount, + int replacementPlanPartialCount, + int replacementPlanValidationCount, + int replacementPlanValidationValidCount, + int replacementPlanValidationInvalidCount, + String replacementPlanValidationFirstBlocker, + String firstBlocker + ) { + + private static OptimizerRuleAggregate empty(String id) { + return new OptimizerRuleAggregate(id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "none", "none"); + } + + private OptimizerRuleAggregate add( + int candidateCount, + int proposalCount, + int appliedCount, + int skippedCount, + int blockedCount, + int replacementPlanCount, + int replacementPlanCompleteCount, + int replacementPlanPartialCount, + int replacementPlanValidationCount, + int replacementPlanValidationValidCount, + int replacementPlanValidationInvalidCount, + String nextValidationBlocker, + String nextBlocker + ) { + String blocker = firstBlocker; + String validationBlocker = replacementPlanValidationFirstBlocker; + if ("none".equals(validationBlocker) + && nextValidationBlocker != null + && !nextValidationBlocker.isBlank() + && !"none".equals(nextValidationBlocker)) { + validationBlocker = nextValidationBlocker; + } + if ("none".equals(blocker) + && nextBlocker != null + && !nextBlocker.isBlank() + && !"none".equals(nextBlocker)) { + blocker = nextBlocker; + } + return new OptimizerRuleAggregate( + id, + this.candidateCount + candidateCount, + this.proposalCount + proposalCount, + this.appliedCount + appliedCount, + this.skippedCount + skippedCount, + this.blockedCount + blockedCount, + this.replacementPlanCount + replacementPlanCount, + this.replacementPlanCompleteCount + replacementPlanCompleteCount, + this.replacementPlanPartialCount + replacementPlanPartialCount, + this.replacementPlanValidationCount + replacementPlanValidationCount, + this.replacementPlanValidationValidCount + replacementPlanValidationValidCount, + this.replacementPlanValidationInvalidCount + replacementPlanValidationInvalidCount, + validationBlocker, + blocker + ); + } + } } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilityFormatter.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilityFormatter.java index e44f8882..143eccdd 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilityFormatter.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilityFormatter.java @@ -179,6 +179,46 @@ public static String format( "optimizerFamily.summary", summarizeKernelProperty(gate, kernelCount, "runtimeOptimizerDrift.optimizerFamily.summary") ); + int optimizerReplacementPlanCompleteCount = parsePositiveInt(gate.getProperty( + "optimizerReplacementPlan.complete.count", + Integer.toString(sumKernelProperty(gate, kernelCount, "runtimeOptimizerDrift.replacementPlan.complete.count")) + )); + int optimizerReplacementPlanPartialCount = parsePositiveInt(gate.getProperty( + "optimizerReplacementPlan.partial.count", + Integer.toString(sumKernelProperty(gate, kernelCount, "runtimeOptimizerDrift.replacementPlan.partial.count")) + )); + String optimizerReplacementPlanFirstBlockers = gate.getProperty( + "optimizerReplacementPlan.firstBlockers", + summarizeKernelProperty(gate, kernelCount, "runtimeOptimizerDrift.replacementPlan.firstBlocker") + ); + int optimizerReplacementPlanValidationCount = parsePositiveInt(gate.getProperty( + "optimizerReplacementPlan.validation.count", + Integer.toString(sumKernelProperty(gate, kernelCount, "runtimeOptimizerDrift.replacementPlan.validation.count")) + )); + int optimizerReplacementPlanValidationValidCount = parsePositiveInt(gate.getProperty( + "optimizerReplacementPlan.validation.valid.count", + Integer.toString(sumKernelProperty(gate, kernelCount, "runtimeOptimizerDrift.replacementPlan.validation.valid.count")) + )); + int optimizerReplacementPlanValidationInvalidCount = parsePositiveInt(gate.getProperty( + "optimizerReplacementPlan.validation.invalid.count", + Integer.toString(sumKernelProperty(gate, kernelCount, "runtimeOptimizerDrift.replacementPlan.validation.invalid.count")) + )); + String optimizerReplacementPlanValidationFirstBlockers = gate.getProperty( + "optimizerReplacementPlan.validation.firstBlockers", + summarizeKernelProperty(gate, kernelCount, "runtimeOptimizerDrift.replacementPlan.validation.firstBlocker") + ); + int optimizerRuleCount = parsePositiveInt(gate.getProperty( + "optimizerRule.count", + Integer.toString(sumKernelProperty(gate, kernelCount, "runtimeOptimizerDrift.optimizerRule.count")) + )); + String optimizerRuleSummary = gate.getProperty( + "optimizerRule.summary", + summarizeKernelProperty(gate, kernelCount, "runtimeOptimizerDrift.optimizerRule.summary") + ); + String optimizerRuleDetails = gate.getProperty( + "optimizerRule.details", + summarizeOptimizerRules(gate, kernelCount) + ); int optimizerFamilyPayloadCompleteCount = parsePositiveInt( gate.getProperty( "optimizerFamilyPayload.complete.count", @@ -436,6 +476,16 @@ public static String format( builder.append("optimizerFamily.count=").append(optimizerFamilyCount).append('\n'); builder.append("optimizerFamily.promotionReady.count=").append(optimizerFamilyPromotionReadyCount).append('\n'); builder.append("optimizerFamily.summary=").append(optimizerFamilySummary).append('\n'); + builder.append("optimizerReplacementPlan.complete.count=").append(optimizerReplacementPlanCompleteCount).append('\n'); + builder.append("optimizerReplacementPlan.partial.count=").append(optimizerReplacementPlanPartialCount).append('\n'); + builder.append("optimizerReplacementPlan.firstBlockers=").append(optimizerReplacementPlanFirstBlockers).append('\n'); + builder.append("optimizerReplacementPlan.validation.count=").append(optimizerReplacementPlanValidationCount).append('\n'); + builder.append("optimizerReplacementPlan.validation.valid.count=").append(optimizerReplacementPlanValidationValidCount).append('\n'); + builder.append("optimizerReplacementPlan.validation.invalid.count=").append(optimizerReplacementPlanValidationInvalidCount).append('\n'); + builder.append("optimizerReplacementPlan.validation.firstBlockers=").append(optimizerReplacementPlanValidationFirstBlockers).append('\n'); + builder.append("optimizerRule.count=").append(optimizerRuleCount).append('\n'); + builder.append("optimizerRule.summary=").append(optimizerRuleSummary).append('\n'); + builder.append("optimizerRule.details=").append(optimizerRuleDetails).append('\n'); builder.append("optimizerFamilyPayload.complete.count=").append(optimizerFamilyPayloadCompleteCount).append('\n'); builder.append("optimizerFamilyPayload.complete.all=").append(optimizerFamilyPayloadCompleteAll).append('\n'); builder.append("optimizerFamily.runtimeEquivalenceHistoryBaselineReady=") @@ -777,6 +827,76 @@ private static String summarizeKernelProperty(Properties properties, int kernelC return builder.toString(); } + private static String summarizeOptimizerRules(Properties properties, int kernelCount) { + if (kernelCount <= 0) { + return "none"; + } + java.util.LinkedHashMap rules = new java.util.LinkedHashMap<>(); + for (int kernelIndex = 0; kernelIndex < kernelCount; kernelIndex++) { + String basePrefix = "kernel." + kernelIndex + ".runtimeOptimizerDrift.optimizerRule."; + int ruleCount = parsePositiveInt(properties.getProperty(basePrefix + "count", "0")); + for (int ruleIndex = 0; ruleIndex < ruleCount; ruleIndex++) { + String prefix = basePrefix + ruleIndex + "."; + String id = properties.getProperty(prefix + "id", ""); + if (id.isBlank()) { + continue; + } + OptimizerRuleAggregate existing = rules.getOrDefault(id, OptimizerRuleAggregate.empty(id)); + rules.put(id, existing.add( + parsePositiveInt(properties.getProperty(prefix + "candidate.count", "0")), + parsePositiveInt(properties.getProperty(prefix + "proposal.count", "0")), + parsePositiveInt(properties.getProperty(prefix + "applied.count", "0")), + parsePositiveInt(properties.getProperty(prefix + "skipped.count", "0")), + parsePositiveInt(properties.getProperty(prefix + "blocked.count", "0")), + parsePositiveInt(properties.getProperty(prefix + "replacementPlan.count", "0")), + parsePositiveInt(properties.getProperty(prefix + "replacementPlan.complete.count", "0")), + parsePositiveInt(properties.getProperty(prefix + "replacementPlan.partial.count", "0")), + parsePositiveInt(properties.getProperty(prefix + "replacementPlan.validation.count", "0")), + parsePositiveInt(properties.getProperty(prefix + "replacementPlan.validation.valid.count", "0")), + parsePositiveInt(properties.getProperty(prefix + "replacementPlan.validation.invalid.count", "0")), + properties.getProperty(prefix + "replacementPlan.validation.firstBlocker", "none"), + properties.getProperty(prefix + "firstBlocker", properties.getProperty(prefix + "replacementPlan.firstBlocker", "none")) + )); + } + } + if (rules.isEmpty()) { + return "none"; + } + StringBuilder builder = new StringBuilder(); + for (OptimizerRuleAggregate rule : rules.values()) { + if (!builder.isEmpty()) { + builder.append(", "); + } + builder.append(rule.id()) + .append("[candidates=") + .append(rule.candidateCount()) + .append(", proposals=") + .append(rule.proposalCount()) + .append(", applied=") + .append(rule.appliedCount()) + .append(", skipped=") + .append(rule.skippedCount()) + .append(", blocked=") + .append(rule.blockedCount()) + .append(", replacementPlans=") + .append(rule.replacementPlanCount()) + .append(", completePlans=") + .append(rule.replacementPlanCompleteCount()) + .append(", partialPlans=") + .append(rule.replacementPlanPartialCount()) + .append(", planValidations=") + .append(rule.replacementPlanValidationCount()) + .append(", invalidPlanValidations=") + .append(rule.replacementPlanValidationInvalidCount()) + .append(", planValidationFirstBlocker=") + .append(rule.replacementPlanValidationFirstBlocker()) + .append(", firstBlocker=") + .append(rule.firstBlocker()) + .append(']'); + } + return builder.toString(); + } + private static void appendReadinessChecklist(StringBuilder builder, List checklist) { int readyCount = 0; List blocked = new ArrayList<>(); @@ -874,6 +994,75 @@ private static int parsePositiveInt(String value) { } } + private record OptimizerRuleAggregate( + String id, + int candidateCount, + int proposalCount, + int appliedCount, + int skippedCount, + int blockedCount, + int replacementPlanCount, + int replacementPlanCompleteCount, + int replacementPlanPartialCount, + int replacementPlanValidationCount, + int replacementPlanValidationValidCount, + int replacementPlanValidationInvalidCount, + String replacementPlanValidationFirstBlocker, + String firstBlocker + ) { + + private static OptimizerRuleAggregate empty(String id) { + return new OptimizerRuleAggregate(id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "none", "none"); + } + + private OptimizerRuleAggregate add( + int candidateCount, + int proposalCount, + int appliedCount, + int skippedCount, + int blockedCount, + int replacementPlanCount, + int replacementPlanCompleteCount, + int replacementPlanPartialCount, + int replacementPlanValidationCount, + int replacementPlanValidationValidCount, + int replacementPlanValidationInvalidCount, + String nextValidationBlocker, + String nextBlocker + ) { + String blocker = firstBlocker; + String validationBlocker = replacementPlanValidationFirstBlocker; + if ("none".equals(validationBlocker) + && nextValidationBlocker != null + && !nextValidationBlocker.isBlank() + && !"none".equals(nextValidationBlocker)) { + validationBlocker = nextValidationBlocker; + } + if ("none".equals(blocker) + && nextBlocker != null + && !nextBlocker.isBlank() + && !"none".equals(nextBlocker)) { + blocker = nextBlocker; + } + return new OptimizerRuleAggregate( + id, + this.candidateCount + candidateCount, + this.proposalCount + proposalCount, + this.appliedCount + appliedCount, + this.skippedCount + skippedCount, + this.blockedCount + blockedCount, + this.replacementPlanCount + replacementPlanCount, + this.replacementPlanCompleteCount + replacementPlanCompleteCount, + this.replacementPlanPartialCount + replacementPlanPartialCount, + this.replacementPlanValidationCount + replacementPlanValidationCount, + this.replacementPlanValidationValidCount + replacementPlanValidationValidCount, + this.replacementPlanValidationInvalidCount + replacementPlanValidationInvalidCount, + validationBlocker, + blocker + ); + } + } + private record ControlledSourceSwitchingCoverage( List realWorkloadResources, List coveredResources, diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilitySummary.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilitySummary.java index 8a3b67d6..d6537d8a 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilitySummary.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilitySummary.java @@ -27,6 +27,16 @@ public record GpuProductionPromotionExplainabilitySummary( int optimizerFamilyCount, int optimizerFamilyPromotionReadyCount, String optimizerFamilySummary, + int optimizerReplacementPlanCompleteCount, + int optimizerReplacementPlanPartialCount, + String optimizerReplacementPlanFirstBlockers, + int optimizerReplacementPlanValidationCount, + int optimizerReplacementPlanValidationValidCount, + int optimizerReplacementPlanValidationInvalidCount, + String optimizerReplacementPlanValidationFirstBlockers, + int optimizerRuleCount, + String optimizerRuleSummary, + String optimizerRuleDetails, int optimizerFamilyPayloadCompleteCount, String optimizerFamilyPayloadCompleteAll, String optimizerFamilyRuntimeEquivalenceHistoryBaselineReady, @@ -90,6 +100,16 @@ public static GpuProductionPromotionExplainabilitySummary notRecorded() { 0, "none", 0, + 0, + "", + 0, + 0, + 0, + "", + 0, + "none", + "none", + 0, "false", "false", "true", @@ -158,6 +178,16 @@ public static GpuProductionPromotionExplainabilitySummary fromProperties(Propert parsePositiveInt(properties.getProperty("optimizerFamily.count", "0")), parsePositiveInt(properties.getProperty("optimizerFamily.promotionReady.count", "0")), properties.getProperty("optimizerFamily.summary", "none"), + parsePositiveInt(properties.getProperty("optimizerReplacementPlan.complete.count", "0")), + parsePositiveInt(properties.getProperty("optimizerReplacementPlan.partial.count", "0")), + properties.getProperty("optimizerReplacementPlan.firstBlockers", ""), + parsePositiveInt(properties.getProperty("optimizerReplacementPlan.validation.count", "0")), + parsePositiveInt(properties.getProperty("optimizerReplacementPlan.validation.valid.count", "0")), + parsePositiveInt(properties.getProperty("optimizerReplacementPlan.validation.invalid.count", "0")), + properties.getProperty("optimizerReplacementPlan.validation.firstBlockers", ""), + parsePositiveInt(properties.getProperty("optimizerRule.count", "0")), + properties.getProperty("optimizerRule.summary", "none"), + properties.getProperty("optimizerRule.details", "none"), parsePositiveInt(properties.getProperty("optimizerFamilyPayload.complete.count", "0")), properties.getProperty("optimizerFamilyPayload.complete.all", "false"), properties.getProperty("optimizerFamily.runtimeEquivalenceHistoryBaselineReady", "false"), @@ -279,6 +309,8 @@ public String historyStatus() { + ", optimizerPromotionPreflightReady=" + optimizerFamilyPromotionPreflightReady + optimizerFamilySummaryText() + + optimizerReplacementPlanSummaryText() + + optimizerRuleSummaryText() + ", backendPromotionArtifactSupportComplete=" + backendPromotionArtifactSupportComplete + ", backendPromotionArtifactSupportMissing=" + backendPromotionArtifactSupportMissingCount + ", controlledSourceSwitching=" + controlledProductionSourceSwitchingStatus @@ -336,6 +368,53 @@ private String optimizerFamilySummaryText() { : ", optimizerFamilySummary=" + optimizerFamilySummary; } + private String optimizerRuleSummaryText() { + if (optimizerRuleCount == 0 + && (optimizerRuleSummary == null || optimizerRuleSummary.isBlank() || "none".equals(optimizerRuleSummary)) + && (optimizerRuleDetails == null || optimizerRuleDetails.isBlank() || "none".equals(optimizerRuleDetails))) { + return ""; + } + StringBuilder builder = new StringBuilder(", optimizerRules=").append(optimizerRuleCount); + if (optimizerRuleSummary != null && !optimizerRuleSummary.isBlank() && !"none".equals(optimizerRuleSummary)) { + builder.append(", optimizerRuleSummary=").append(optimizerRuleSummary); + } + if (optimizerRuleDetails != null && !optimizerRuleDetails.isBlank() && !"none".equals(optimizerRuleDetails)) { + builder.append(", optimizerRuleDetails=").append(optimizerRuleDetails); + } + return builder.toString(); + } + + private String optimizerReplacementPlanSummaryText() { + if (optimizerReplacementPlanCompleteCount == 0 + && optimizerReplacementPlanPartialCount == 0 + && optimizerReplacementPlanValidationCount == 0 + && optimizerReplacementPlanValidationValidCount == 0 + && optimizerReplacementPlanValidationInvalidCount == 0 + && (optimizerReplacementPlanFirstBlockers == null || optimizerReplacementPlanFirstBlockers.isBlank()) + && (optimizerReplacementPlanValidationFirstBlockers == null + || optimizerReplacementPlanValidationFirstBlockers.isBlank())) { + return ""; + } + StringBuilder builder = new StringBuilder(", optimizerReplacementPlans=complete=") + .append(optimizerReplacementPlanCompleteCount) + .append("/partial=") + .append(optimizerReplacementPlanPartialCount) + .append("/validation=valid=") + .append(optimizerReplacementPlanValidationValidCount) + .append("/total=") + .append(optimizerReplacementPlanValidationCount) + .append("/invalid=") + .append(optimizerReplacementPlanValidationInvalidCount); + if (optimizerReplacementPlanFirstBlockers != null && !optimizerReplacementPlanFirstBlockers.isBlank()) { + builder.append("/firstBlockers=").append(optimizerReplacementPlanFirstBlockers); + } + if (optimizerReplacementPlanValidationFirstBlockers != null + && !optimizerReplacementPlanValidationFirstBlockers.isBlank()) { + builder.append("/validationFirstBlockers=").append(optimizerReplacementPlanValidationFirstBlockers); + } + return builder.toString(); + } + private static int parsePositiveInt(String value) { try { return Math.max(0, Integer.parseInt(value == null ? "0" : value.trim())); diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeClampPeepholeRule.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeClampPeepholeRule.java new file mode 100644 index 00000000..a93ecb17 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeClampPeepholeRule.java @@ -0,0 +1,127 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Detects typed {@code min(max(x, lo), hi)} clamp candidates without rewriting them. + */ +public final class GpuRuntimeClampPeepholeRule implements GpuRuntimeIrPeepholeRule { + + public static final String RULE_ID = "clamp"; + public static final String VERSION = "peephole-rule:clamp-v1"; + + @Override + public GpuRuntimeIrPeepholeRuleReport analyze(GpuRuntimeIrPeepholeRuleContext context) { + int candidates = 0; + ArrayList plans = new ArrayList<>(); + for (IrGpuTypedNode node : context.graph().nodes()) { + if (!context.graph().isCall(node, "min")) { + continue; + } + GpuRuntimeIrPeepholeReplacementPlan plan = replacementPlan(context, node); + if (plan.complete()) { + candidates++; + } + if (!"not-clamp-shape".equals(plan.firstBlocker())) { + plans.add(plan); + } + } + return GpuRuntimeIrPeepholeRuleReport.diagnosticCandidates( + this, + context.methodBody().name(), + candidates, + Map.of( + "pattern", "min(max(x,lo),hi)", + "replacementPlan.count", Integer.toString(plans.size()), + "replacementPlan.complete.count", Long.toString(plans.stream().filter(GpuRuntimeIrPeepholeReplacementPlan::complete).count()), + "replacementPlan.partial.count", Long.toString(plans.stream().filter(plan -> !plan.complete()).count()), + "replacementPlan.firstBlocker", plans.stream() + .filter(plan -> !plan.complete()) + .map(GpuRuntimeIrPeepholeReplacementPlan::firstBlocker) + .findFirst() + .orElse("none") + ), + plans + ); + } + + @Override + public String ruleId() { + return RULE_ID; + } + + @Override + public String ruleVersion() { + return VERSION; + } + + @Override + public String extensionId() { + return "javatogpu.peephole.clamp"; + } + + @Override + public int extensionOrder() { + return 100; + } + + private GpuRuntimeIrPeepholeReplacementPlan replacementPlan( + GpuRuntimeIrPeepholeRuleContext context, + IrGpuTypedNode minNode + ) { + GpuRuntimeIrTypedNodeGraph graph = context.graph(); + List minArgs = graph.callArguments(minNode); + if (minArgs.size() != 2) { + return GpuRuntimeIrPeepholeReplacementPlan.blocked( + RULE_ID, + context.methodBody().name(), + minNode.id(), + "clamp", + List.of(minNode.id()), + minArgs, + "min-arguments-incomplete" + ); + } + IrGpuTypedNode first = graph.node(minArgs.get(0)); + IrGpuTypedNode second = graph.node(minArgs.get(1)); + boolean firstMax = graph.isCall(first, "max"); + boolean secondMax = graph.isCall(second, "max"); + if (!firstMax && !secondMax) { + return GpuRuntimeIrPeepholeReplacementPlan.blocked( + RULE_ID, + context.methodBody().name(), + minNode.id(), + "clamp", + List.of(minNode.id()), + minArgs, + "not-clamp-shape" + ); + } + IrGpuTypedNode maxNode = firstMax ? first : second; + int hiId = firstMax ? minArgs.get(1) : minArgs.get(0); + List maxArgs = graph.callArguments(maxNode); + if (maxArgs.size() != 2) { + return GpuRuntimeIrPeepholeReplacementPlan.blocked( + RULE_ID, + context.methodBody().name(), + minNode.id(), + "clamp", + List.of(minNode.id(), maxNode.id()), + List.of(hiId), + "max-arguments-incomplete" + ); + } + return GpuRuntimeIrPeepholeReplacementPlan.complete( + RULE_ID, + context.methodBody().name(), + minNode.id(), + "clamp", + List.of(minNode.id(), maxNode.id()), + List.of(maxArgs.get(0), maxArgs.get(1), hiId) + ); + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeDotPeepholeRule.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeDotPeepholeRule.java new file mode 100644 index 00000000..c33467ee --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeDotPeepholeRule.java @@ -0,0 +1,191 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Detects fixed-width additive multiply trees that can become {@code dot(...)}. + */ +public final class GpuRuntimeDotPeepholeRule implements GpuRuntimeIrPeepholeRule { + + public static final String RULE_ID = "dot"; + public static final String VERSION = "peephole-rule:dot-v1"; + + @Override + public GpuRuntimeIrPeepholeRuleReport analyze(GpuRuntimeIrPeepholeRuleContext context) { + GpuRuntimeIrTypedNodeGraph graph = context.graph(); + Set nestedAddNodes = nestedAddNodes(graph); + int candidates = 0; + ArrayList plans = new ArrayList<>(); + for (IrGpuTypedNode node : graph.nodes()) { + if (!graph.isBinary(node, "+") || nestedAddNodes.contains(node.id())) { + continue; + } + GpuRuntimeIrPeepholeReplacementPlan plan = replacementPlan(context, node); + if (plan.complete()) { + candidates++; + } + if (!"not-dot-shape".equals(plan.firstBlocker())) { + plans.add(plan); + } + } + return GpuRuntimeIrPeepholeRuleReport.diagnosticCandidates( + this, + context.methodBody().name(), + candidates, + Map.of( + "pattern", "sum(aN*bN)", + "replacementPlan.count", Integer.toString(plans.size()), + "replacementPlan.complete.count", Long.toString(plans.stream().filter(GpuRuntimeIrPeepholeReplacementPlan::complete).count()), + "replacementPlan.partial.count", Long.toString(plans.stream().filter(plan -> !plan.complete()).count()), + "replacementPlan.firstBlocker", plans.stream() + .filter(plan -> !plan.complete()) + .map(GpuRuntimeIrPeepholeReplacementPlan::firstBlocker) + .findFirst() + .orElse("none") + ), + plans + ); + } + + @Override + public String ruleId() { + return RULE_ID; + } + + @Override + public String ruleVersion() { + return VERSION; + } + + @Override + public String extensionId() { + return "javatogpu.peephole.dot"; + } + + @Override + public int extensionOrder() { + return 100; + } + + private GpuRuntimeIrPeepholeReplacementPlan replacementPlan( + GpuRuntimeIrPeepholeRuleContext context, + IrGpuTypedNode addNode + ) { + DotPlan plan = collect(context.graph(), addNode); + if (!"none".equals(plan.firstBlocker())) { + return GpuRuntimeIrPeepholeReplacementPlan.blocked( + RULE_ID, + context.methodBody().name(), + addNode.id(), + "dot", + plan.coveredNodeIds(), + plan.inputNodeIds(), + plan.firstBlocker() + ); + } + if (plan.productCount() < 2) { + return GpuRuntimeIrPeepholeReplacementPlan.blocked( + RULE_ID, + context.methodBody().name(), + addNode.id(), + "dot", + plan.coveredNodeIds(), + plan.inputNodeIds(), + "not-dot-shape" + ); + } + return GpuRuntimeIrPeepholeReplacementPlan.complete( + RULE_ID, + context.methodBody().name(), + addNode.id(), + "dot", + plan.coveredNodeIds(), + plan.inputNodeIds() + ); + } + + private static DotPlan collect(GpuRuntimeIrTypedNodeGraph graph, IrGpuTypedNode node) { + if (node == null) { + return DotPlan.blocked(List.of(), List.of(), "dot-node-missing"); + } + if (graph.isBinary(node, "+")) { + Integer left = graph.singleChild(node, "left"); + Integer right = graph.singleChild(node, "right"); + if (left == null || right == null) { + return DotPlan.blocked( + List.of(node.id()), + GpuRuntimeIrTypedNodeGraph.presentIds(left, right), + "dot-add-operands-incomplete" + ); + } + DotPlan leftPlan = collect(graph, graph.node(left)); + DotPlan rightPlan = collect(graph, graph.node(right)); + return DotPlan.merge(node.id(), leftPlan, rightPlan); + } + if (graph.isBinary(node, "*")) { + Integer left = graph.singleChild(node, "left"); + Integer right = graph.singleChild(node, "right"); + if (left == null || right == null) { + return DotPlan.blocked( + List.of(node.id()), + GpuRuntimeIrTypedNodeGraph.presentIds(left, right), + "dot-multiply-operands-incomplete" + ); + } + return new DotPlan(List.of(node.id()), List.of(left, right), 1, "none"); + } + return DotPlan.blocked(List.of(), List.of(node.id()), "not-dot-shape"); + } + + private static Set nestedAddNodes(GpuRuntimeIrTypedNodeGraph graph) { + HashSet nested = new HashSet<>(); + for (IrGpuTypedNode node : graph.nodes()) { + if (!graph.isBinary(node, "+")) { + continue; + } + for (Integer childId : graph.childIds(node, "left", "right")) { + IrGpuTypedNode child = graph.node(childId); + if (graph.isBinary(child, "+")) { + nested.add(child.id()); + } + } + } + return nested; + } + + private record DotPlan( + List coveredNodeIds, + List inputNodeIds, + int productCount, + String firstBlocker + ) { + + private DotPlan { + coveredNodeIds = List.copyOf(coveredNodeIds); + inputNodeIds = List.copyOf(inputNodeIds); + firstBlocker = firstBlocker == null || firstBlocker.isBlank() ? "none" : firstBlocker; + } + + private static DotPlan blocked(List coveredNodeIds, List inputNodeIds, String blocker) { + return new DotPlan(coveredNodeIds, inputNodeIds, 0, blocker); + } + + private static DotPlan merge(int addNodeId, DotPlan left, DotPlan right) { + ArrayList covered = new ArrayList<>(); + covered.add(addNodeId); + covered.addAll(left.coveredNodeIds()); + covered.addAll(right.coveredNodeIds()); + ArrayList inputs = new ArrayList<>(); + inputs.addAll(left.inputNodeIds()); + inputs.addAll(right.inputNodeIds()); + String blocker = !"none".equals(left.firstBlocker()) ? left.firstBlocker() : right.firstBlocker(); + return new DotPlan(covered, inputs, left.productCount() + right.productCount(), blocker); + } + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholePass.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholePass.java index 7ab25cb4..ca899a3a 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholePass.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholePass.java @@ -90,11 +90,18 @@ private Map diagnosticFields( fields.put("rule.count", Integer.toString(ruleRegistry.rules().size())); fields.put("candidate.count", Integer.toString(analysis.candidateCount())); fields.put("proposal.count", Integer.toString(analysis.proposalCount())); - fields.put("rule.mix.candidate.count", "0"); + fields.put("replacementPlan.partial.count", Integer.toString(analysis.partialReplacementPlanCount())); + fields.put("replacementPlan.firstBlocker", analysis.firstReplacementPlanBlocker()); + fields.put( + "replacementPlan.validation.invalid.count", + Integer.toString(analysis.invalidReplacementPlanValidationCount()) + ); + fields.put("replacementPlan.validation.firstBlocker", analysis.firstReplacementPlanValidationBlocker()); + fields.put("rule.mix.candidate.count", Integer.toString(candidateCount(analysis, "mix"))); fields.put("rule.madFma.candidate.count", Integer.toString(candidateCount(analysis, "madFma"))); - fields.put("rule.clamp.candidate.count", "0"); - fields.put("rule.step.candidate.count", "0"); - fields.put("rule.dot.candidate.count", "0"); + fields.put("rule.clamp.candidate.count", Integer.toString(candidateCount(analysis, "clamp"))); + fields.put("rule.step.candidate.count", Integer.toString(candidateCount(analysis, "step"))); + fields.put("rule.dot.candidate.count", Integer.toString(candidateCount(analysis, "dot"))); appendRuleFields(fields, analysis); fields.put("rule.execution.count", Integer.toString(analysis.executionReports().size())); fields.put("rule.execution.failedContinued.count", Long.toString(analysis.executionReports().stream() @@ -107,6 +114,10 @@ private Map diagnosticFields( artifact, typedIrAvailable, analysis.candidateCount(), + analysis.invalidReplacementPlanValidationCount(), + analysis.firstReplacementPlanValidationBlocker(), + analysis.partialReplacementPlanCount(), + analysis.firstReplacementPlanBlocker(), analysis.failedClosed() )); return Map.copyOf(fields); @@ -141,11 +152,87 @@ private void appendRuleFields( fields.put(prefix + ".proposal.count", Integer.toString(reports.stream() .mapToInt(GpuRuntimeIrPeepholeRuleReport::proposalCount) .sum())); - fields.put(prefix + ".mutationProposed", Boolean.toString(reports.stream() - .anyMatch(GpuRuntimeIrPeepholeRuleReport::mutationProposed))); + int candidateCount = reports.stream().mapToInt(GpuRuntimeIrPeepholeRuleReport::candidateCount).sum(); + int proposalCount = reports.stream().mapToInt(GpuRuntimeIrPeepholeRuleReport::proposalCount).sum(); + long partialPlanCount = reports.stream() + .flatMap(report -> report.replacementPlans().stream()) + .filter(plan -> !plan.complete()) + .count(); + boolean mutationProposed = reports.stream().anyMatch(GpuRuntimeIrPeepholeRuleReport::mutationProposed); + fields.put(prefix + ".applied.count", mutationProposed ? "1" : "0"); + fields.put(prefix + ".skipped.count", reports.isEmpty() || (candidateCount == 0 && proposalCount == 0 && partialPlanCount == 0) ? "1" : "0"); + fields.put(prefix + ".blocked.count", !mutationProposed && (candidateCount > 0 || partialPlanCount > 0) ? "1" : "0"); + fields.put(prefix + ".mutationProposed", Boolean.toString(mutationProposed)); fields.put(prefix + ".proofStatus", reports.isEmpty() ? executionProofStatus(rule, analysis) : reports.get(0).proofStatus()); + appendReplacementPlanFields(fields, prefix, reports); + fields.put(prefix + ".firstBlocker", ruleFirstBlocker(fields, prefix, reports.isEmpty())); + } + } + + private static String ruleFirstBlocker( + Map fields, + String prefix, + boolean reportsEmpty + ) { + String validationBlocker = fields.getOrDefault(prefix + ".replacementPlan.validation.firstBlocker", "none"); + if (!validationBlocker.isBlank() && !"none".equals(validationBlocker)) { + return validationBlocker; + } + String replacementBlocker = fields.getOrDefault(prefix + ".replacementPlan.firstBlocker", "none"); + if (!replacementBlocker.isBlank() && !"none".equals(replacementBlocker)) { + return replacementBlocker; + } + if (parsePositiveInt(fields.get(prefix + ".candidate.count")) > 0) { + return "rewrite-engine-not-implemented"; + } + if (reportsEmpty) { + return fields.getOrDefault(prefix + ".proofStatus", "not-run"); + } + return "none"; + } + + private static void appendReplacementPlanFields( + LinkedHashMap fields, + String prefix, + List reports + ) { + List plans = reports.stream() + .flatMap(report -> report.replacementPlans().stream()) + .toList(); + fields.put(prefix + ".replacementPlan.count", Integer.toString(plans.size())); + fields.put(prefix + ".replacementPlan.complete.count", Long.toString(plans.stream() + .filter(GpuRuntimeIrPeepholeReplacementPlan::complete) + .count())); + fields.put(prefix + ".replacementPlan.partial.count", Long.toString(plans.stream() + .filter(plan -> !plan.complete()) + .count())); + fields.put(prefix + ".replacementPlan.firstBlocker", plans.stream() + .filter(plan -> !plan.complete()) + .map(GpuRuntimeIrPeepholeReplacementPlan::firstBlocker) + .findFirst() + .orElse("none")); + if (!plans.isEmpty()) { + fields.putAll(plans.get(0).fields(prefix + ".replacementPlan.0")); + } + List validations = reports.stream() + .flatMap(report -> report.replacementPlanValidations().stream()) + .toList(); + fields.put(prefix + ".replacementPlan.validation.count", Integer.toString(validations.size())); + fields.put(prefix + ".replacementPlan.validation.valid.count", Long.toString(validations.stream() + .filter(GpuRuntimeIrPeepholeReplacementPlanValidation::valid) + .count())); + fields.put(prefix + ".replacementPlan.validation.invalid.count", Long.toString(validations.stream() + .filter(validation -> !validation.valid()) + .count())); + fields.put(prefix + ".replacementPlan.validation.firstBlocker", validations.stream() + .filter(validation -> !validation.valid()) + .map(GpuRuntimeIrPeepholeReplacementPlanValidation::firstBlocker) + .findFirst() + .orElse("none")); + if (!validations.isEmpty()) { + fields.putAll(validations.get(0).fields(prefix + ".replacementPlan.validation.0")); } } @@ -169,6 +256,10 @@ private static String firstBlocker( Optional artifact, boolean typedIrAvailable, int candidateCount, + int invalidReplacementPlanValidationCount, + String firstReplacementPlanValidationBlocker, + int partialReplacementPlanCount, + String firstReplacementPlanBlocker, boolean failedClosed ) { if (artifact.isEmpty()) { @@ -180,6 +271,16 @@ private static String firstBlocker( if (failedClosed) { return "rule-execution-failed-closed"; } + if (invalidReplacementPlanValidationCount > 0) { + return firstReplacementPlanValidationBlocker == null || firstReplacementPlanValidationBlocker.isBlank() + ? "replacement-plan-validation-failed" + : firstReplacementPlanValidationBlocker; + } + if (partialReplacementPlanCount > 0) { + return firstReplacementPlanBlocker == null || firstReplacementPlanBlocker.isBlank() + ? "replacement-plan-incomplete" + : firstReplacementPlanBlocker; + } return candidateCount == 0 ? "no-peephole-candidates" : "rewrite-engine-not-implemented"; } @@ -190,10 +291,24 @@ private static String diagnostic(Map fields) { "typed runtime IR is required; text-based peephole rewriting is disabled"; case "no-peephole-candidates" -> "typed runtime IR contains no supported peephole candidates"; case "rule-execution-failed-closed" -> "peephole rule execution failed closed in a production profile"; + case "replacement-plan-root-missing", "replacement-plan-root-not-covered", + "replacement-plan-covered-node-missing", "replacement-plan-input-node-missing", + "replacement-plan-validation-failed" -> + "typed peephole replacement plan failed structural validation; no mutation proposal was emitted"; + case "add-operands-incomplete", "multiply-operands-incomplete", "replacement-plan-incomplete" -> + "typed peephole replacement plan is incomplete; no mutation proposal was emitted"; case "rewrite-engine-not-implemented" -> "typed peephole candidates detected, but structural rewrite and proof emission are not implemented"; default -> "peephole preflight blocked"; }; } + private static int parsePositiveInt(String value) { + try { + return Math.max(0, Integer.parseInt(value == null ? "0" : value)); + } catch (NumberFormatException ignored) { + return 0; + } + } + } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeReplacementPlan.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeReplacementPlan.java new file mode 100644 index 00000000..46211874 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeReplacementPlan.java @@ -0,0 +1,108 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import java.util.List; +import java.util.Map; + +/** + * Read-only proof plan for a future structural typed-IR peephole replacement. + * + *

The plan records the nodes a rule would replace and the input nodes needed to build the replacement, but it does + * not mutate or authorize mutation by itself.

+ */ +public record GpuRuntimeIrPeepholeReplacementPlan( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + List coveredNodeIds, + List inputNodeIds, + boolean complete, + String firstBlocker +) { + + public GpuRuntimeIrPeepholeReplacementPlan { + ruleId = normalize(ruleId, "rule:unknown"); + methodName = normalize(methodName, "unknown"); + rootNodeId = Math.max(0, rootNodeId); + replacementKind = normalize(replacementKind, "unknown"); + coveredNodeIds = normalizeNodeIds(coveredNodeIds); + inputNodeIds = normalizeNodeIds(inputNodeIds); + firstBlocker = normalize(firstBlocker, complete ? "none" : "replacement-plan-incomplete"); + } + + public static GpuRuntimeIrPeepholeReplacementPlan complete( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + List coveredNodeIds, + List inputNodeIds + ) { + return new GpuRuntimeIrPeepholeReplacementPlan( + ruleId, + methodName, + rootNodeId, + replacementKind, + coveredNodeIds, + inputNodeIds, + true, + "none" + ); + } + + public static GpuRuntimeIrPeepholeReplacementPlan blocked( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + List coveredNodeIds, + List inputNodeIds, + String firstBlocker + ) { + return new GpuRuntimeIrPeepholeReplacementPlan( + ruleId, + methodName, + rootNodeId, + replacementKind, + coveredNodeIds, + inputNodeIds, + false, + firstBlocker + ); + } + + public Map fields(String prefix) { + String normalizedPrefix = normalize(prefix, "replacementPlan"); + return Map.of( + normalizedPrefix + ".ruleId", ruleId, + normalizedPrefix + ".methodName", methodName, + normalizedPrefix + ".rootNodeId", Integer.toString(rootNodeId), + normalizedPrefix + ".replacementKind", replacementKind, + normalizedPrefix + ".coveredNodeIds", join(coveredNodeIds), + normalizedPrefix + ".inputNodeIds", join(inputNodeIds), + normalizedPrefix + ".complete", Boolean.toString(complete), + normalizedPrefix + ".firstBlocker", firstBlocker + ); + } + + private static List normalizeNodeIds(List ids) { + if (ids == null || ids.isEmpty()) { + return List.of(); + } + return ids.stream() + .filter(id -> id != null && id >= 0) + .distinct() + .toList(); + } + + private static String join(List ids) { + if (ids == null || ids.isEmpty()) { + return "none"; + } + return ids.stream().map(String::valueOf).collect(java.util.stream.Collectors.joining(",")); + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeReplacementPlanValidation.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeReplacementPlanValidation.java new file mode 100644 index 00000000..65e4dead --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeReplacementPlanValidation.java @@ -0,0 +1,117 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Fail-closed structural validation for a read-only peephole replacement plan. + */ +public record GpuRuntimeIrPeepholeReplacementPlanValidation( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + boolean valid, + String firstBlocker, + boolean rootExists, + boolean coveredIncludesRoot, + List missingCoveredNodeIds, + List missingInputNodeIds +) { + + public GpuRuntimeIrPeepholeReplacementPlanValidation { + ruleId = normalize(ruleId, "rule:unknown"); + methodName = normalize(methodName, "unknown"); + rootNodeId = Math.max(0, rootNodeId); + replacementKind = normalize(replacementKind, "unknown"); + firstBlocker = normalize(firstBlocker, valid ? "none" : "replacement-plan-validation-failed"); + missingCoveredNodeIds = missingCoveredNodeIds == null ? List.of() : List.copyOf(missingCoveredNodeIds); + missingInputNodeIds = missingInputNodeIds == null ? List.of() : List.copyOf(missingInputNodeIds); + } + + public static GpuRuntimeIrPeepholeReplacementPlanValidation validate( + GpuRuntimeIrPeepholeReplacementPlan plan, + GpuRuntimeIrTypedNodeGraph graph + ) { + if (plan == null) { + return new GpuRuntimeIrPeepholeReplacementPlanValidation( + "rule:unknown", + "unknown", + 0, + "unknown", + false, + "replacement-plan-missing", + false, + false, + List.of(), + List.of() + ); + } + boolean rootExists = graph != null && graph.containsNode(plan.rootNodeId()); + boolean coveredIncludesRoot = plan.coveredNodeIds().contains(plan.rootNodeId()); + List missingCovered = graph == null ? plan.coveredNodeIds() : graph.missingNodeIds(plan.coveredNodeIds()); + List missingInput = graph == null ? plan.inputNodeIds() : graph.missingNodeIds(plan.inputNodeIds()); + String blocker = firstBlocker(rootExists, coveredIncludesRoot, missingCovered, missingInput); + return new GpuRuntimeIrPeepholeReplacementPlanValidation( + plan.ruleId(), + plan.methodName(), + plan.rootNodeId(), + plan.replacementKind(), + "none".equals(blocker), + blocker, + rootExists, + coveredIncludesRoot, + missingCovered, + missingInput + ); + } + + public Map fields(String prefix) { + String normalizedPrefix = normalize(prefix, "replacementPlan.validation"); + return Map.of( + normalizedPrefix + ".ruleId", ruleId, + normalizedPrefix + ".methodName", methodName, + normalizedPrefix + ".rootNodeId", Integer.toString(rootNodeId), + normalizedPrefix + ".replacementKind", replacementKind, + normalizedPrefix + ".valid", Boolean.toString(valid), + normalizedPrefix + ".firstBlocker", firstBlocker, + normalizedPrefix + ".rootExists", Boolean.toString(rootExists), + normalizedPrefix + ".coveredIncludesRoot", Boolean.toString(coveredIncludesRoot), + normalizedPrefix + ".missingCoveredNodeIds", join(missingCoveredNodeIds), + normalizedPrefix + ".missingInputNodeIds", join(missingInputNodeIds) + ); + } + + private static String firstBlocker( + boolean rootExists, + boolean coveredIncludesRoot, + List missingCovered, + List missingInput + ) { + if (!rootExists) { + return "replacement-plan-root-missing"; + } + if (!coveredIncludesRoot) { + return "replacement-plan-root-not-covered"; + } + if (missingCovered != null && !missingCovered.isEmpty()) { + return "replacement-plan-covered-node-missing"; + } + if (missingInput != null && !missingInput.isEmpty()) { + return "replacement-plan-input-node-missing"; + } + return "none"; + } + + private static String join(List ids) { + if (ids == null || ids.isEmpty()) { + return "none"; + } + return ids.stream().map(String::valueOf).collect(Collectors.joining(",")); + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRuleContext.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRuleContext.java index 639b9c3c..4bcc54cc 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRuleContext.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRuleContext.java @@ -7,7 +7,6 @@ import java.util.Map; import java.util.Objects; -import java.util.stream.Collectors; /** * Immutable typed method-body context passed to one peephole rule. @@ -16,7 +15,7 @@ public record GpuRuntimeIrPeepholeRuleContext( GpuRuntimeIrOptimizationRequest request, IrGpuArtifact artifact, IrGpuMethodBody methodBody, - Map nodesById + GpuRuntimeIrTypedNodeGraph graph ) { public GpuRuntimeIrPeepholeRuleContext { @@ -26,9 +25,7 @@ public record GpuRuntimeIrPeepholeRuleContext( if (!methodBody.typedBody().available()) { throw new IllegalArgumentException("Peephole rule context requires an available typed IrGpu body"); } - nodesById = nodesById == null - ? indexNodes(methodBody.typedBody()) - : Map.copyOf(nodesById); + graph = graph == null ? GpuRuntimeIrTypedNodeGraph.from(methodBody.typedBody()) : graph; } public GpuRuntimeIrPeepholeRuleContext( @@ -36,7 +33,16 @@ public GpuRuntimeIrPeepholeRuleContext( IrGpuArtifact artifact, IrGpuMethodBody methodBody ) { - this(request, artifact, methodBody, null); + this(request, artifact, methodBody, (GpuRuntimeIrTypedNodeGraph) null); + } + + public GpuRuntimeIrPeepholeRuleContext( + GpuRuntimeIrOptimizationRequest request, + IrGpuArtifact artifact, + IrGpuMethodBody methodBody, + Map nodesById + ) { + this(request, artifact, methodBody, GpuRuntimeIrTypedNodeGraph.from(methodBody.typedBody(), nodesById)); } public IrGpuTypedBody typedBody() { @@ -44,10 +50,10 @@ public IrGpuTypedBody typedBody() { } public IrGpuTypedNode node(int id) { - return nodesById.get(id); + return graph.node(id); } - private static Map indexNodes(IrGpuTypedBody body) { - return body.nodes().stream().collect(Collectors.toUnmodifiableMap(IrGpuTypedNode::id, node -> node)); + public Map nodesById() { + return graph.nodesById(); } } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRuleRegistry.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRuleRegistry.java index c88b34b3..33896afe 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRuleRegistry.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRuleRegistry.java @@ -44,6 +44,10 @@ public static GpuRuntimeIrPeepholeRuleRegistry of(List public static GpuRuntimeIrPeepholeRuleRegistry loadWithBuiltIns() { ArrayList loaded = new ArrayList<>(); loaded.add(new GpuRuntimeMadFmaPeepholeRule()); + loaded.add(new GpuRuntimeClampPeepholeRule()); + loaded.add(new GpuRuntimeMixPeepholeRule()); + loaded.add(new GpuRuntimeStepPeepholeRule()); + loaded.add(new GpuRuntimeDotPeepholeRule()); ServiceLoader.load(GpuRuntimeIrPeepholeRule.class, GpuRuntimeIrPeepholeRule.class.getClassLoader()) .forEach(loaded::add); loaded.sort(Comparator @@ -65,11 +69,17 @@ public Analysis analyze(GpuRuntimeIrOptimizationRequest request, IrGpuArtifact a continue; } try { + GpuRuntimeIrPeepholeRuleContext context = new GpuRuntimeIrPeepholeRuleContext( + request, + artifact, + methodBody + ); GpuRuntimeIrPeepholeRuleReport report = Objects.requireNonNull( - rule.analyze(new GpuRuntimeIrPeepholeRuleContext(request, artifact, methodBody)), + rule.analyze(context), "peephole rule report" ); validateRuleReport(rule, report); + report = withReplacementPlanValidations(context, report); reports.add(report); executions.add(GpuExtensionExecutionReport.succeeded( rule, @@ -130,6 +140,15 @@ private static void validateRuleReport( } } + private static GpuRuntimeIrPeepholeRuleReport withReplacementPlanValidations( + GpuRuntimeIrPeepholeRuleContext context, + GpuRuntimeIrPeepholeRuleReport report + ) { + return report.withReplacementPlanValidations(report.replacementPlans().stream() + .map(plan -> GpuRuntimeIrPeepholeReplacementPlanValidation.validate(plan, context.graph())) + .toList()); + } + public record Analysis( List ruleReports, List executionReports, @@ -147,5 +166,37 @@ public int candidateCount() { public int proposalCount() { return ruleReports.stream().mapToInt(GpuRuntimeIrPeepholeRuleReport::proposalCount).sum(); } + + public int partialReplacementPlanCount() { + return (int) ruleReports.stream() + .flatMap(report -> report.replacementPlans().stream()) + .filter(plan -> !plan.complete()) + .count(); + } + + public String firstReplacementPlanBlocker() { + return ruleReports.stream() + .flatMap(report -> report.replacementPlans().stream()) + .filter(plan -> !plan.complete()) + .map(GpuRuntimeIrPeepholeReplacementPlan::firstBlocker) + .findFirst() + .orElse("none"); + } + + public int invalidReplacementPlanValidationCount() { + return (int) ruleReports.stream() + .flatMap(report -> report.replacementPlanValidations().stream()) + .filter(validation -> !validation.valid()) + .count(); + } + + public String firstReplacementPlanValidationBlocker() { + return ruleReports.stream() + .flatMap(report -> report.replacementPlanValidations().stream()) + .filter(validation -> !validation.valid()) + .map(GpuRuntimeIrPeepholeReplacementPlanValidation::firstBlocker) + .findFirst() + .orElse("none"); + } } } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRuleReport.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRuleReport.java index 3eb9b5b7..1d6286d3 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRuleReport.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRuleReport.java @@ -15,6 +15,8 @@ public record GpuRuntimeIrPeepholeRuleReport( boolean mutationProposed, String proofStatus, Map fields, + List replacementPlans, + List replacementPlanValidations, List diagnostics ) { @@ -26,9 +28,40 @@ public record GpuRuntimeIrPeepholeRuleReport( proposalCount = Math.max(0, proposalCount); proofStatus = normalize(proofStatus, "not-proven"); fields = fields == null ? Map.of() : Map.copyOf(fields); + replacementPlans = replacementPlans == null ? List.of() : List.copyOf(replacementPlans); + replacementPlanValidations = replacementPlanValidations == null + ? List.of() + : List.copyOf(replacementPlanValidations); diagnostics = diagnostics == null ? List.of() : List.copyOf(diagnostics); } + public GpuRuntimeIrPeepholeRuleReport( + String ruleId, + String ruleVersion, + String methodName, + int candidateCount, + int proposalCount, + boolean mutationProposed, + String proofStatus, + Map fields, + List replacementPlans, + List diagnostics + ) { + this( + ruleId, + ruleVersion, + methodName, + candidateCount, + proposalCount, + mutationProposed, + proofStatus, + fields, + replacementPlans, + List.of(), + diagnostics + ); + } + public static GpuRuntimeIrPeepholeRuleReport diagnosticCandidates( GpuRuntimeIrPeepholeRule rule, String methodName, @@ -44,10 +77,52 @@ public static GpuRuntimeIrPeepholeRuleReport diagnosticCandidates( false, candidateCount > 0 ? "candidate-detected" : "no-candidate", fields, + List.of(), + List.of(), List.of("rule is diagnostic-only; no mutation proposal was emitted") ); } + public static GpuRuntimeIrPeepholeRuleReport diagnosticCandidates( + GpuRuntimeIrPeepholeRule rule, + String methodName, + int candidateCount, + Map fields, + List replacementPlans + ) { + return new GpuRuntimeIrPeepholeRuleReport( + rule.ruleId(), + rule.ruleVersion(), + methodName, + candidateCount, + 0, + false, + candidateCount > 0 ? "candidate-detected" : "no-candidate", + fields, + replacementPlans, + List.of(), + List.of("rule is diagnostic-only; no mutation proposal was emitted") + ); + } + + public GpuRuntimeIrPeepholeRuleReport withReplacementPlanValidations( + List validations + ) { + return new GpuRuntimeIrPeepholeRuleReport( + ruleId, + ruleVersion, + methodName, + candidateCount, + proposalCount, + mutationProposed, + proofStatus, + fields, + replacementPlans, + validations, + diagnostics + ); + } + private static String normalize(String value, String fallback) { return value == null || value.isBlank() ? fallback : value; } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrTypedNodeGraph.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrTypedNodeGraph.java new file mode 100644 index 00000000..50d01d28 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrTypedNodeGraph.java @@ -0,0 +1,172 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Immutable read-only graph view over one flattened typed IrGpu method body. + */ +public final class GpuRuntimeIrTypedNodeGraph { + + private final IrGpuTypedBody body; + private final Map nodesById; + + private GpuRuntimeIrTypedNodeGraph(IrGpuTypedBody body, Map nodesById) { + this.body = Objects.requireNonNull(body, "body"); + this.nodesById = Map.copyOf(nodesById == null ? indexNodes(body) : nodesById); + } + + public static GpuRuntimeIrTypedNodeGraph from(IrGpuTypedBody body) { + return new GpuRuntimeIrTypedNodeGraph(body, null); + } + + public static GpuRuntimeIrTypedNodeGraph from( + IrGpuTypedBody body, + Map nodesById + ) { + return new GpuRuntimeIrTypedNodeGraph(body, nodesById); + } + + public IrGpuTypedBody body() { + return body; + } + + public List rootNodeIds() { + return body.rootNodeIds(); + } + + public List nodes() { + return body.nodes(); + } + + public Map nodesById() { + return nodesById; + } + + public IrGpuTypedNode node(int id) { + return nodesById.get(id); + } + + public boolean containsNode(int id) { + return nodesById.containsKey(id); + } + + public List childIds(IrGpuTypedNode node, String... names) { + if (node == null || names == null || names.length == 0) { + return List.of(); + } + ArrayList ids = new ArrayList<>(); + for (String name : names) { + ids.addAll(node.children().getOrDefault(name, List.of())); + } + return List.copyOf(ids); + } + + public Integer singleChild(IrGpuTypedNode node, String... names) { + if (node == null || names == null) { + return null; + } + for (String name : names) { + List ids = node.children().getOrDefault(name, List.of()); + if (ids.size() == 1) { + return ids.get(0); + } + } + return null; + } + + public boolean isBinary(IrGpuTypedNode node, String operator) { + return node != null + && "GpuIrBinary".equals(node.kind()) + && Objects.equals(operator, node.attributes().get("operator")); + } + + public boolean isCall(IrGpuTypedNode node, String name) { + if (node == null) { + return false; + } + return ("GpuIrCall".equals(node.kind()) + || "GpuIrIntrinsicCall".equals(node.kind()) + || "GpuIrFunctionCall".equals(node.kind())) + && Objects.equals(name, callName(node)); + } + + public String callName(IrGpuTypedNode node) { + return attribute(node, "name", "function", "intrinsic"); + } + + public List callArguments(IrGpuTypedNode node) { + List args = childIds(node, "args"); + return args.isEmpty() ? childIds(node, "arguments") : args; + } + + public boolean isConditional(IrGpuTypedNode node) { + return node != null && ("GpuIrConditional".equals(node.kind()) + || "GpuIrTernary".equals(node.kind()) + || "GpuIrSelect".equals(node.kind())); + } + + public String literalText(IrGpuTypedNode node) { + if (node == null || !"GpuIrLiteral".equals(node.kind())) { + return ""; + } + return attribute(node, "sourceText", "value", "literal"); + } + + public String attribute(IrGpuTypedNode node, String... names) { + if (node == null || names == null) { + return ""; + } + for (String name : names) { + String value = node.attributes().get(name); + if (value != null) { + return value; + } + } + return ""; + } + + public List missingNodeIds(Collection ids) { + if (ids == null || ids.isEmpty()) { + return List.of(); + } + ArrayList missing = new ArrayList<>(); + for (Integer id : ids) { + if (id != null && !nodesById.containsKey(id)) { + missing.add(id); + } + } + return List.copyOf(missing); + } + + public static List presentIds(Integer... ids) { + if (ids == null || ids.length == 0) { + return List.of(); + } + ArrayList present = new ArrayList<>(); + for (Integer id : ids) { + if (id != null) { + present.add(id); + } + } + return List.copyOf(present); + } + + private static Map indexNodes(IrGpuTypedBody body) { + LinkedHashMap indexed = new LinkedHashMap<>(); + for (IrGpuTypedNode node : body.nodes()) { + IrGpuTypedNode previous = indexed.putIfAbsent(node.id(), node); + if (previous != null) { + throw new IllegalArgumentException("Duplicate typed IrGpu node id '" + node.id() + "'"); + } + } + return indexed; + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeMadFmaPeepholeRule.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeMadFmaPeepholeRule.java index 37fa784a..0ea4e6d0 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeMadFmaPeepholeRule.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeMadFmaPeepholeRule.java @@ -2,6 +2,7 @@ import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -16,20 +17,35 @@ public final class GpuRuntimeMadFmaPeepholeRule implements GpuRuntimeIrPeepholeR @Override public GpuRuntimeIrPeepholeRuleReport analyze(GpuRuntimeIrPeepholeRuleContext context) { int candidates = 0; - for (IrGpuTypedNode node : context.typedBody().nodes()) { - if (!isBinary(node, "+")) { + ArrayList plans = new ArrayList<>(); + for (IrGpuTypedNode node : context.graph().nodes()) { + if (!context.graph().isBinary(node, "+")) { continue; } - if (childIsBinary(node, "left", "*", context.nodesById()) - || childIsBinary(node, "right", "*", context.nodesById())) { + GpuRuntimeIrPeepholeReplacementPlan plan = replacementPlan(context, node); + if (plan.complete()) { candidates++; } + if (!"not-mad-fma-shape".equals(plan.firstBlocker())) { + plans.add(plan); + } } return GpuRuntimeIrPeepholeRuleReport.diagnosticCandidates( this, context.methodBody().name(), candidates, - Map.of("pattern", "a*b+c|c+a*b") + Map.of( + "pattern", "a*b+c|c+a*b", + "replacementPlan.count", Integer.toString(plans.size()), + "replacementPlan.complete.count", Long.toString(plans.stream().filter(GpuRuntimeIrPeepholeReplacementPlan::complete).count()), + "replacementPlan.partial.count", Long.toString(plans.stream().filter(plan -> !plan.complete()).count()), + "replacementPlan.firstBlocker", plans.stream() + .filter(plan -> !plan.complete()) + .map(GpuRuntimeIrPeepholeReplacementPlan::firstBlocker) + .findFirst() + .orElse("none") + ), + plans ); } @@ -48,22 +64,61 @@ public String extensionId() { return "javatogpu.peephole.mad-fma"; } - private static boolean childIsBinary( - IrGpuTypedNode parent, - String childName, - String operator, - Map nodesById + private GpuRuntimeIrPeepholeReplacementPlan replacementPlan( + GpuRuntimeIrPeepholeRuleContext context, + IrGpuTypedNode addNode ) { - List childIds = parent.children().getOrDefault(childName, List.of()); - if (childIds.size() != 1) { - return false; + GpuRuntimeIrTypedNodeGraph graph = context.graph(); + Integer left = graph.singleChild(addNode, "left"); + Integer right = graph.singleChild(addNode, "right"); + if (left == null || right == null) { + return GpuRuntimeIrPeepholeReplacementPlan.blocked( + RULE_ID, + context.methodBody().name(), + addNode.id(), + "mad-fma", + List.of(addNode.id()), + List.of(), + "add-operands-incomplete" + ); } - return isBinary(nodesById.get(childIds.get(0)), operator); - } - - private static boolean isBinary(IrGpuTypedNode node, String operator) { - return node != null - && "GpuIrBinary".equals(node.kind()) - && operator.equals(node.attributes().get("operator")); + IrGpuTypedNode leftNode = graph.node(left); + IrGpuTypedNode rightNode = graph.node(right); + boolean leftMultiply = graph.isBinary(leftNode, "*"); + boolean rightMultiply = graph.isBinary(rightNode, "*"); + if (!leftMultiply && !rightMultiply) { + return GpuRuntimeIrPeepholeReplacementPlan.blocked( + RULE_ID, + context.methodBody().name(), + addNode.id(), + "mad-fma", + List.of(addNode.id()), + List.of(left, right), + "not-mad-fma-shape" + ); + } + IrGpuTypedNode multiply = leftMultiply ? leftNode : rightNode; + int addendId = leftMultiply ? right : left; + Integer multiplyLeft = graph.singleChild(multiply, "left"); + Integer multiplyRight = graph.singleChild(multiply, "right"); + if (multiplyLeft == null || multiplyRight == null) { + return GpuRuntimeIrPeepholeReplacementPlan.blocked( + RULE_ID, + context.methodBody().name(), + addNode.id(), + "mad-fma", + List.of(addNode.id(), multiply.id()), + List.of(addendId), + "multiply-operands-incomplete" + ); + } + return GpuRuntimeIrPeepholeReplacementPlan.complete( + RULE_ID, + context.methodBody().name(), + addNode.id(), + "mad-fma", + List.of(addNode.id(), multiply.id()), + List.of(multiplyLeft, multiplyRight, addendId) + ); } } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeMixPeepholeRule.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeMixPeepholeRule.java new file mode 100644 index 00000000..a8bef177 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeMixPeepholeRule.java @@ -0,0 +1,191 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Detects typed {@code a + t * (b - a)} and {@code t * (b - a) + a} candidates. + */ +public final class GpuRuntimeMixPeepholeRule implements GpuRuntimeIrPeepholeRule { + + public static final String RULE_ID = "mix"; + public static final String VERSION = "peephole-rule:mix-v1"; + + @Override + public GpuRuntimeIrPeepholeRuleReport analyze(GpuRuntimeIrPeepholeRuleContext context) { + int candidates = 0; + ArrayList plans = new ArrayList<>(); + for (IrGpuTypedNode node : context.graph().nodes()) { + if (!context.graph().isBinary(node, "+")) { + continue; + } + GpuRuntimeIrPeepholeReplacementPlan plan = replacementPlan(context, node); + if (plan.complete()) { + candidates++; + } + if (!"not-mix-shape".equals(plan.firstBlocker())) { + plans.add(plan); + } + } + return GpuRuntimeIrPeepholeRuleReport.diagnosticCandidates( + this, + context.methodBody().name(), + candidates, + Map.of( + "pattern", "a+t*(b-a)|t*(b-a)+a", + "replacementPlan.count", Integer.toString(plans.size()), + "replacementPlan.complete.count", Long.toString(plans.stream().filter(GpuRuntimeIrPeepholeReplacementPlan::complete).count()), + "replacementPlan.partial.count", Long.toString(plans.stream().filter(plan -> !plan.complete()).count()), + "replacementPlan.firstBlocker", plans.stream() + .filter(plan -> !plan.complete()) + .map(GpuRuntimeIrPeepholeReplacementPlan::firstBlocker) + .findFirst() + .orElse("none") + ), + plans + ); + } + + @Override + public String ruleId() { + return RULE_ID; + } + + @Override + public String ruleVersion() { + return VERSION; + } + + @Override + public String extensionId() { + return "javatogpu.peephole.mix"; + } + + @Override + public int extensionOrder() { + return 100; + } + + private GpuRuntimeIrPeepholeReplacementPlan replacementPlan( + GpuRuntimeIrPeepholeRuleContext context, + IrGpuTypedNode addNode + ) { + GpuRuntimeIrTypedNodeGraph graph = context.graph(); + Integer left = graph.singleChild(addNode, "left"); + Integer right = graph.singleChild(addNode, "right"); + if (left == null || right == null) { + return GpuRuntimeIrPeepholeReplacementPlan.blocked( + RULE_ID, + context.methodBody().name(), + addNode.id(), + "mix", + List.of(addNode.id()), + GpuRuntimeIrTypedNodeGraph.presentIds(left, right), + "mix-add-operands-incomplete" + ); + } + GpuRuntimeIrPeepholeReplacementPlan leftPlan = planWithBaseAndProduct(context, addNode, left, right); + if (leftPlan.complete() || !"not-mix-shape".equals(leftPlan.firstBlocker())) { + return leftPlan; + } + return planWithBaseAndProduct(context, addNode, right, left); + } + + private static GpuRuntimeIrPeepholeReplacementPlan planWithBaseAndProduct( + GpuRuntimeIrPeepholeRuleContext context, + IrGpuTypedNode addNode, + int baseId, + int productId + ) { + GpuRuntimeIrTypedNodeGraph graph = context.graph(); + IrGpuTypedNode product = graph.node(productId); + if (!graph.isBinary(product, "*")) { + return blockedNotMix(context, addNode, baseId, productId); + } + Integer productLeft = graph.singleChild(product, "left"); + Integer productRight = graph.singleChild(product, "right"); + if (productLeft == null || productRight == null) { + return GpuRuntimeIrPeepholeReplacementPlan.blocked( + RULE_ID, + context.methodBody().name(), + addNode.id(), + "mix", + List.of(addNode.id(), product.id()), + List.of(baseId), + "mix-multiply-operands-incomplete" + ); + } + GpuRuntimeIrPeepholeReplacementPlan leftDelta = planWithDelta(context, addNode, product, baseId, productLeft, productRight); + if (leftDelta.complete() || !"not-mix-shape".equals(leftDelta.firstBlocker())) { + return leftDelta; + } + return planWithDelta(context, addNode, product, baseId, productRight, productLeft); + } + + private static GpuRuntimeIrPeepholeReplacementPlan planWithDelta( + GpuRuntimeIrPeepholeRuleContext context, + IrGpuTypedNode addNode, + IrGpuTypedNode product, + int baseId, + int deltaId, + int tId + ) { + GpuRuntimeIrTypedNodeGraph graph = context.graph(); + IrGpuTypedNode delta = graph.node(deltaId); + if (!graph.isBinary(delta, "-")) { + return blockedNotMix(context, addNode, baseId, product.id()); + } + Integer bId = graph.singleChild(delta, "left"); + Integer deltaBaseId = graph.singleChild(delta, "right"); + if (bId == null || deltaBaseId == null) { + return GpuRuntimeIrPeepholeReplacementPlan.blocked( + RULE_ID, + context.methodBody().name(), + addNode.id(), + "mix", + List.of(addNode.id(), product.id(), delta.id()), + List.of(baseId, tId), + "mix-delta-operands-incomplete" + ); + } + if (deltaBaseId != baseId) { + return GpuRuntimeIrPeepholeReplacementPlan.blocked( + RULE_ID, + context.methodBody().name(), + addNode.id(), + "mix", + List.of(addNode.id(), product.id(), delta.id()), + List.of(baseId, bId, deltaBaseId, tId), + "mix-base-mismatch" + ); + } + return GpuRuntimeIrPeepholeReplacementPlan.complete( + RULE_ID, + context.methodBody().name(), + addNode.id(), + "mix", + List.of(addNode.id(), product.id(), delta.id()), + List.of(baseId, bId, tId) + ); + } + + private static GpuRuntimeIrPeepholeReplacementPlan blockedNotMix( + GpuRuntimeIrPeepholeRuleContext context, + IrGpuTypedNode addNode, + int left, + int right + ) { + return GpuRuntimeIrPeepholeReplacementPlan.blocked( + RULE_ID, + context.methodBody().name(), + addNode.id(), + "mix", + List.of(addNode.id()), + List.of(left, right), + "not-mix-shape" + ); + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeOptimizerDriftArtifact.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeOptimizerDriftArtifact.java index 341b31c8..8bb6cb90 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeOptimizerDriftArtifact.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeOptimizerDriftArtifact.java @@ -23,6 +23,16 @@ public record GpuRuntimeOptimizerDriftArtifact( int proofArtifactCount, int acceptedProofArtifactCount, int blockingProofArtifactCount, + int replacementPlanCompleteCount, + int replacementPlanPartialCount, + String replacementPlanFirstBlocker, + int replacementPlanValidationCount, + int replacementPlanValidationValidCount, + int replacementPlanValidationInvalidCount, + String replacementPlanValidationFirstBlocker, + int optimizerRuleCount, + String optimizerRuleSummary, + String optimizerRuleDetailsProperties, int optimizerFamilyCount, int optimizerFamilyPromotionReadyCount, String optimizerFamilySummary, @@ -51,6 +61,16 @@ public static GpuRuntimeOptimizerDriftArtifact from(GpuRuntimeCompileArtifactSna 0, 0, 0, + "none", + 0, + 0, + 0, + "none", + 0, + "none", + "", + 0, + 0, "", "not-requested", false @@ -63,6 +83,7 @@ public static GpuRuntimeOptimizerDriftArtifact from(GpuRuntimeCompileArtifactSna GpuRuntimeProductionOptimizerGate gate = snapshot.productionOptimizerGate(); GpuRuntimeIrSelection selection = snapshot.runtimeIrSelection(); Map optimizerFamilies = optimizerFamilies(report); + Map optimizerRules = optimizerRules(report); return new GpuRuntimeOptimizerDriftArtifact( optimizationPassCount(report), count(report, GpuRuntimeIrOptimizationOutcome.APPLIED), @@ -80,6 +101,16 @@ public static GpuRuntimeOptimizerDriftArtifact from(GpuRuntimeCompileArtifactSna proofArtifactCount(report), acceptedProofArtifactCount(report), blockingProofArtifactCount(report), + replacementPlanCompleteCount(report), + replacementPlanPartialCount(report), + replacementPlanFirstBlocker(report), + replacementPlanValidationCount(report), + replacementPlanValidationValidCount(report), + replacementPlanValidationInvalidCount(report), + replacementPlanValidationFirstBlocker(report), + optimizerRules.size(), + formatOptimizerRuleSummary(optimizerRules), + formatOptimizerRuleProperties(optimizerRules), optimizerFamilies.size(), promotionReadyFamilyCount(optimizerFamilies), formatOptimizerFamilySummary(optimizerFamilies), @@ -112,6 +143,18 @@ public String toPropertiesText() { builder.append("proofArtifact.count=").append(proofArtifactCount).append('\n'); builder.append("proofArtifact.accepted.count=").append(acceptedProofArtifactCount).append('\n'); builder.append("proofArtifact.blocking.count=").append(blockingProofArtifactCount).append('\n'); + builder.append("replacementPlan.complete.count=").append(replacementPlanCompleteCount).append('\n'); + builder.append("replacementPlan.partial.count=").append(replacementPlanPartialCount).append('\n'); + builder.append("replacementPlan.firstBlocker=").append(replacementPlanFirstBlocker).append('\n'); + builder.append("replacementPlan.validation.count=").append(replacementPlanValidationCount).append('\n'); + builder.append("replacementPlan.validation.valid.count=").append(replacementPlanValidationValidCount).append('\n'); + builder.append("replacementPlan.validation.invalid.count=").append(replacementPlanValidationInvalidCount).append('\n'); + builder.append("replacementPlan.validation.firstBlocker=").append(replacementPlanValidationFirstBlocker).append('\n'); + builder.append("optimizerRule.count=").append(optimizerRuleCount).append('\n'); + builder.append("optimizerRule.summary=").append(optimizerRuleSummary).append('\n'); + if (optimizerRuleDetailsProperties != null && !optimizerRuleDetailsProperties.isBlank()) { + builder.append(optimizerRuleDetailsProperties); + } builder.append("optimizerFamily.count=").append(optimizerFamilyCount).append('\n'); builder.append("optimizerFamily.promotionReady.count=").append(optimizerFamilyPromotionReadyCount).append('\n'); builder.append("optimizerFamily.summary=").append(optimizerFamilySummary).append('\n'); @@ -150,6 +193,209 @@ private static int blockingProofArtifactCount(GpuRuntimeIrOptimizationReport rep .count(); } + private static int replacementPlanCompleteCount(GpuRuntimeIrOptimizationReport report) { + return replacementPlanRuleCount(report, ".replacementPlan.complete.count"); + } + + private static int replacementPlanPartialCount(GpuRuntimeIrOptimizationReport report) { + int total = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + Map fields = passReport.proofArtifact().fields(); + int passTotal = parseInt(fields.get("replacementPlan.partial.count")); + total += passTotal > 0 ? passTotal : replacementPlanRuleCount(fields, ".replacementPlan.partial.count"); + } + } + return total; + } + + private static int replacementPlanRuleCount(GpuRuntimeIrOptimizationReport report, String suffix) { + int total = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + total += replacementPlanRuleCount(passReport.proofArtifact().fields(), suffix); + } + } + return total; + } + + private static int replacementPlanRuleCount(Map fields, String suffix) { + return fields.entrySet().stream() + .filter(entry -> entry.getKey().startsWith("rule.")) + .filter(entry -> entry.getKey().endsWith(suffix)) + .mapToInt(entry -> parseInt(entry.getValue())) + .sum(); + } + + private static String replacementPlanFirstBlocker(GpuRuntimeIrOptimizationReport report) { + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + String blocker = passReport.proofArtifact().fields().getOrDefault("replacementPlan.firstBlocker", "none"); + if (!blocker.isBlank() && !"none".equals(blocker)) { + return blocker; + } + } + } + return "none"; + } + + private static int replacementPlanValidationCount(GpuRuntimeIrOptimizationReport report) { + return replacementPlanRuleCount(report, ".replacementPlan.validation.count"); + } + + private static int replacementPlanValidationValidCount(GpuRuntimeIrOptimizationReport report) { + return replacementPlanRuleCount(report, ".replacementPlan.validation.valid.count"); + } + + private static int replacementPlanValidationInvalidCount(GpuRuntimeIrOptimizationReport report) { + int total = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + Map fields = passReport.proofArtifact().fields(); + int passTotal = parseInt(fields.get("replacementPlan.validation.invalid.count")); + total += passTotal > 0 ? passTotal : replacementPlanRuleCount(fields, ".replacementPlan.validation.invalid.count"); + } + } + return total; + } + + private static String replacementPlanValidationFirstBlocker(GpuRuntimeIrOptimizationReport report) { + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + Map fields = passReport.proofArtifact().fields(); + String blocker = fields.getOrDefault("replacementPlan.validation.firstBlocker", "none"); + if (!blocker.isBlank() && !"none".equals(blocker)) { + return blocker; + } + String ruleBlocker = firstRuleBlocker(fields, ".replacementPlan.validation.firstBlocker"); + if (!"none".equals(ruleBlocker)) { + return ruleBlocker; + } + } + } + return "none"; + } + + private static String firstRuleBlocker(Map fields, String suffix) { + for (Map.Entry entry : fields.entrySet()) { + String blocker = entry.getValue(); + if (entry.getKey().startsWith("rule.") + && entry.getKey().endsWith(suffix) + && blocker != null + && !blocker.isBlank() + && !"none".equals(blocker)) { + return blocker; + } + } + return "none"; + } + + private static Map optimizerRules(GpuRuntimeIrOptimizationReport report) { + Map rules = new LinkedHashMap<>(); + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (passReport.analysisOnly() || !hasProofArtifact(passReport)) { + continue; + } + Map fields = passReport.proofArtifact().fields(); + int ruleCount = parseInt(fields.get("rule.count")); + for (int index = 0; index < ruleCount; index++) { + String prefix = "rule." + index; + String id = fields.getOrDefault(prefix + ".id", ""); + if (id.isBlank()) { + continue; + } + OptimizerRuleEvidence existing = rules.getOrDefault(id, OptimizerRuleEvidence.empty(id)); + rules.put(id, existing.add(fields, prefix)); + } + } + return rules; + } + + private static String formatOptimizerRuleSummary(Map rules) { + if (rules.isEmpty()) { + return "none"; + } + StringBuilder builder = new StringBuilder(); + for (OptimizerRuleEvidence rule : rules.values()) { + if (!builder.isEmpty()) { + builder.append(", "); + } + builder.append(rule.id()) + .append("[candidates=") + .append(rule.candidateCount()) + .append(", proposals=") + .append(rule.proposalCount()) + .append(", applied=") + .append(rule.appliedCount()) + .append(", skipped=") + .append(rule.skippedCount()) + .append(", blocked=") + .append(rule.blockedCount()) + .append(", mutationProposed=") + .append(rule.mutationProposed()) + .append(", replacementPlans=") + .append(rule.replacementPlanCount()) + .append(", completePlans=") + .append(rule.replacementPlanCompleteCount()) + .append(", partialPlans=") + .append(rule.replacementPlanPartialCount()) + .append(", planValidations=") + .append(rule.replacementPlanValidationCount()) + .append(", invalidPlanValidations=") + .append(rule.replacementPlanValidationInvalidCount()) + .append(", planValidationFirstBlocker=") + .append(rule.replacementPlanValidationFirstBlocker()) + .append(", firstBlocker=") + .append(rule.firstBlocker()) + .append(']'); + } + return builder.toString(); + } + + private static String formatOptimizerRuleProperties(Map rules) { + if (rules.isEmpty()) { + return ""; + } + StringBuilder builder = new StringBuilder(); + int index = 0; + for (OptimizerRuleEvidence rule : rules.values()) { + String prefix = "optimizerRule." + index + "."; + builder.append(prefix).append("id=").append(rule.id()).append('\n'); + builder.append(prefix).append("version=").append(rule.version()).append('\n'); + builder.append(prefix).append("extensionId=").append(rule.extensionId()).append('\n'); + builder.append(prefix).append("extensionVersion=").append(rule.extensionVersion()).append('\n'); + builder.append(prefix).append("proofStatus=").append(rule.proofStatus()).append('\n'); + builder.append(prefix).append("candidate.count=").append(rule.candidateCount()).append('\n'); + builder.append(prefix).append("proposal.count=").append(rule.proposalCount()).append('\n'); + builder.append(prefix).append("applied.count=").append(rule.appliedCount()).append('\n'); + builder.append(prefix).append("skipped.count=").append(rule.skippedCount()).append('\n'); + builder.append(prefix).append("blocked.count=").append(rule.blockedCount()).append('\n'); + builder.append(prefix).append("mutationProposed=").append(rule.mutationProposed()).append('\n'); + builder.append(prefix).append("replacementPlan.count=").append(rule.replacementPlanCount()).append('\n'); + builder.append(prefix).append("replacementPlan.complete.count=").append(rule.replacementPlanCompleteCount()).append('\n'); + builder.append(prefix).append("replacementPlan.partial.count=").append(rule.replacementPlanPartialCount()).append('\n'); + builder.append(prefix).append("replacementPlan.firstBlocker=").append(rule.firstBlocker()).append('\n'); + builder.append(prefix).append("replacementPlan.validation.count=").append(rule.replacementPlanValidationCount()).append('\n'); + builder.append(prefix).append("replacementPlan.validation.valid.count=").append(rule.replacementPlanValidationValidCount()).append('\n'); + builder.append(prefix).append("replacementPlan.validation.invalid.count=").append(rule.replacementPlanValidationInvalidCount()).append('\n'); + builder.append(prefix).append("replacementPlan.validation.firstBlocker=").append(rule.replacementPlanValidationFirstBlocker()).append('\n'); + builder.append(prefix).append("firstBlocker=").append(rule.firstBlocker()).append('\n'); + index++; + } + return builder.toString(); + } + + private static int parseInt(String value) { + if (value == null || value.isBlank()) { + return 0; + } + try { + return Integer.parseInt(value); + } catch (NumberFormatException ignored) { + return 0; + } + } + private static boolean hasProofArtifact(GpuRuntimeIrOptimizationPassReport passReport) { GpuRuntimeIrOptimizationProofArtifact proofArtifact = passReport.proofArtifact(); return proofArtifact != null @@ -261,4 +507,97 @@ private boolean promotionReady() { return acceptedProofCount > 0 && blockingProofCount == 0 && rolledBackCount == 0 && failedCount == 0; } } + + private record OptimizerRuleEvidence( + String id, + String version, + String extensionId, + String extensionVersion, + String proofStatus, + int candidateCount, + int proposalCount, + int appliedCount, + int skippedCount, + int blockedCount, + boolean mutationProposed, + int replacementPlanCount, + int replacementPlanCompleteCount, + int replacementPlanPartialCount, + int replacementPlanValidationCount, + int replacementPlanValidationValidCount, + int replacementPlanValidationInvalidCount, + String replacementPlanValidationFirstBlocker, + String firstBlocker + ) { + + private static OptimizerRuleEvidence empty(String id) { + return new OptimizerRuleEvidence( + id, + "unknown", + "unknown", + "unknown", + "unknown", + 0, + 0, + 0, + 0, + 0, + false, + 0, + 0, + 0, + 0, + 0, + 0, + "none", + "none" + ); + } + + private OptimizerRuleEvidence add(Map fields, String prefix) { + String blocker = firstBlocker; + String validationBlocker = replacementPlanValidationFirstBlocker; + String nextValidationBlocker = fields.getOrDefault(prefix + ".replacementPlan.validation.firstBlocker", "none"); + if ("none".equals(validationBlocker) + && !nextValidationBlocker.isBlank() + && !"none".equals(nextValidationBlocker)) { + validationBlocker = nextValidationBlocker; + } + String nextBlocker = fields.getOrDefault( + prefix + ".firstBlocker", + fields.getOrDefault(prefix + ".replacementPlan.firstBlocker", "none") + ); + if ("none".equals(blocker) && !nextBlocker.isBlank() && !"none".equals(nextBlocker)) { + blocker = nextBlocker; + } + return new OptimizerRuleEvidence( + id, + firstKnown(version, fields.getOrDefault(prefix + ".version", "unknown")), + firstKnown(extensionId, fields.getOrDefault(prefix + ".extensionId", "unknown")), + firstKnown(extensionVersion, fields.getOrDefault(prefix + ".extensionVersion", "unknown")), + firstKnown(proofStatus, fields.getOrDefault(prefix + ".proofStatus", "unknown")), + candidateCount + parseInt(fields.get(prefix + ".candidate.count")), + proposalCount + parseInt(fields.get(prefix + ".proposal.count")), + appliedCount + parseInt(fields.get(prefix + ".applied.count")), + skippedCount + parseInt(fields.get(prefix + ".skipped.count")), + blockedCount + parseInt(fields.get(prefix + ".blocked.count")), + mutationProposed || Boolean.parseBoolean(fields.getOrDefault(prefix + ".mutationProposed", "false")), + replacementPlanCount + parseInt(fields.get(prefix + ".replacementPlan.count")), + replacementPlanCompleteCount + parseInt(fields.get(prefix + ".replacementPlan.complete.count")), + replacementPlanPartialCount + parseInt(fields.get(prefix + ".replacementPlan.partial.count")), + replacementPlanValidationCount + parseInt(fields.get(prefix + ".replacementPlan.validation.count")), + replacementPlanValidationValidCount + parseInt(fields.get(prefix + ".replacementPlan.validation.valid.count")), + replacementPlanValidationInvalidCount + parseInt(fields.get(prefix + ".replacementPlan.validation.invalid.count")), + validationBlocker, + blocker + ); + } + + private static String firstKnown(String current, String next) { + if (current != null && !current.isBlank() && !"unknown".equals(current)) { + return current; + } + return next == null || next.isBlank() ? "unknown" : next; + } + } } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeStepPeepholeRule.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeStepPeepholeRule.java new file mode 100644 index 00000000..b389a6e3 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeStepPeepholeRule.java @@ -0,0 +1,175 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Detects simple typed ternary forms that can become {@code step(edge, x)}. + */ +public final class GpuRuntimeStepPeepholeRule implements GpuRuntimeIrPeepholeRule { + + public static final String RULE_ID = "step"; + public static final String VERSION = "peephole-rule:step-v1"; + + @Override + public GpuRuntimeIrPeepholeRuleReport analyze(GpuRuntimeIrPeepholeRuleContext context) { + int candidates = 0; + ArrayList plans = new ArrayList<>(); + for (IrGpuTypedNode node : context.graph().nodes()) { + if (!context.graph().isConditional(node)) { + continue; + } + GpuRuntimeIrPeepholeReplacementPlan plan = replacementPlan(context, node); + if (plan.complete()) { + candidates++; + } + if (!"not-step-shape".equals(plan.firstBlocker())) { + plans.add(plan); + } + } + return GpuRuntimeIrPeepholeRuleReport.diagnosticCandidates( + this, + context.methodBody().name(), + candidates, + Map.of( + "pattern", "x=edge?1:0", + "replacementPlan.count", Integer.toString(plans.size()), + "replacementPlan.complete.count", Long.toString(plans.stream().filter(GpuRuntimeIrPeepholeReplacementPlan::complete).count()), + "replacementPlan.partial.count", Long.toString(plans.stream().filter(plan -> !plan.complete()).count()), + "replacementPlan.firstBlocker", plans.stream() + .filter(plan -> !plan.complete()) + .map(GpuRuntimeIrPeepholeReplacementPlan::firstBlocker) + .findFirst() + .orElse("none") + ), + plans + ); + } + + @Override + public String ruleId() { + return RULE_ID; + } + + @Override + public String ruleVersion() { + return VERSION; + } + + @Override + public String extensionId() { + return "javatogpu.peephole.step"; + } + + @Override + public int extensionOrder() { + return 100; + } + + private GpuRuntimeIrPeepholeReplacementPlan replacementPlan( + GpuRuntimeIrPeepholeRuleContext context, + IrGpuTypedNode conditional + ) { + GpuRuntimeIrTypedNodeGraph graph = context.graph(); + Integer conditionId = graph.singleChild(conditional, "condition", "cond"); + Integer thenId = graph.singleChild(conditional, "then", "true", "ifTrue"); + Integer elseId = graph.singleChild(conditional, "else", "false", "ifFalse"); + if (conditionId == null || thenId == null || elseId == null) { + return GpuRuntimeIrPeepholeReplacementPlan.blocked( + RULE_ID, + context.methodBody().name(), + conditional.id(), + "step", + List.of(conditional.id()), + GpuRuntimeIrTypedNodeGraph.presentIds(conditionId, thenId, elseId), + "ternary-operands-incomplete" + ); + } + IrGpuTypedNode condition = graph.node(conditionId); + if (condition == null || !"GpuIrBinary".equals(condition.kind())) { + return blockedNotStep(context, conditional, conditionId, thenId, elseId); + } + Integer left = graph.singleChild(condition, "left"); + Integer right = graph.singleChild(condition, "right"); + if (left == null || right == null) { + return GpuRuntimeIrPeepholeReplacementPlan.blocked( + RULE_ID, + context.methodBody().name(), + conditional.id(), + "step", + List.of(conditional.id(), condition.id()), + List.of(thenId, elseId), + "comparison-operands-incomplete" + ); + } + IrGpuTypedNode thenNode = graph.node(thenId); + IrGpuTypedNode elseNode = graph.node(elseId); + String operator = graph.attribute(condition, "operator"); + if ("<".equals(operator) && isZero(graph, thenNode) && isOne(graph, elseNode)) { + return complete(context, conditional, condition, right, left); + } + if (">=".equals(operator) && isOne(graph, thenNode) && isZero(graph, elseNode)) { + return complete(context, conditional, condition, right, left); + } + if (("<".equals(operator) || ">=".equals(operator)) + && (isZero(graph, thenNode) || isOne(graph, thenNode) + || isZero(graph, elseNode) || isOne(graph, elseNode))) { + return GpuRuntimeIrPeepholeReplacementPlan.blocked( + RULE_ID, + context.methodBody().name(), + conditional.id(), + "step", + List.of(conditional.id(), condition.id()), + List.of(left, right, thenId, elseId), + "step-branch-values-unsupported" + ); + } + return blockedNotStep(context, conditional, conditionId, thenId, elseId); + } + + private static GpuRuntimeIrPeepholeReplacementPlan complete( + GpuRuntimeIrPeepholeRuleContext context, + IrGpuTypedNode conditional, + IrGpuTypedNode condition, + int edgeId, + int valueId + ) { + return GpuRuntimeIrPeepholeReplacementPlan.complete( + RULE_ID, + context.methodBody().name(), + conditional.id(), + "step", + List.of(conditional.id(), condition.id()), + List.of(edgeId, valueId) + ); + } + + private static GpuRuntimeIrPeepholeReplacementPlan blockedNotStep( + GpuRuntimeIrPeepholeRuleContext context, + IrGpuTypedNode conditional, + int conditionId, + int thenId, + int elseId + ) { + return GpuRuntimeIrPeepholeReplacementPlan.blocked( + RULE_ID, + context.methodBody().name(), + conditional.id(), + "step", + List.of(conditional.id()), + List.of(conditionId, thenId, elseId), + "not-step-shape" + ); + } + + private static boolean isZero(GpuRuntimeIrTypedNodeGraph graph, IrGpuTypedNode node) { + return graph.literalText(node).matches("[+]?0(?:\\.0+)?[fFdD]?"); + } + + private static boolean isOne(GpuRuntimeIrTypedNodeGraph graph, IrGpuTypedNode node) { + return graph.literalText(node).matches("[+]?1(?:\\.0+)?[fFdD]?"); + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReporter.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReporter.java index 16a9f3f0..bdb2da17 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReporter.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReporter.java @@ -12,6 +12,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Optional; +import java.util.Properties; /** * Small CLI entry point for generating a vendor-validation snapshot report. @@ -1072,6 +1073,24 @@ private static void appendBackendSourcePromotionWorkloadKernelSummary( .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.proofArtifact.accepted.count", "0"))) .append("`, blockingProof=`") .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.proofArtifact.blocking.count", "0"))) + .append("`, replacementPlanComplete=`") + .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.replacementPlan.complete.count", "0"))) + .append("`, replacementPlanPartial=`") + .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.replacementPlan.partial.count", "0"))) + .append("`, replacementPlanFirstBlocker=`") + .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.replacementPlan.firstBlocker", "none"))) + .append("`, replacementPlanValidation=`") + .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.replacementPlan.validation.valid.count", "0"))) + .append("/") + .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.replacementPlan.validation.count", "0"))) + .append(" valid, invalid=") + .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.replacementPlan.validation.invalid.count", "0"))) + .append("`, replacementPlanValidationFirstBlocker=`") + .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.replacementPlan.validation.firstBlocker", "none"))) + .append("`, optimizerRules=`") + .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.optimizerRule.count", "0"))) + .append("`, optimizerRuleDetails=`") + .append(sanitizeInline(kernelOptimizerRuleDetails(properties, prefix))) .append("`, optimizerFamilies=`") .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.optimizerFamily.count", "0"))) .append("`, promotionReadyFamilies=`") @@ -1431,6 +1450,16 @@ private static String formatI3ReadinessWorkloadSummary(java.util.Properties gate builder.append(prefix).append("optimizerDriftStatus=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.status", "not-recorded")).append('\n'); builder.append(prefix).append("optimizerDriftPassCount=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.pass.count", "0")).append('\n'); builder.append(prefix).append("optimizerDriftRolledBackCount=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.pass.rolledBack.count", "0")).append('\n'); + builder.append(prefix).append("optimizerDriftReplacementPlanCompleteCount=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.replacementPlan.complete.count", "0")).append('\n'); + builder.append(prefix).append("optimizerDriftReplacementPlanPartialCount=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.replacementPlan.partial.count", "0")).append('\n'); + builder.append(prefix).append("optimizerDriftReplacementPlanFirstBlocker=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.replacementPlan.firstBlocker", "none")).append('\n'); + builder.append(prefix).append("optimizerDriftReplacementPlanValidationCount=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.replacementPlan.validation.count", "0")).append('\n'); + builder.append(prefix).append("optimizerDriftReplacementPlanValidationValidCount=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.replacementPlan.validation.valid.count", "0")).append('\n'); + builder.append(prefix).append("optimizerDriftReplacementPlanValidationInvalidCount=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.replacementPlan.validation.invalid.count", "0")).append('\n'); + builder.append(prefix).append("optimizerDriftReplacementPlanValidationFirstBlocker=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.replacementPlan.validation.firstBlocker", "none")).append('\n'); + builder.append(prefix).append("optimizerDriftRuleCount=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.optimizerRule.count", "0")).append('\n'); + builder.append(prefix).append("optimizerDriftRuleSummary=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.optimizerRule.summary", "none")).append('\n'); + builder.append(prefix).append("optimizerDriftRuleDetails=").append(kernelOptimizerRuleDetails(gate, prefix)).append('\n'); builder.append(prefix).append("optimizerDriftFallbackDecision=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.fallbackDecision", "unknown")).append('\n'); builder.append(prefix).append("sourceReady=").append(gate.getProperty(prefix + "i3Readiness.sourceReady", "unknown")).append('\n'); builder.append(prefix).append("sourcePromotionStatus=").append(gate.getProperty(prefix + "i3Readiness.sourcePromotionStatus", "unknown")).append('\n'); @@ -1452,6 +1481,53 @@ private static String normalizeI3ReadinessStatus(String status) { }; } + private static String kernelOptimizerRuleDetails(Properties properties, String kernelPrefix) { + int ruleCount = parsePositiveInt(properties.getProperty( + kernelPrefix + "runtimeOptimizerDrift.optimizerRule.count", + "0" + )); + if (ruleCount == 0) { + return "none"; + } + StringBuilder builder = new StringBuilder(); + for (int ruleIndex = 0; ruleIndex < ruleCount; ruleIndex++) { + String prefix = kernelPrefix + "runtimeOptimizerDrift.optimizerRule." + ruleIndex + "."; + if (!builder.isEmpty()) { + builder.append(", "); + } + builder.append(properties.getProperty(prefix + "id", "unknown")) + .append("[candidates=") + .append(properties.getProperty(prefix + "candidate.count", "0")) + .append(", proposals=") + .append(properties.getProperty(prefix + "proposal.count", "0")) + .append(", applied=") + .append(properties.getProperty(prefix + "applied.count", "0")) + .append(", skipped=") + .append(properties.getProperty(prefix + "skipped.count", "0")) + .append(", blocked=") + .append(properties.getProperty(prefix + "blocked.count", "0")) + .append(", replacementPlans=") + .append(properties.getProperty(prefix + "replacementPlan.count", "0")) + .append(", completePlans=") + .append(properties.getProperty(prefix + "replacementPlan.complete.count", "0")) + .append(", partialPlans=") + .append(properties.getProperty(prefix + "replacementPlan.partial.count", "0")) + .append(", planValidations=") + .append(properties.getProperty(prefix + "replacementPlan.validation.count", "0")) + .append(", invalidPlanValidations=") + .append(properties.getProperty(prefix + "replacementPlan.validation.invalid.count", "0")) + .append(", planValidationFirstBlocker=") + .append(properties.getProperty(prefix + "replacementPlan.validation.firstBlocker", "none")) + .append(", firstBlocker=") + .append(properties.getProperty( + prefix + "firstBlocker", + properties.getProperty(prefix + "replacementPlan.firstBlocker", "none") + )) + .append(']'); + } + return builder.toString(); + } + private static String i3ReadinessWorkloadSummaryDiagnostic( String status, int kernelCount, diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatterTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatterTest.java index 5c55621d..a7b27279 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatterTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatterTest.java @@ -402,6 +402,20 @@ void mergesSourceSwitchingDecisionEvidencePerKernel() throws IOException { assertEquals("2", gate.getProperty("kernel.0.runtimeOptimizerDrift.proofArtifact.count")); assertEquals("1", gate.getProperty("kernel.0.runtimeOptimizerDrift.proofArtifact.accepted.count")); assertEquals("1", gate.getProperty("kernel.0.runtimeOptimizerDrift.proofArtifact.blocking.count")); + assertEquals("3", gate.getProperty("kernel.0.runtimeOptimizerDrift.replacementPlan.complete.count")); + assertEquals("1", gate.getProperty("kernel.0.runtimeOptimizerDrift.replacementPlan.partial.count")); + assertEquals("multiply-operands-incomplete", gate.getProperty("kernel.0.runtimeOptimizerDrift.replacementPlan.firstBlocker")); + assertEquals("4", gate.getProperty("kernel.0.runtimeOptimizerDrift.replacementPlan.validation.count")); + assertEquals("4", gate.getProperty("kernel.0.runtimeOptimizerDrift.replacementPlan.validation.valid.count")); + assertEquals("0", gate.getProperty("kernel.0.runtimeOptimizerDrift.replacementPlan.validation.invalid.count")); + assertEquals("none", gate.getProperty("kernel.0.runtimeOptimizerDrift.replacementPlan.validation.firstBlocker")); + assertEquals("1", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizerRule.count")); + assertEquals("madFma", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizerRule.0.id")); + assertEquals("3", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizerRule.0.candidate.count")); + assertEquals("1", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizerRule.0.blocked.count")); + assertEquals("4", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizerRule.0.replacementPlan.validation.count")); + assertEquals("0", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizerRule.0.replacementPlan.validation.invalid.count")); + assertEquals("multiply-operands-incomplete", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizerRule.0.firstBlocker")); assertEquals("2", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizerFamily.count")); assertEquals("1", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizerFamily.promotionReady.count")); assertEquals("cse[passes=1, acceptedProof=1, blockingProof=0, rolledBack=0, failed=0, promotionReady=true], vector[passes=1, acceptedProof=0, blockingProof=1, rolledBack=0, failed=0, promotionReady=false]", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizerFamily.summary")); @@ -420,6 +434,15 @@ void mergesSourceSwitchingDecisionEvidencePerKernel() throws IOException { assertEquals("2", gate.getProperty("kernel.1.runtimeOptimizerDrift.proofArtifact.count")); assertEquals("1", gate.getProperty("kernel.1.runtimeOptimizerDrift.proofArtifact.accepted.count")); assertEquals("1", gate.getProperty("kernel.1.runtimeOptimizerDrift.proofArtifact.blocking.count")); + assertEquals("0", gate.getProperty("kernel.1.runtimeOptimizerDrift.replacementPlan.complete.count")); + assertEquals("0", gate.getProperty("kernel.1.runtimeOptimizerDrift.replacementPlan.partial.count")); + assertEquals("none", gate.getProperty("kernel.1.runtimeOptimizerDrift.replacementPlan.firstBlocker")); + assertEquals("0", gate.getProperty("kernel.1.runtimeOptimizerDrift.replacementPlan.validation.count")); + assertEquals("0", gate.getProperty("kernel.1.runtimeOptimizerDrift.replacementPlan.validation.valid.count")); + assertEquals("0", gate.getProperty("kernel.1.runtimeOptimizerDrift.replacementPlan.validation.invalid.count")); + assertEquals("none", gate.getProperty("kernel.1.runtimeOptimizerDrift.replacementPlan.validation.firstBlocker")); + assertEquals("0", gate.getProperty("kernel.1.runtimeOptimizerDrift.optimizerRule.count")); + assertEquals(null, gate.getProperty("kernel.1.runtimeOptimizerDrift.optimizerRule.0.id")); assertEquals("1", gate.getProperty("kernel.1.runtimeOptimizerDrift.optimizerFamily.count")); assertEquals("0", gate.getProperty("kernel.1.runtimeOptimizerDrift.optimizerFamily.promotionReady.count")); assertEquals("vector[passes=2, acceptedProof=1, blockingProof=1, rolledBack=1, failed=0, promotionReady=false]", gate.getProperty("kernel.1.runtimeOptimizerDrift.optimizerFamily.summary")); @@ -818,6 +841,37 @@ private static String runtimeOptimizerDriftProperties( "proofArtifact.count=2", "proofArtifact.accepted.count=1", "proofArtifact.blocking.count=1", + "replacementPlan.complete.count=" + ("optimized".equals(selectedRuntimeIrStage) ? "3" : "0"), + "replacementPlan.partial.count=" + ("optimized".equals(selectedRuntimeIrStage) ? "1" : "0"), + "replacementPlan.firstBlocker=" + ("optimized".equals(selectedRuntimeIrStage) ? "multiply-operands-incomplete" : "none"), + "replacementPlan.validation.count=" + ("optimized".equals(selectedRuntimeIrStage) ? "4" : "0"), + "replacementPlan.validation.valid.count=" + ("optimized".equals(selectedRuntimeIrStage) ? "4" : "0"), + "replacementPlan.validation.invalid.count=0", + "replacementPlan.validation.firstBlocker=none", + "optimizerRule.count=" + ("optimized".equals(selectedRuntimeIrStage) ? "1" : "0"), + "optimizerRule.summary=" + ("optimized".equals(selectedRuntimeIrStage) + ? "madFma[candidates=3, proposals=0, applied=0, skipped=0, blocked=1, mutationProposed=false, replacementPlans=4, completePlans=3, partialPlans=1, planValidations=4, invalidPlanValidations=0, planValidationFirstBlocker=none, firstBlocker=multiply-operands-incomplete]" + : "none"), + "optimizerRule.0.id=" + ("optimized".equals(selectedRuntimeIrStage) ? "madFma" : ""), + "optimizerRule.0.version=peephole-rule:mad-fma-v1", + "optimizerRule.0.extensionId=javatogpu.peephole.mad-fma", + "optimizerRule.0.extensionVersion=peephole-rule:mad-fma-v1", + "optimizerRule.0.proofStatus=candidate-detected", + "optimizerRule.0.candidate.count=" + ("optimized".equals(selectedRuntimeIrStage) ? "3" : "0"), + "optimizerRule.0.proposal.count=0", + "optimizerRule.0.applied.count=0", + "optimizerRule.0.skipped.count=0", + "optimizerRule.0.blocked.count=" + ("optimized".equals(selectedRuntimeIrStage) ? "1" : "0"), + "optimizerRule.0.mutationProposed=false", + "optimizerRule.0.replacementPlan.count=" + ("optimized".equals(selectedRuntimeIrStage) ? "4" : "0"), + "optimizerRule.0.replacementPlan.complete.count=" + ("optimized".equals(selectedRuntimeIrStage) ? "3" : "0"), + "optimizerRule.0.replacementPlan.partial.count=" + ("optimized".equals(selectedRuntimeIrStage) ? "1" : "0"), + "optimizerRule.0.replacementPlan.firstBlocker=" + ("optimized".equals(selectedRuntimeIrStage) ? "multiply-operands-incomplete" : "none"), + "optimizerRule.0.replacementPlan.validation.count=" + ("optimized".equals(selectedRuntimeIrStage) ? "4" : "0"), + "optimizerRule.0.replacementPlan.validation.valid.count=" + ("optimized".equals(selectedRuntimeIrStage) ? "4" : "0"), + "optimizerRule.0.replacementPlan.validation.invalid.count=0", + "optimizerRule.0.replacementPlan.validation.firstBlocker=none", + "optimizerRule.0.firstBlocker=" + ("optimized".equals(selectedRuntimeIrStage) ? "multiply-operands-incomplete" : "none"), "optimizerFamily.count=" + ("optimized".equals(selectedRuntimeIrStage) ? "2" : "1"), "optimizerFamily.promotionReady.count=" + ("optimized".equals(selectedRuntimeIrStage) ? "1" : "0"), "optimizerFamily.summary=" + ("optimized".equals(selectedRuntimeIrStage) diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummaryTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummaryTest.java index 0eb8fc2b..88e8d913 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummaryTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummaryTest.java @@ -36,6 +36,13 @@ void summarizesIndexedSourceSwitchingEvidenceForHistoryAndReports() { properties.setProperty("kernel.0.runtimeOptimizerDrift.proofArtifact.count", "2"); properties.setProperty("kernel.0.runtimeOptimizerDrift.proofArtifact.accepted.count", "1"); properties.setProperty("kernel.0.runtimeOptimizerDrift.proofArtifact.blocking.count", "1"); + properties.setProperty("kernel.0.runtimeOptimizerDrift.replacementPlan.complete.count", "3"); + properties.setProperty("kernel.0.runtimeOptimizerDrift.replacementPlan.partial.count", "1"); + properties.setProperty("kernel.0.runtimeOptimizerDrift.replacementPlan.firstBlocker", "multiply-operands-incomplete"); + properties.setProperty("kernel.0.runtimeOptimizerDrift.replacementPlan.validation.count", "4"); + properties.setProperty("kernel.0.runtimeOptimizerDrift.replacementPlan.validation.valid.count", "3"); + properties.setProperty("kernel.0.runtimeOptimizerDrift.replacementPlan.validation.invalid.count", "1"); + properties.setProperty("kernel.0.runtimeOptimizerDrift.replacementPlan.validation.firstBlocker", "replacement-plan-root-missing"); properties.setProperty("kernel.0.runtimeOptimizerDrift.optimizerFamily.count", "2"); properties.setProperty("kernel.0.runtimeOptimizerDrift.optimizerFamily.promotionReady.count", "1"); properties.setProperty( @@ -84,6 +91,13 @@ void summarizesIndexedSourceSwitchingEvidenceForHistoryAndReports() { assertEquals(2, summary.optimizerProofArtifactCount()); assertEquals(1, summary.optimizerAcceptedProofArtifactCount()); assertEquals(1, summary.optimizerBlockingProofArtifactCount()); + assertEquals(3, summary.optimizerReplacementPlanCompleteCount()); + assertEquals(1, summary.optimizerReplacementPlanPartialCount()); + assertEquals("multiply-operands-incomplete=1", summary.optimizerReplacementPlanFirstBlockers()); + assertEquals(4, summary.optimizerReplacementPlanValidationCount()); + assertEquals(3, summary.optimizerReplacementPlanValidationValidCount()); + assertEquals(1, summary.optimizerReplacementPlanValidationInvalidCount()); + assertEquals("replacement-plan-root-missing=1", summary.optimizerReplacementPlanValidationFirstBlockers()); assertEquals(2, summary.optimizerFamilyCount()); assertEquals(1, summary.optimizerFamilyPromotionReadyCount()); assertEquals( @@ -107,11 +121,12 @@ void summarizesIndexedSourceSwitchingEvidenceForHistoryAndReports() { assertTrue(summary.historyStatus().contains("runtimeExtensionParticipation=recordedKernels=1/executions=3/failedContinued=1/failedClosed=0/sources=original-irgpu:ir-validation=1, backend-compiler-feedback=2")); assertTrue(summary.historyStatus().contains("optimizerFamilies=2")); assertTrue(summary.historyStatus().contains("optimizerPromotionReadyFamilies=1")); + assertTrue(summary.historyStatus().contains("optimizerReplacementPlans=complete=3/partial=1/validation=valid=3/total=4/invalid=1/validationFirstBlockers=replacement-plan-root-missing=1/firstBlockers=multiply-operands-incomplete=1")); assertTrue(summary.historyStatus().contains("optimizerFamilySummary=cse[passes=1")); assertTrue(summary.historyStatus().contains("kernelCount=1")); assertTrue(summary.historyStatus().contains("extensionParticipation=recorded/3executions/failedContinued=1/failedClosed=0")); assertTrue(summary.historyStatus().contains("sourceSwitching=reject-production-irgpu-source/operatorAccepted=true")); - assertTrue(summary.historyStatus().contains("proof=2/acceptedProof=1/blockingProof=1/optimizerFamilies=2/promotionReadyFamilies=1")); + assertTrue(summary.historyStatus().contains("proof=2/acceptedProof=1/blockingProof=1/replacementPlanComplete=3/replacementPlanPartial=1/replacementPlanFirstBlocker=multiply-operands-incomplete/replacementPlanValidationValid=3/replacementPlanValidationTotal=4/replacementPlanValidationInvalid=1/replacementPlanValidationFirstBlocker=replacement-plan-root-missing/optimizerRules=0/optimizerRuleDetails=none/optimizerFamilies=2/promotionReadyFamilies=1")); assertTrue(summary.historyStatus().contains("families=source-parity=1")); } diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java index cba6bbd4..a17479c9 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java @@ -1990,6 +1990,120 @@ void optimizerDriftArtifactCapturesRollbackAndFallbackCounts() { assertTrue(dump.artifact("runtime-production-mutation-safety.properties").contains("fallbackDecision=optimizer-rollback")); } + @Test + void optimizerDriftArtifactCapturesReadOnlyReplacementPlanTelemetry() { + IrGpuArtifact original = artifact("body\n return original\n"); + GpuBackendModuleArtifact backendArtifact = GpuBackendModuleArtifact.openClSource( + "__kernel void kernel(__global int* out) { out[0] = 1; }", + "runtime/lowered/kernel.cl", + "test-lowerer-v1" + ); + GpuRuntimeCompileRequest request = new GpuRuntimeCompileRequest( + descriptor(), + new GpuRuntimeCompileOptions(GpuBackendTarget.OPENCL, List.of(), "diagnostic"), + GpuRuntimeDeviceProfile.generic(GpuBackendTarget.OPENCL, "OpenCL"), + Optional.of(original) + ); + GpuRuntimeIrOptimizationPassReport peepholeReport = GpuRuntimeIrOptimizationPassReport.skipped( + "optimizer:peephole-diagnostic-v1", + "irgpu:sha256:original", + "typed peephole replacement plans recorded without mutation" + ).withProofArtifact(GpuRuntimeIrOptimizationProofArtifact.fromFields( + "runtime.peephole.preflight", + "blocked", + Map.ofEntries( + Map.entry("optimizerFamily", "peephole"), + Map.entry("replacementPlan.partial.count", "1"), + Map.entry("replacementPlan.firstBlocker", "multiply-operands-incomplete"), + Map.entry("replacementPlan.validation.invalid.count", "1"), + Map.entry("replacementPlan.validation.firstBlocker", "replacement-plan-root-missing"), + Map.entry("rule.count", "2"), + Map.entry("rule.0.id", "madFma"), + Map.entry("rule.0.version", "peephole-rule:mad-fma-v1"), + Map.entry("rule.0.extensionId", "javatogpu.peephole.mad-fma"), + Map.entry("rule.0.extensionVersion", "peephole-rule:mad-fma-v1"), + Map.entry("rule.0.proofStatus", "candidate-detected"), + Map.entry("rule.0.candidate.count", "2"), + Map.entry("rule.0.proposal.count", "0"), + Map.entry("rule.0.applied.count", "0"), + Map.entry("rule.0.skipped.count", "0"), + Map.entry("rule.0.blocked.count", "1"), + Map.entry("rule.0.mutationProposed", "false"), + Map.entry("rule.0.replacementPlan.count", "3"), + Map.entry("rule.0.replacementPlan.complete.count", "2"), + Map.entry("rule.0.replacementPlan.partial.count", "1"), + Map.entry("rule.0.replacementPlan.firstBlocker", "multiply-operands-incomplete"), + Map.entry("rule.0.replacementPlan.validation.count", "3"), + Map.entry("rule.0.replacementPlan.validation.valid.count", "2"), + Map.entry("rule.0.replacementPlan.validation.invalid.count", "1"), + Map.entry("rule.0.replacementPlan.validation.firstBlocker", "replacement-plan-root-missing"), + Map.entry("rule.0.firstBlocker", "multiply-operands-incomplete"), + Map.entry("rule.1.id", "clamp"), + Map.entry("rule.1.version", "peephole-rule:clamp-v1"), + Map.entry("rule.1.extensionId", "javatogpu.peephole.clamp"), + Map.entry("rule.1.extensionVersion", "peephole-rule:clamp-v1"), + Map.entry("rule.1.proofStatus", "no-candidate"), + Map.entry("rule.1.candidate.count", "0"), + Map.entry("rule.1.proposal.count", "0"), + Map.entry("rule.1.applied.count", "0"), + Map.entry("rule.1.skipped.count", "1"), + Map.entry("rule.1.blocked.count", "0"), + Map.entry("rule.1.mutationProposed", "false"), + Map.entry("rule.1.replacementPlan.count", "3"), + Map.entry("rule.1.replacementPlan.complete.count", "3"), + Map.entry("rule.1.replacementPlan.partial.count", "0"), + Map.entry("rule.1.replacementPlan.firstBlocker", "none"), + Map.entry("rule.1.replacementPlan.validation.count", "3"), + Map.entry("rule.1.replacementPlan.validation.valid.count", "3"), + Map.entry("rule.1.replacementPlan.validation.invalid.count", "0"), + Map.entry("rule.1.replacementPlan.validation.firstBlocker", "none"), + Map.entry("rule.1.firstBlocker", "none") + ) + )); + GpuRuntimeIrOptimizationReport optimizationReport = new GpuRuntimeIrOptimizationReport( + Optional.of(original), + List.of(peepholeReport), + GpuOptimizationStrategyDecision.none(request) + ); + GpuRuntimeCompileArtifactSnapshot snapshot = GpuRuntimeCompileArtifactSnapshot.from( + request, + request, + backendArtifact, + GpuRuntimeCompileInvalidationStamp.from(request, backendArtifact, "optimizer:test-v1"), + GpuRuntimeCompileProvenance.from(request), + optimizationReport + ); + + GpuRuntimeCompileArtifactDump dump = GpuRuntimeCompileArtifactDumper.dump(snapshot); + String drift = dump.artifact("runtime-optimizer-drift.properties"); + + assertTrue(drift.contains("pass.count=1")); + assertTrue(drift.contains("pass.skipped.count=1")); + assertTrue(drift.contains("proofArtifact.count=1")); + assertTrue(drift.contains("replacementPlan.complete.count=5")); + assertTrue(drift.contains("replacementPlan.partial.count=1")); + assertTrue(drift.contains("replacementPlan.firstBlocker=multiply-operands-incomplete")); + assertTrue(drift.contains("replacementPlan.validation.count=6")); + assertTrue(drift.contains("replacementPlan.validation.valid.count=5")); + assertTrue(drift.contains("replacementPlan.validation.invalid.count=1")); + assertTrue(drift.contains("replacementPlan.validation.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("optimizerRule.count=2")); + assertTrue(drift.contains("optimizerRule.0.id=madFma")); + assertTrue(drift.contains("optimizerRule.0.blocked.count=1")); + assertTrue(drift.contains("optimizerRule.0.replacementPlan.validation.count=3")); + assertTrue(drift.contains("optimizerRule.0.replacementPlan.validation.invalid.count=1")); + assertTrue(drift.contains("optimizerRule.0.replacementPlan.validation.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("optimizerRule.0.firstBlocker=multiply-operands-incomplete")); + assertTrue(drift.contains("optimizerRule.1.id=clamp")); + assertTrue(drift.contains("optimizerRule.1.skipped.count=1")); + assertTrue(drift.contains("optimizerRule.summary=madFma[candidates=2, proposals=0, applied=0, skipped=0, blocked=1")); + assertTrue(drift.contains("planValidations=3, invalidPlanValidations=1, planValidationFirstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("optimizerFamily.count=1")); + assertTrue(drift.contains("optimizerFamily.summary=peephole[passes=1, acceptedProof=0, blockingProof=1, rolledBack=0, failed=0, promotionReady=false]")); + assertTrue(drift.contains("selectedRuntimeIrStage=original")); + assertTrue(drift.contains("optimizedIrRejected=false")); + } + @Test void dumpsRuntimeIrOptimizerEvidenceArtifactForProposalBridgeReports() { IrGpuArtifact original = artifact("body\n return original\n"); diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistryTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistryTest.java index a7623f87..7c40727a 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistryTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistryTest.java @@ -511,6 +511,36 @@ void diagnosticPeepholePassRefusesTextBasedRewrites() { assertTrue(passReport.toLine().contains("text-based peephole rewriting is disabled")); } + @Test + void builtInPeepholeRegistryKeepsStableDiagnosticRuleOrder() { + GpuRuntimeIrPeepholeRuleRegistry registry = GpuRuntimeIrPeepholeRuleRegistry.loadWithBuiltIns(); + + assertEquals( + List.of("madFma", "clamp", "dot", "mix", "step"), + registry.rules().stream().map(GpuRuntimeIrPeepholeRule::ruleId).toList() + ); + assertEquals( + List.of( + "peephole-rule:mad-fma-v1", + "peephole-rule:clamp-v1", + "peephole-rule:dot-v1", + "peephole-rule:mix-v1", + "peephole-rule:step-v1" + ), + registry.rules().stream().map(GpuRuntimeIrPeepholeRule::ruleVersion).toList() + ); + assertEquals( + List.of( + "javatogpu.peephole.mad-fma", + "javatogpu.peephole.clamp", + "javatogpu.peephole.dot", + "javatogpu.peephole.mix", + "javatogpu.peephole.step" + ), + registry.rules().stream().map(GpuRuntimeIrPeepholeRule::extensionId).toList() + ); + } + @Test void diagnosticPeepholePassFindsTypedMadFmaCandidateWithoutMutatingIr() { IrGpuArtifact artifact = fastMathTypedArtifact(); @@ -526,10 +556,215 @@ void diagnosticPeepholePassFindsTypedMadFmaCandidateWithoutMutatingIr() { assertEquals("true", passReport.proofArtifact().fields().get("typedIrAvailable")); assertEquals("1", passReport.proofArtifact().fields().get("candidate.count")); assertEquals("1", passReport.proofArtifact().fields().get("rule.madFma.candidate.count")); + assertEquals("5", passReport.proofArtifact().fields().get("rule.count")); + assertEquals("1", passReport.proofArtifact().fields().get("rule.0.replacementPlan.count")); + assertEquals("1", passReport.proofArtifact().fields().get("rule.0.replacementPlan.complete.count")); + assertEquals("0", passReport.proofArtifact().fields().get("rule.0.replacementPlan.partial.count")); + assertEquals("none", passReport.proofArtifact().fields().get("rule.0.replacementPlan.firstBlocker")); + assertEquals("madFma", passReport.proofArtifact().fields().get("rule.0.replacementPlan.0.ruleId")); + assertEquals("kernel", passReport.proofArtifact().fields().get("rule.0.replacementPlan.0.methodName")); + assertEquals("1", passReport.proofArtifact().fields().get("rule.0.replacementPlan.0.rootNodeId")); + assertEquals("mad-fma", passReport.proofArtifact().fields().get("rule.0.replacementPlan.0.replacementKind")); + assertEquals("1,2", passReport.proofArtifact().fields().get("rule.0.replacementPlan.0.coveredNodeIds")); + assertEquals("3,4,5", passReport.proofArtifact().fields().get("rule.0.replacementPlan.0.inputNodeIds")); + assertEquals("true", passReport.proofArtifact().fields().get("rule.0.replacementPlan.0.complete")); + assertEquals("none", passReport.proofArtifact().fields().get("rule.0.replacementPlan.0.firstBlocker")); + assertEquals("0", passReport.proofArtifact().fields().get("replacementPlan.validation.invalid.count")); + assertEquals("none", passReport.proofArtifact().fields().get("replacementPlan.validation.firstBlocker")); + assertEquals("1", passReport.proofArtifact().fields().get("rule.0.replacementPlan.validation.count")); + assertEquals("1", passReport.proofArtifact().fields().get("rule.0.replacementPlan.validation.valid.count")); + assertEquals("0", passReport.proofArtifact().fields().get("rule.0.replacementPlan.validation.invalid.count")); + assertEquals("true", passReport.proofArtifact().fields().get("rule.0.replacementPlan.validation.0.valid")); + assertEquals("true", passReport.proofArtifact().fields().get("rule.0.replacementPlan.validation.0.rootExists")); + assertEquals("true", passReport.proofArtifact().fields().get("rule.0.replacementPlan.validation.0.coveredIncludesRoot")); + assertEquals("none", passReport.proofArtifact().fields().get("rule.0.replacementPlan.validation.0.missingCoveredNodeIds")); + assertEquals("none", passReport.proofArtifact().fields().get("rule.0.replacementPlan.validation.0.missingInputNodeIds")); + assertEquals("clamp", passReport.proofArtifact().fields().get("rule.1.id")); + assertEquals("dot", passReport.proofArtifact().fields().get("rule.2.id")); + assertEquals("mix", passReport.proofArtifact().fields().get("rule.3.id")); + assertEquals("step", passReport.proofArtifact().fields().get("rule.4.id")); + assertEquals("1", passReport.proofArtifact().fields().get("rule.1.skipped.count")); + assertEquals("no-candidate", passReport.proofArtifact().fields().get("rule.1.proofStatus")); assertEquals("rewrite-engine-not-implemented", passReport.proofArtifact().fields().get("firstBlocker")); assertTrue(passReport.toLine().contains("structural rewrite and proof emission are not implemented")); } + @Test + void diagnosticPeepholePassReportsPartialMadFmaPlanWithoutCandidate() { + IrGpuArtifact artifact = fastMathPartialMadFmaTypedArtifact(); + GpuRuntimeIrOptimizerRegistry registry = GpuRuntimeIrOptimizerRegistry.ofPasses( + List.of(new GpuRuntimeIrPeepholePass()) + ); + + GpuRuntimeIrOptimizationReport report = registry.optimizeWithReport(request(artifact)); + + assertSame(artifact, report.artifact().orElseThrow()); + GpuRuntimeIrOptimizationPassReport passReport = report.passReports().get(0); + Map fields = passReport.proofArtifact().fields(); + assertEquals(GpuRuntimeIrOptimizationOutcome.SKIPPED, passReport.outcome()); + assertEquals("0", fields.get("candidate.count")); + assertEquals("0", fields.get("rule.madFma.candidate.count")); + assertEquals("3", fields.get("replacementPlan.partial.count")); + assertEquals("multiply-operands-incomplete", fields.get("replacementPlan.firstBlocker")); + assertEquals("1", fields.get("rule.0.replacementPlan.count")); + assertEquals("0", fields.get("rule.0.replacementPlan.complete.count")); + assertEquals("1", fields.get("rule.0.replacementPlan.partial.count")); + assertEquals("multiply-operands-incomplete", fields.get("rule.0.replacementPlan.firstBlocker")); + assertEquals("1", fields.get("rule.0.replacementPlan.0.rootNodeId")); + assertEquals("1,2", fields.get("rule.0.replacementPlan.0.coveredNodeIds")); + assertEquals("5", fields.get("rule.0.replacementPlan.0.inputNodeIds")); + assertEquals("false", fields.get("rule.0.replacementPlan.0.complete")); + assertEquals("multiply-operands-incomplete", fields.get("rule.0.replacementPlan.0.firstBlocker")); + assertEquals("dot", fields.get("rule.2.id")); + assertEquals("1", fields.get("rule.2.replacementPlan.count")); + assertEquals("0", fields.get("rule.2.replacementPlan.complete.count")); + assertEquals("1", fields.get("rule.2.replacementPlan.partial.count")); + assertEquals("dot-multiply-operands-incomplete", fields.get("rule.2.replacementPlan.firstBlocker")); + assertEquals("mix", fields.get("rule.3.id")); + assertEquals("1", fields.get("rule.3.replacementPlan.count")); + assertEquals("0", fields.get("rule.3.replacementPlan.complete.count")); + assertEquals("1", fields.get("rule.3.replacementPlan.partial.count")); + assertEquals("mix-multiply-operands-incomplete", fields.get("rule.3.replacementPlan.firstBlocker")); + assertEquals("multiply-operands-incomplete", fields.get("firstBlocker")); + assertTrue(passReport.toLine().contains("replacement plan is incomplete")); + } + + @Test + void diagnosticPeepholePassFindsTypedClampCandidateWithoutMutatingIr() { + IrGpuArtifact artifact = fastMathClampTypedArtifact(); + GpuRuntimeIrOptimizerRegistry registry = GpuRuntimeIrOptimizerRegistry.ofPasses( + List.of(new GpuRuntimeIrPeepholePass()) + ); + + GpuRuntimeIrOptimizationReport report = registry.optimizeWithReport(request(artifact)); + + assertSame(artifact, report.artifact().orElseThrow()); + GpuRuntimeIrOptimizationPassReport passReport = report.passReports().get(0); + Map fields = passReport.proofArtifact().fields(); + assertEquals(GpuRuntimeIrOptimizationOutcome.SKIPPED, passReport.outcome()); + assertEquals("1", fields.get("candidate.count")); + assertEquals("0", fields.get("rule.madFma.candidate.count")); + assertEquals("1", fields.get("rule.clamp.candidate.count")); + assertEquals("clamp", fields.get("rule.1.id")); + assertEquals("1", fields.get("rule.1.candidate.count")); + assertEquals("1", fields.get("rule.1.replacementPlan.count")); + assertEquals("1", fields.get("rule.1.replacementPlan.complete.count")); + assertEquals("0", fields.get("rule.1.replacementPlan.partial.count")); + assertEquals("none", fields.get("rule.1.replacementPlan.firstBlocker")); + assertEquals("clamp", fields.get("rule.1.replacementPlan.0.ruleId")); + assertEquals("kernel", fields.get("rule.1.replacementPlan.0.methodName")); + assertEquals("1", fields.get("rule.1.replacementPlan.0.rootNodeId")); + assertEquals("clamp", fields.get("rule.1.replacementPlan.0.replacementKind")); + assertEquals("1,2", fields.get("rule.1.replacementPlan.0.coveredNodeIds")); + assertEquals("3,4,5", fields.get("rule.1.replacementPlan.0.inputNodeIds")); + assertEquals("true", fields.get("rule.1.replacementPlan.0.complete")); + assertEquals("rewrite-engine-not-implemented", fields.get("firstBlocker")); + assertTrue(passReport.toLine().contains("structural rewrite and proof emission are not implemented")); + } + + @Test + void diagnosticPeepholePassFindsTypedStepCandidateWithoutMutatingIr() { + IrGpuArtifact artifact = fastMathStepTypedArtifact(); + GpuRuntimeIrOptimizerRegistry registry = GpuRuntimeIrOptimizerRegistry.ofPasses( + List.of(new GpuRuntimeIrPeepholePass()) + ); + + GpuRuntimeIrOptimizationReport report = registry.optimizeWithReport(request(artifact)); + + assertSame(artifact, report.artifact().orElseThrow()); + GpuRuntimeIrOptimizationPassReport passReport = report.passReports().get(0); + Map fields = passReport.proofArtifact().fields(); + assertEquals(GpuRuntimeIrOptimizationOutcome.SKIPPED, passReport.outcome()); + assertEquals("1", fields.get("candidate.count")); + assertEquals("0", fields.get("rule.madFma.candidate.count")); + assertEquals("0", fields.get("rule.clamp.candidate.count")); + assertEquals("1", fields.get("rule.step.candidate.count")); + assertEquals("step", fields.get("rule.4.id")); + assertEquals("1", fields.get("rule.4.candidate.count")); + assertEquals("1", fields.get("rule.4.replacementPlan.count")); + assertEquals("1", fields.get("rule.4.replacementPlan.complete.count")); + assertEquals("0", fields.get("rule.4.replacementPlan.partial.count")); + assertEquals("none", fields.get("rule.4.replacementPlan.firstBlocker")); + assertEquals("step", fields.get("rule.4.replacementPlan.0.ruleId")); + assertEquals("kernel", fields.get("rule.4.replacementPlan.0.methodName")); + assertEquals("1", fields.get("rule.4.replacementPlan.0.rootNodeId")); + assertEquals("step", fields.get("rule.4.replacementPlan.0.replacementKind")); + assertEquals("1,2", fields.get("rule.4.replacementPlan.0.coveredNodeIds")); + assertEquals("4,3", fields.get("rule.4.replacementPlan.0.inputNodeIds")); + assertEquals("true", fields.get("rule.4.replacementPlan.0.complete")); + assertEquals("rewrite-engine-not-implemented", fields.get("firstBlocker")); + assertTrue(passReport.toLine().contains("structural rewrite and proof emission are not implemented")); + } + + @Test + void diagnosticPeepholePassFindsTypedDotCandidateWithoutMutatingIr() { + IrGpuArtifact artifact = fastMathDotTypedArtifact(); + GpuRuntimeIrOptimizerRegistry registry = GpuRuntimeIrOptimizerRegistry.ofPasses( + List.of(new GpuRuntimeIrPeepholePass()) + ); + + GpuRuntimeIrOptimizationReport report = registry.optimizeWithReport(request(artifact)); + + assertSame(artifact, report.artifact().orElseThrow()); + GpuRuntimeIrOptimizationPassReport passReport = report.passReports().get(0); + Map fields = passReport.proofArtifact().fields(); + assertEquals(GpuRuntimeIrOptimizationOutcome.SKIPPED, passReport.outcome()); + assertEquals("2", fields.get("candidate.count")); + assertEquals("1", fields.get("rule.madFma.candidate.count")); + assertEquals("0", fields.get("rule.clamp.candidate.count")); + assertEquals("1", fields.get("rule.dot.candidate.count")); + assertEquals("dot", fields.get("rule.2.id")); + assertEquals("1", fields.get("rule.2.candidate.count")); + assertEquals("1", fields.get("rule.2.replacementPlan.count")); + assertEquals("1", fields.get("rule.2.replacementPlan.complete.count")); + assertEquals("0", fields.get("rule.2.replacementPlan.partial.count")); + assertEquals("none", fields.get("rule.2.replacementPlan.firstBlocker")); + assertEquals("dot", fields.get("rule.2.replacementPlan.0.ruleId")); + assertEquals("kernel", fields.get("rule.2.replacementPlan.0.methodName")); + assertEquals("1", fields.get("rule.2.replacementPlan.0.rootNodeId")); + assertEquals("dot", fields.get("rule.2.replacementPlan.0.replacementKind")); + assertEquals("1,2,5", fields.get("rule.2.replacementPlan.0.coveredNodeIds")); + assertEquals("3,4,6,7", fields.get("rule.2.replacementPlan.0.inputNodeIds")); + assertEquals("true", fields.get("rule.2.replacementPlan.0.complete")); + assertEquals("rewrite-engine-not-implemented", fields.get("firstBlocker")); + assertTrue(passReport.toLine().contains("structural rewrite and proof emission are not implemented")); + } + + @Test + void diagnosticPeepholePassFindsTypedMixCandidateWithoutMutatingIr() { + IrGpuArtifact artifact = fastMathMixTypedArtifact(); + GpuRuntimeIrOptimizerRegistry registry = GpuRuntimeIrOptimizerRegistry.ofPasses( + List.of(new GpuRuntimeIrPeepholePass()) + ); + + GpuRuntimeIrOptimizationReport report = registry.optimizeWithReport(request(artifact)); + + assertSame(artifact, report.artifact().orElseThrow()); + GpuRuntimeIrOptimizationPassReport passReport = report.passReports().get(0); + Map fields = passReport.proofArtifact().fields(); + assertEquals(GpuRuntimeIrOptimizationOutcome.SKIPPED, passReport.outcome()); + assertEquals("2", fields.get("candidate.count")); + assertEquals("1", fields.get("rule.madFma.candidate.count")); + assertEquals("0", fields.get("rule.clamp.candidate.count")); + assertEquals("0", fields.get("rule.dot.candidate.count")); + assertEquals("1", fields.get("rule.mix.candidate.count")); + assertEquals("mix", fields.get("rule.3.id")); + assertEquals("1", fields.get("rule.3.candidate.count")); + assertEquals("1", fields.get("rule.3.replacementPlan.count")); + assertEquals("1", fields.get("rule.3.replacementPlan.complete.count")); + assertEquals("0", fields.get("rule.3.replacementPlan.partial.count")); + assertEquals("none", fields.get("rule.3.replacementPlan.firstBlocker")); + assertEquals("mix", fields.get("rule.3.replacementPlan.0.ruleId")); + assertEquals("kernel", fields.get("rule.3.replacementPlan.0.methodName")); + assertEquals("1", fields.get("rule.3.replacementPlan.0.rootNodeId")); + assertEquals("mix", fields.get("rule.3.replacementPlan.0.replacementKind")); + assertEquals("1,2,3", fields.get("rule.3.replacementPlan.0.coveredNodeIds")); + assertEquals("4,5,6", fields.get("rule.3.replacementPlan.0.inputNodeIds")); + assertEquals("true", fields.get("rule.3.replacementPlan.0.complete")); + assertEquals("rewrite-engine-not-implemented", fields.get("firstBlocker")); + assertTrue(passReport.toLine().contains("structural rewrite and proof emission are not implemented")); + } + @Test void diagnosticPeepholePassRunsThirdPartyTypedRuleAndExportsRuleMetadata() { GpuRuntimeIrPeepholeRule customRule = new GpuRuntimeIrPeepholeRule() { @@ -574,6 +809,65 @@ public String extensionId() { assertEquals("1", fields.get("rule.execution.count")); } + @Test + void diagnosticPeepholePassRejectsStructurallyInvalidReplacementPlan() { + GpuRuntimeIrPeepholeRule invalidPlanRule = new GpuRuntimeIrPeepholeRule() { + @Override + public GpuRuntimeIrPeepholeRuleReport analyze(GpuRuntimeIrPeepholeRuleContext context) { + return GpuRuntimeIrPeepholeRuleReport.diagnosticCandidates( + this, + context.methodBody().name(), + 1, + Map.of("family", "invalid-plan-test"), + List.of(GpuRuntimeIrPeepholeReplacementPlan.complete( + ruleId(), + context.methodBody().name(), + 999, + "invalid-test", + List.of(999), + List.of(1) + )) + ); + } + + @Override + public String ruleId() { + return "invalidPlanRule"; + } + + @Override + public String extensionId() { + return "test.peephole.invalid-plan"; + } + }; + GpuRuntimeIrPeepholePass pass = new GpuRuntimeIrPeepholePass( + GpuRuntimeIrPeepholeRuleRegistry.of(List.of(invalidPlanRule)) + ); + IrGpuArtifact artifact = fastMathTypedArtifact(); + + GpuRuntimeIrOptimizationReport report = GpuRuntimeIrOptimizerRegistry.ofPasses(List.of(pass)) + .optimizeWithReport(request(artifact)); + + assertSame(artifact, report.artifact().orElseThrow()); + GpuRuntimeIrOptimizationPassReport passReport = report.passReports().get(0); + Map fields = passReport.proofArtifact().fields(); + assertEquals(GpuRuntimeIrOptimizationOutcome.SKIPPED, passReport.outcome()); + assertEquals("1", fields.get("candidate.count")); + assertEquals("1", fields.get("replacementPlan.validation.invalid.count")); + assertEquals("replacement-plan-root-missing", fields.get("replacementPlan.validation.firstBlocker")); + assertEquals("replacement-plan-root-missing", fields.get("firstBlocker")); + assertEquals("replacement-plan-root-missing", fields.get("rule.0.firstBlocker")); + assertEquals("1", fields.get("rule.0.replacementPlan.validation.count")); + assertEquals("0", fields.get("rule.0.replacementPlan.validation.valid.count")); + assertEquals("1", fields.get("rule.0.replacementPlan.validation.invalid.count")); + assertEquals("false", fields.get("rule.0.replacementPlan.validation.0.valid")); + assertEquals("false", fields.get("rule.0.replacementPlan.validation.0.rootExists")); + assertEquals("true", fields.get("rule.0.replacementPlan.validation.0.coveredIncludesRoot")); + assertEquals("999", fields.get("rule.0.replacementPlan.validation.0.missingCoveredNodeIds")); + assertEquals("none", fields.get("rule.0.replacementPlan.validation.0.missingInputNodeIds")); + assertTrue(passReport.toLine().contains("structural validation")); + } + @Test void advisoryPeepholeRuleFailureIsIsolatedAndFollowingRuleStillRuns() { GpuRuntimeIrPeepholeRule failing = new GpuRuntimeIrPeepholeRule() { @@ -1119,6 +1413,263 @@ private static IrGpuArtifact fastMathTypedArtifact() { ); } + private static IrGpuArtifact fastMathPartialMadFmaTypedArtifact() { + IrGpuTypedBody typedBody = new IrGpuTypedBody( + IrGpuTypedBody.FORMAT, + List.of(0), + List.of( + new IrGpuTypedNode(0, "GpuIrReturn", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(2), + "right", List.of(5) + )), + new IrGpuTypedNode(2, "GpuIrBinary", Map.of("operator", "*"), Map.of( + "left", List.of(3) + )), + new IrGpuTypedNode(3, "GpuIrVariableRef", Map.of("name", "a"), Map.of()), + new IrGpuTypedNode(5, "GpuIrVariableRef", Map.of("name", "c"), Map.of()) + ) + ); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + new IrGpuModule( + "kernel", + "jtg_kernel", + List.of(), + List.of(), + List.of(new IrGpuMethodBody( + "entry", + "kernel", + "jtg_kernel", + "ir-text-v1", + "body\n return ((a * ) + c)\n", + typedBody, + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBodyIndex.empty(), + List.of(), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuSourceLocation.unknown("kernel") + )) + ), + List.of(), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuLaunchMetadata.defaultOneDimensional(), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuValidationMetadata.frontendSubset(), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuFeatureMetadata.none(), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuOptimizerPolicyMetadata.fromGpuOptimize(true), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuRegenerationMetadata.transitionalIrText(), + List.of(IrGpuBackendOutput.openClSource("javatogpu/sample/Demo/kernel.cl")), + "opencl", + "off" + ); + } + + private static IrGpuArtifact fastMathClampTypedArtifact() { + IrGpuTypedBody typedBody = new IrGpuTypedBody( + IrGpuTypedBody.FORMAT, + List.of(0), + List.of( + new IrGpuTypedNode(0, "GpuIrReturn", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrIntrinsicCall", Map.of("name", "min"), Map.of( + "args", List.of(2, 5) + )), + new IrGpuTypedNode(2, "GpuIrIntrinsicCall", Map.of("name", "max"), Map.of( + "args", List.of(3, 4) + )), + new IrGpuTypedNode(3, "GpuIrVariableRef", Map.of("name", "x"), Map.of()), + new IrGpuTypedNode(4, "GpuIrVariableRef", Map.of("name", "lo"), Map.of()), + new IrGpuTypedNode(5, "GpuIrVariableRef", Map.of("name", "hi"), Map.of()) + ) + ); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + new IrGpuModule( + "kernel", + "jtg_kernel", + List.of(), + List.of(), + List.of(new IrGpuMethodBody( + "entry", + "kernel", + "jtg_kernel", + "ir-text-v1", + "body\n return min(max(x, lo), hi)\n", + typedBody, + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBodyIndex.empty(), + List.of(), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuSourceLocation.unknown("kernel") + )) + ), + List.of(), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuLaunchMetadata.defaultOneDimensional(), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuValidationMetadata.frontendSubset(), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuFeatureMetadata.none(), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuOptimizerPolicyMetadata.fromGpuOptimize(true), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuRegenerationMetadata.transitionalIrText(), + List.of(IrGpuBackendOutput.openClSource("javatogpu/sample/Demo/kernel.cl")), + "opencl", + "off" + ); + } + + private static IrGpuArtifact fastMathStepTypedArtifact() { + IrGpuTypedBody typedBody = new IrGpuTypedBody( + IrGpuTypedBody.FORMAT, + List.of(0), + List.of( + new IrGpuTypedNode(0, "GpuIrReturn", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrConditional", Map.of(), Map.of( + "condition", List.of(2), + "then", List.of(5), + "else", List.of(6) + )), + new IrGpuTypedNode(2, "GpuIrBinary", Map.of("operator", "<"), Map.of( + "left", List.of(3), + "right", List.of(4) + )), + new IrGpuTypedNode(3, "GpuIrVariableRef", Map.of("name", "x"), Map.of()), + new IrGpuTypedNode(4, "GpuIrVariableRef", Map.of("name", "edge"), Map.of()), + new IrGpuTypedNode(5, "GpuIrLiteral", Map.of("sourceText", "0.0f"), Map.of()), + new IrGpuTypedNode(6, "GpuIrLiteral", Map.of("sourceText", "1.0f"), Map.of()) + ) + ); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + new IrGpuModule( + "kernel", + "jtg_kernel", + List.of(), + List.of(), + List.of(new IrGpuMethodBody( + "entry", + "kernel", + "jtg_kernel", + "ir-text-v1", + "body\n return x < edge ? 0.0f : 1.0f\n", + typedBody, + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBodyIndex.empty(), + List.of(), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuSourceLocation.unknown("kernel") + )) + ), + List.of(), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuLaunchMetadata.defaultOneDimensional(), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuValidationMetadata.frontendSubset(), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuFeatureMetadata.none(), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuOptimizerPolicyMetadata.fromGpuOptimize(true), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuRegenerationMetadata.transitionalIrText(), + List.of(IrGpuBackendOutput.openClSource("javatogpu/sample/Demo/kernel.cl")), + "opencl", + "off" + ); + } + + private static IrGpuArtifact fastMathDotTypedArtifact() { + IrGpuTypedBody typedBody = new IrGpuTypedBody( + IrGpuTypedBody.FORMAT, + List.of(0), + List.of( + new IrGpuTypedNode(0, "GpuIrReturn", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(2), + "right", List.of(5) + )), + new IrGpuTypedNode(2, "GpuIrBinary", Map.of("operator", "*"), Map.of( + "left", List.of(3), + "right", List.of(4) + )), + new IrGpuTypedNode(3, "GpuIrVariableRef", Map.of("name", "a0"), Map.of()), + new IrGpuTypedNode(4, "GpuIrVariableRef", Map.of("name", "b0"), Map.of()), + new IrGpuTypedNode(5, "GpuIrBinary", Map.of("operator", "*"), Map.of( + "left", List.of(6), + "right", List.of(7) + )), + new IrGpuTypedNode(6, "GpuIrVariableRef", Map.of("name", "a1"), Map.of()), + new IrGpuTypedNode(7, "GpuIrVariableRef", Map.of("name", "b1"), Map.of()) + ) + ); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + new IrGpuModule( + "kernel", + "jtg_kernel", + List.of(), + List.of(), + List.of(new IrGpuMethodBody( + "entry", + "kernel", + "jtg_kernel", + "ir-text-v1", + "body\n return (a0 * b0) + (a1 * b1)\n", + typedBody, + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBodyIndex.empty(), + List.of(), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuSourceLocation.unknown("kernel") + )) + ), + List.of(), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuLaunchMetadata.defaultOneDimensional(), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuValidationMetadata.frontendSubset(), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuFeatureMetadata.none(), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuOptimizerPolicyMetadata.fromGpuOptimize(true), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuRegenerationMetadata.transitionalIrText(), + List.of(IrGpuBackendOutput.openClSource("javatogpu/sample/Demo/kernel.cl")), + "opencl", + "off" + ); + } + + private static IrGpuArtifact fastMathMixTypedArtifact() { + IrGpuTypedBody typedBody = new IrGpuTypedBody( + IrGpuTypedBody.FORMAT, + List.of(0), + List.of( + new IrGpuTypedNode(0, "GpuIrReturn", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(4), + "right", List.of(2) + )), + new IrGpuTypedNode(2, "GpuIrBinary", Map.of("operator", "*"), Map.of( + "left", List.of(6), + "right", List.of(3) + )), + new IrGpuTypedNode(3, "GpuIrBinary", Map.of("operator", "-"), Map.of( + "left", List.of(5), + "right", List.of(4) + )), + new IrGpuTypedNode(4, "GpuIrVariableRef", Map.of("name", "a"), Map.of()), + new IrGpuTypedNode(5, "GpuIrVariableRef", Map.of("name", "b"), Map.of()), + new IrGpuTypedNode(6, "GpuIrVariableRef", Map.of("name", "t"), Map.of()) + ) + ); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + new IrGpuModule( + "kernel", + "jtg_kernel", + List.of(), + List.of(), + List.of(new IrGpuMethodBody( + "entry", + "kernel", + "jtg_kernel", + "ir-text-v1", + "body\n return a + t * (b - a)\n", + typedBody, + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBodyIndex.empty(), + List.of(), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuSourceLocation.unknown("kernel") + )) + ), + List.of(), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuLaunchMetadata.defaultOneDimensional(), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuValidationMetadata.frontendSubset(), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuFeatureMetadata.none(), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuOptimizerPolicyMetadata.fromGpuOptimize(true), + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuRegenerationMetadata.transitionalIrText(), + List.of(IrGpuBackendOutput.openClSource("javatogpu/sample/Demo/kernel.cl")), + "opencl", + "off" + ); + } + private static IrGpuArtifact repeatedCseTypedArtifact() { IrGpuTypedBody typedBody = new IrGpuTypedBody( IrGpuTypedBody.FORMAT, diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java index d41c01de..c125b645 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java @@ -955,7 +955,7 @@ void validationReportAndHistoryExposeRuntimeSnapshotWorkloadGateEvidence() throw assertTrue(reportMarkdown.contains("- Kernel `0` runtime IR handoff: stage=`optimized`, transformed=`true`, rollback=`false`, rejected=`false`, fallback=`none`, first=`optimized IrGpu is selected for backend lowering after runtime optimizer passes`")); assertTrue(reportMarkdown.contains("- Kernel `0` production mutation safety: enabled=`false`, gate=`not-requested`, profileRequested=`false`, first=`runtime IR participates in diagnostics, but production mutation is disabled because no production profile was requested`")); assertTrue(reportMarkdown.contains("- Kernel `0` I3 readiness: status=`review-ready`, sourcePromotion=`review-ready`, sourceReady=`true`, optimizerGate=`not-requested`, productionMutation=`false`, first=`I3 source pipeline is review-ready, but production mutation remains disabled until production gates are accepted`")); - assertTrue(reportMarkdown.contains("- Kernel `0` runtime optimizer drift: passes=`2`, applied=`2`, rolledBack=`0`, failed=`0`, proof=`0`, acceptedProof=`0`, blockingProof=`0`, optimizerFamilies=`0`, promotionReadyFamilies=`0`, selected=`optimized`, fallback=`none`, gate=`not-requested`")); + assertTrue(reportMarkdown.contains("- Kernel `0` runtime optimizer drift: passes=`2`, applied=`2`, rolledBack=`0`, failed=`0`, proof=`0`, acceptedProof=`0`, blockingProof=`0`, replacementPlanComplete=`0`, replacementPlanPartial=`0`, replacementPlanFirstBlocker=`none`, replacementPlanValidation=`0/0 valid, invalid=0`, replacementPlanValidationFirstBlocker=`none`, optimizerRules=`0`, optimizerRuleDetails=`none`, optimizerFamilies=`0`, promotionReadyFamilies=`0`, selected=`optimized`, fallback=`none`, gate=`not-requested`")); assertTrue(reportMarkdown.contains("- Kernel `0` diagnostics: `1`; first=`packaged IrGpu source reconstructed from runtime artifact loader with descriptor parity`")); assertFalse(reportMarkdown.contains("- Kernel `0` reconstruction blockers:")); assertTrue(reportMarkdown.contains("- Kernel `1`: `inline://integration/perlin-kernel.cl`, status=`blocked`, parity=`false`, runtimeEquivalence=`false`, sourceSwitching=`reject-production-irgpu-source`, operatorAccepted=`false`, runtimeIr=`original`, productionMutation=`false`, sourceReady=`false`, i3=`blocked`")); @@ -963,7 +963,7 @@ void validationReportAndHistoryExposeRuntimeSnapshotWorkloadGateEvidence() throw assertTrue(reportMarkdown.contains("- Kernel `1` runtime IR handoff: stage=`original`, transformed=`false`, rollback=`false`, rejected=`true`, fallback=`production-ir-gate-blocked`, first=`optimized IrGpu was rejected by the production IR acceptance gate; original IrGpu remains selected`")); assertTrue(reportMarkdown.contains("- Kernel `1` production mutation safety: enabled=`false`, gate=`blocked`, profileRequested=`true`, first=`runtime IR participates in diagnostics, but production mutation remains fail-closed until production optimizer gates pass`")); assertTrue(reportMarkdown.contains("- Kernel `1` I3 readiness: status=`blocked`, sourcePromotion=`blocked`, sourceReady=`false`, optimizerGate=`blocked`, productionMutation=`false`, first=`I3 pipeline is active for diagnostics, but source promotion or production mutation is still blocked`")); - assertTrue(reportMarkdown.contains("- Kernel `1` runtime optimizer drift: passes=`3`, applied=`1`, rolledBack=`0`, failed=`1`, proof=`0`, acceptedProof=`0`, blockingProof=`0`, optimizerFamilies=`0`, promotionReadyFamilies=`0`, selected=`original`, fallback=`production-ir-gate-blocked`, gate=`blocked`")); + assertTrue(reportMarkdown.contains("- Kernel `1` runtime optimizer drift: passes=`3`, applied=`1`, rolledBack=`0`, failed=`1`, proof=`0`, acceptedProof=`0`, blockingProof=`0`, replacementPlanComplete=`0`, replacementPlanPartial=`0`, replacementPlanFirstBlocker=`none`, replacementPlanValidation=`0/0 valid, invalid=0`, replacementPlanValidationFirstBlocker=`none`, optimizerRules=`0`, optimizerRuleDetails=`none`, optimizerFamilies=`0`, promotionReadyFamilies=`0`, selected=`original`, fallback=`production-ir-gate-blocked`, gate=`blocked`")); assertTrue(reportMarkdown.contains("- Kernel `1` diagnostics: `1`; first=`reconstructed source must match descriptor source before promotion review`")); assertTrue(reportMarkdown.contains("- Kernel blocker families: `source-parity=1`")); assertFalse(reportMarkdown.contains("runtimeIr=`unknown`")); @@ -980,8 +980,8 @@ void validationReportAndHistoryExposeRuntimeSnapshotWorkloadGateEvidence() throw assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("sourcePromotionFirstBlockers=runtime equivalence must execute and pass before backend source promotion=1")); assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("sourcePromotionFirstBlockerFamilies=runtime-equivalence=1")); assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("kernelCount=2")); - assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("kernel.0=inline://integration/image-kernel.cl[diagnostics=1, sourceSwitching=compile-irgpu-source-review/operatorAccepted=false/sourcePromotionFirstBlocker=none, runtimeIr=optimized, optimizerDrift=recorded/2passes/rollback=0/proof=0/acceptedProof=0/blockingProof=0/optimizerFamilies=0/promotionReadyFamilies=0/payloadCompleteFamilies=0/payloadCompleteAll=false, extensionParticipation=not-recorded/0executions/failedContinued=0/failedClosed=0/fallback=none, productionMutation=false, sourceReady=true, i3=review-ready, families=none]")); - assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("kernel.1=inline://integration/perlin-kernel.cl[diagnostics=1, sourceSwitching=reject-production-irgpu-source/operatorAccepted=false/sourcePromotionFirstBlocker=runtime equivalence must execute and pass before backend source promotion, runtimeIr=original, optimizerDrift=recorded/3passes/rollback=0/proof=0/acceptedProof=0/blockingProof=0/optimizerFamilies=0/promotionReadyFamilies=0/payloadCompleteFamilies=0/payloadCompleteAll=false, extensionParticipation=not-recorded/0executions/failedContinued=0/failedClosed=0/fallback=production-ir-gate-blocked, productionMutation=false, sourceReady=false, i3=blocked, families=source-parity=1]")); + assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("kernel.0=inline://integration/image-kernel.cl[diagnostics=1, sourceSwitching=compile-irgpu-source-review/operatorAccepted=false/sourcePromotionFirstBlocker=none, runtimeIr=optimized, optimizerDrift=recorded/2passes/rollback=0/proof=0/acceptedProof=0/blockingProof=0/replacementPlanComplete=0/replacementPlanPartial=0/replacementPlanFirstBlocker=none/replacementPlanValidationValid=0/replacementPlanValidationTotal=0/replacementPlanValidationInvalid=0/replacementPlanValidationFirstBlocker=none/optimizerRules=0/optimizerRuleDetails=none/optimizerFamilies=0/promotionReadyFamilies=0/payloadCompleteFamilies=0/payloadCompleteAll=false, extensionParticipation=not-recorded/0executions/failedContinued=0/failedClosed=0/fallback=none, productionMutation=false, sourceReady=true, i3=review-ready, families=none]")); + assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("kernel.1=inline://integration/perlin-kernel.cl[diagnostics=1, sourceSwitching=reject-production-irgpu-source/operatorAccepted=false/sourcePromotionFirstBlocker=runtime equivalence must execute and pass before backend source promotion, runtimeIr=original, optimizerDrift=recorded/3passes/rollback=0/proof=0/acceptedProof=0/blockingProof=0/replacementPlanComplete=0/replacementPlanPartial=0/replacementPlanFirstBlocker=none/replacementPlanValidationValid=0/replacementPlanValidationTotal=0/replacementPlanValidationInvalid=0/replacementPlanValidationFirstBlocker=none/optimizerRules=0/optimizerRuleDetails=none/optimizerFamilies=0/promotionReadyFamilies=0/payloadCompleteFamilies=0/payloadCompleteAll=false, extensionParticipation=not-recorded/0executions/failedContinued=0/failedClosed=0/fallback=production-ir-gate-blocked, productionMutation=false, sourceReady=false, i3=blocked, families=source-parity=1]")); assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("productionSourceSwitching=disabled")); assertTrue(entries.get(0).productionPromotionExplainabilityStatus().contains("contract=valid")); assertTrue(entries.get(0).productionPromotionExplainabilityStatus().contains("decisionMode=diagnostic-only")); From 498aa68b13286115843012c30cef2310ff083d99 Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Sun, 12 Jul 2026 22:30:03 +0300 Subject: [PATCH 21/32] wip: read-only preflight --- ROADMAP.md | 6 +- docs/IR-Optimizer.md | 2 +- docs/Runtime-Guide.md | 2 +- .../api/annotations/OpenCLAttributes.java | 1 + .../api/annotations/OpenCLQualifiers.java | 1 + ...dSourcePromotionWorkloadGateFormatter.java | 84 ++++++ ...BackendSourcePromotionWorkloadSummary.java | 192 +++++++++++++- ...ctionPromotionExplainabilityFormatter.java | 135 +++++++++- ...ductionPromotionExplainabilitySummary.java | 73 ++++++ ...tionPromotionExplainabilitySummaryCli.java | 53 ++++ .../runtime/GpuRuntimeIrPeepholePass.java | 105 ++++++++ ...meIrPeepholeRewriteSelectionReadiness.java | 126 +++++++++ .../GpuRuntimeIrPeepholeRewriteSketch.java | 193 ++++++++++++++ ...untimeIrPeepholeRewriteSketchConflict.java | 77 ++++++ .../GpuRuntimeOptimizerDriftArtifact.java | 245 ++++++++++++++++++ .../opencl/OpenClValidationReporter.java | 37 +++ ...rcePromotionWorkloadGateFormatterTest.java | 42 ++- ...endSourcePromotionWorkloadSummaryTest.java | 19 +- ...nPromotionExplainabilityFormatterTest.java | 9 + ...ionPromotionExplainabilitySummaryTest.java | 25 ++ .../GpuRuntimeCompileArtifactDumperTest.java | 47 ++++ .../GpuRuntimeIrOptimizerRegistryTest.java | 80 ++++++ .../opencl/OpenClValidationReportTest.java | 8 +- 23 files changed, 1549 insertions(+), 13 deletions(-) create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteSelectionReadiness.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteSketch.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteSketchConflict.java diff --git a/ROADMAP.md b/ROADMAP.md index 99b78162..f8be8881 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -445,7 +445,7 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i Build proof-backed local expression-tree rewrites such as `a + t * (b - a) -> mix(a, b, t)`, `a * b + c -> mad/fma(a, b, c)`, `min(max(x, lo), hi) -> clamp(x, lo, hi)`, ternary step forms -> `step(edge, x)`, and small fixed-width dot-product patterns. Mutation must stay fail-closed until structural replacement, typed-graph validation, runtime-equivalence, rollback, and A1/A2 evidence are stable. - [ ] Add a safe IR pattern-matching and rewrite visitor for peephole passes. The current typed preflight safely detects the initial `mad/fma` candidate shape without editing nodes. Next, add reusable typed-node traversal/replacement APIs that can prove complete-pattern replacement before editing anything. Partial matches must produce diagnostics only. Rule implementations should remain isolated (`mix`, `mad/fma`, `clamp`, `step`, `dot`) and report applied/skipped/blocked counts per rule so optimizer drift artifacts can detect regressions. - `GpuRuntimeIrPeepholeReplacementPlan` now adds the first read-only typed replacement-planning contract. The `mad/fma` rule records root, covered, and input node ids plus complete/partial blocker state for detected `a*b+c` / `c+a*b` shapes, `clamp` records read-only complete plans for `min(max(x, lo), hi)` typed call shapes, `step` records read-only complete plans for simple `x < edge ? 0 : 1` / `x >= edge ? 1 : 0` ternary shapes, `dot` records read-only plans for fixed-width additive multiply trees such as `(a0*b0)+(a1*b1)`, and `mix` records read-only complete plans for `a + t * (b - a)` / `t * (b - a) + a`. The pass still reports `rewrite-engine-not-implemented` and never mutates selected IR. All current peephole families now have diagnostic matchers; the remaining work is shared traversal/rewrite mechanics and proof gates, not family visibility. + `GpuRuntimeIrPeepholeReplacementPlan` now adds the first read-only typed replacement-planning contract. The `mad/fma` rule records root, covered, and input node ids plus complete/partial blocker state for detected `a*b+c` / `c+a*b` shapes, `clamp` records read-only complete plans for `min(max(x, lo), hi)` typed call shapes, `step` records read-only complete plans for simple `x < edge ? 0 : 1` / `x >= edge ? 1 : 0` ternary shapes, `dot` records read-only plans for fixed-width additive multiply trees such as `(a0*b0)+(a1*b1)`, and `mix` records read-only complete plans for `a + t * (b - a)` / `t * (b - a) + a`. Rewrite-sketch overlap/conflict detection and `rewriteSelection.*` selection-readiness preflight now make conflict-resolution, rewrite-builder, runtime-equivalence, approval, mutation, and selected-IR blockers explicit before any rewrite engine exists. The pass still reports `rewrite-engine-not-implemented` and never mutates selected IR. All current peephole families now have diagnostic matchers; the remaining work is shared traversal/rewrite mechanics and proof gates, not family visibility. - [x] Add Java-source fast-math policy metadata for runtime optimizer decisions. `@GPUOptimize(fastMath = false)` is now the production-safe default policy surface. Java-source frontend artifacts persist the explicit annotation source or `default-strict`, runtime optimization requests expose the resolved policy, and optimizer hooks can declare `requiresFastMath()` so the registry skips them without invocation unless the method explicitly opts in. This does not enable production mutation by itself. - [x] Preserve fast-math policy through the ASM frontend. @@ -455,7 +455,7 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i - [ ] Add proof, runtime-equivalence, and rollback gates for peephole rewrites. Peephole rewrites must emit `GpuRuntimeIrOptimizationProofArtifact` fields with optimizer family (`peephole`, `instcombine`, or per-rule families), rule counters, fast-math policy, original/transformed IR identities, and compact/durable runtime-equivalence payload evidence. Failed proofs, incomplete pattern rewrites, runtime-equivalence failures, or rollback diagnostics must keep the original IR selected. - [ ] Surface peephole telemetry in optimizer drift and promotion artifacts. - `runtime-optimizer-drift.properties` now surfaces read-only replacement-plan evidence with aggregate complete/partial counts, first incomplete-plan blocker, and compact `optimizerRule.*` summaries from the existing `rule.N.*` proof fields, so CI can detect `mad/fma`, `clamp`, `step`, `dot`, and `mix` planning drift before any rewrite engine exists. Workload gates, compact workload summaries, OpenCL validation Markdown/history, I3 summaries, and production-promotion explainability now carry those replacement-plan counters and per-rule summaries without affecting readiness or production decisions. Production promotion must remain disabled until these rule families have stable A1/A2 runtime evidence and backend/vendor confidence. + `runtime-optimizer-drift.properties` now surfaces read-only replacement-plan evidence with aggregate complete/partial counts, structural validation counts, rewrite-sketch ready/blocked counts, overlap/conflict counters, `rewriteSelection.*` status/first-blocker guardrails, and compact `optimizerRule.*` summaries from the existing `rule.N.*` proof fields, so CI can detect `mad/fma`, `clamp`, `step`, `dot`, and `mix` planning drift before any rewrite engine exists. Workload gates, compact workload summaries, OpenCL validation Markdown/history, I3 summaries, and production-promotion explainability now carry those replacement-plan counters and per-rule summaries without affecting readiness or production decisions. Production promotion must remain disabled until these rule families have stable A1/A2 runtime evidence and backend/vendor confidence. #### I3.5 Vendor/device-specific optimization strategy @@ -598,7 +598,7 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i - [x] Allow third-party validators to extend the existing IR Validator model. `GpuIrValidationProvider` remains a ServiceLoader SPI, while `GpuIrValidationReportEntry` now carries provider alias, extension id/version, rule id, severity, source anchor, method identity, entry-point state, and arbitrary provider fields. `GpuIrValidationRunner` automatically enriches legacy four-field entries with the actual provider metadata and Java/ASM source anchor, so downstream validators do not need to duplicate compiler location logic. `GpuCompilerProcessor` persists the enriched fields in the existing validation properties artifact without breaking the established provider alias contract. The built-in optional `ir-validation` provider now exposes stable id `javatogpu.ir-validation`, version `1`, aggregate rule id, and derived INFO/WARNING/ERROR severity. - [x] Allow third-party optimizer and peephole passes without core forks. - External libraries can contribute full `GpuRuntimeIrOptimizationPass` implementations or typed-body `GpuRuntimeIrPeepholeRule` implementations through ServiceLoader. `GpuRuntimeIrPeepholeRuleRegistry` provides deterministic ordering, duplicate rule-id and extension-id rejection, phase/capability/permission validation, advisory failure isolation, and production-profile fail-closed behavior. Rule reports preserve rule id/version, extension id/version, method, candidate/proposal counts, proof status, diagnostics, and custom fields. The built-in matcher set now shares `GpuRuntimeIrTypedNodeGraph` for read-only typed-node traversal and emits `GpuRuntimeIrPeepholeReplacementPlanValidation` evidence so future structural rewrites have a fail-closed graph/plan boundary before any rewrite builder exists. Drift artifacts, workload gates, OpenCL validation reports/history, and production-promotion explainability now surface plan-validation total/valid/invalid counts and first blockers as evidence-only telemetry. The current pass remains diagnostic/proposal-only: rules cannot bypass fast-math policy, proof requirements, production authorization, rollback, runtime-equivalence, or final IR selection gates. + External libraries can contribute full `GpuRuntimeIrOptimizationPass` implementations or typed-body `GpuRuntimeIrPeepholeRule` implementations through ServiceLoader. `GpuRuntimeIrPeepholeRuleRegistry` provides deterministic ordering, duplicate rule-id and extension-id rejection, phase/capability/permission validation, advisory failure isolation, and production-profile fail-closed behavior. Rule reports preserve rule id/version, extension id/version, method, candidate/proposal counts, proof status, diagnostics, and custom fields. The built-in matcher set now shares `GpuRuntimeIrTypedNodeGraph` for read-only typed-node traversal, emits `GpuRuntimeIrPeepholeReplacementPlanValidation` evidence, and produces mutation-free `GpuRuntimeIrPeepholeRewriteSketch` records so future structural rewrites have a fail-closed graph/plan/sketch boundary before any rewrite builder exists. Drift artifacts, workload gates, OpenCL validation reports/history, and production-promotion explainability now surface plan-validation and rewrite-sketch counts/first blockers as evidence-only telemetry. The current pass remains diagnostic/proposal-only: rules cannot bypass fast-math policy, proof requirements, production authorization, rollback, runtime-equivalence, or final IR selection gates. - [x] Add optional approval-bound IR optimizer modules. `ir-optimizer` and `ir-vendor-optimizer` now exist as optional modules that keep `GpuIrValidationProvider` strictly read-only and expose optimization only through separate `MUTATION_PROPOSAL` provider contracts. The backend-neutral runtime bridge records proposal/provider evidence without loading vendor providers by default, while vendor-specific providers must opt into `GpuIrVendorOptimizationProposalProvider` / `GpuIrVendorOptimizationProposalRegistry`. Optimizer proposal evidence now includes approval-template status: real immutable rewrites can emit pending templates under `META-INF/javatogpu/ir-optimization-approvals/`, while preview/no-change proposals record `not-applicable` blockers. Approval artifacts are bound to optimizer id/version, original and optimized `IrGpu` identities, backend target, optimization profile, proof verdict, and rollback requirement; they remain manual-review-only and do not authorize production mutation or selected-IR replacement by themselves. Runtime OpenCL validation now surfaces this evidence through `runtime-ir-optimizer-evidence.properties` and the `Runtime IR Optimizer Evidence` report section, including approval-template pending/not-applicable counts. - [x] Add the third-party backend/device policy foundation. diff --git a/docs/IR-Optimizer.md b/docs/IR-Optimizer.md index e3eee3fe..093c3646 100644 --- a/docs/IR-Optimizer.md +++ b/docs/IR-Optimizer.md @@ -68,7 +68,7 @@ The default bridge is intentionally proposal-only. It can surface canonicalizati Runtime artifact dumping records bridge/provider evidence in `runtime-ir-optimizer-evidence.properties`. The artifact is filtered by the stable `javatogpu.ir-optimizer` provider prefix and includes pass counts, outcomes, proposal-only/selected counts, original/transformed IR identities, proof artifact fields, and diagnostics. This is an evidence surface only; it does not authorize optimized IR selection or production mutation by itself. -Runtime peephole/InstCombine proof is also kept read-only. `GpuRuntimeIrPeepholeReplacementPlan` records future structural replacement intent, including root node, covered node ids, input node ids, replacement kind, completeness, and first blocker. `GpuRuntimeIrTypedNodeGraph` is the shared read-only traversal view for typed peephole rules, and `GpuRuntimeIrPeepholeReplacementPlanValidation` checks each emitted plan against that graph before it is surfaced. Validation fields such as `replacementPlan.validation.invalid.count` and `rule.N.replacementPlan.validation.firstBlocker` fail closed on missing roots, uncovered roots, or missing covered/input nodes; they still do not build replacement nodes or authorize mutation. The current matcher set covers `mad/fma` for `a*b+c` / `c+a*b`, `clamp` for `min(max(x, lo), hi)`, `step` for simple `x < edge ? 0 : 1` / `x >= edge ? 1 : 0` ternaries, `dot` for additive multiply trees such as `(a0*b0)+(a1*b1)`, and `mix` for `a + t * (b - a)` / `t * (b - a) + a`. `runtime-optimizer-drift.properties` aggregates complete/partial replacement-plan counts, structural plan-validation total/valid/invalid counts, first incomplete/invalid blockers, and compact `optimizerRule.*` summaries for CI drift detection. Workload gates, validation reports/history, I3 summaries, and production-promotion explainability carry those counters forward as evidence-only telemetry. The pass still reports `rewrite-engine-not-implemented` and never mutates the selected IR. +Runtime peephole/InstCombine proof is also kept read-only. `GpuRuntimeIrPeepholeReplacementPlan` records future structural replacement intent, including root node, covered node ids, input node ids, replacement kind, completeness, and first blocker. `GpuRuntimeIrTypedNodeGraph` is the shared read-only traversal view for typed peephole rules, and `GpuRuntimeIrPeepholeReplacementPlanValidation` checks each emitted plan against that graph before it is surfaced. `GpuRuntimeIrPeepholeRewriteSketch` is the next mutation-free skeleton: a complete and valid plan can become `sketch-ready`, but the sketch still records `rewriteBuilderImplemented=false`, `mutationAllowed=false`, and `selectedIrReplacement=false`; runtime equivalence and approval remain required. `GpuRuntimeIrPeepholeRewriteSelectionReadiness` now sits above ready/blocked sketches as a read-only selection preflight: it reports sketch counts, conflict counts, status, first blocker, missing rewrite-builder/conflict-resolution/proof/approval gates, and disabled mutation/selection flags without choosing any optimized IR. Validation fields such as `replacementPlan.validation.invalid.count` and `rule.N.replacementPlan.validation.firstBlocker` fail closed on missing roots, uncovered roots, or missing covered/input nodes; they still do not build replacement nodes or authorize mutation. The current matcher set covers `mad/fma` for `a*b+c` / `c+a*b`, `clamp` for `min(max(x, lo), hi)`, `step` for simple `x < edge ? 0 : 1` / `x >= edge ? 1 : 0` ternaries, `dot` for additive multiply trees such as `(a0*b0)+(a1*b1)`, and `mix` for `a + t * (b - a)` / `t * (b - a) + a`. `runtime-optimizer-drift.properties` aggregates complete/partial replacement-plan counts, structural plan-validation total/valid/invalid counts, rewrite-sketch ready/blocked counts, overlap/conflict counters, `rewriteSelection.*` status/first-blocker guardrails, and compact `optimizerRule.*` summaries for CI drift detection. Workload gates, validation reports/history, I3 summaries, and production-promotion explainability carry those counters forward as evidence-only telemetry. The pass still reports `rewrite-engine-not-implemented` and never mutates the selected IR. The same evidence artifact now records `approvalTemplate.*` fields for each optimizer pass. Real proposal rewrites with distinct original/optimized identities and accepted proof metadata are marked `pending`; preview and no-change proposals are marked `not-applicable` with a first blocker such as `proposal-decision-not-proposed`. diff --git a/docs/Runtime-Guide.md b/docs/Runtime-Guide.md index 90b8e087..cdde8608 100644 --- a/docs/Runtime-Guide.md +++ b/docs/Runtime-Guide.md @@ -235,7 +235,7 @@ Non-analysis optimizer passes with runtime-equivalence payload evidence also pro When the optional IR optimizer bridge participates, each kernel directory may also receive `runtime-ir-optimizer-evidence.properties`. The artifact records only proposal/provider evidence from stable `javatogpu.ir-optimizer` sources: pass counts, proposal-only versus selected optimized counts, rollback counts, IR identities, proof fields, approval-template applicability, and diagnostics. Constant-folding preview proof is also aggregated into `constantFoldingPreview.*` fields for pass count, candidate count, skipped blockers, required runtime-equivalence/approval gates, and unresolved integer-overflow / floating-point-rounding safety flags. Safe-local-CSE preview proof is aggregated into `safeLocalCsePreview.*` fields for pass count, expression/candidate/duplicate/equivalence-class counts, unsupported-operator / impure-operand / control-flow-boundary blockers, required runtime-equivalence/approval gates, and unresolved dominance / side-effect-freedom proof flags. Typed dead-code preview proof is aggregated into `typedDeadCodePreview.*` fields for pass count, typed/reachable/unreachable node counts, missing-root / missing-child-reference / side-effecting-unreachable blockers, required runtime-equivalence/approval gates, and unresolved side-effect-freedom proof flags. The artifact also records `previewReadiness.*` fields with aggregate status, recorded/candidate/blocked family counts, and a per-family status summary so CI can distinguish no-candidate previews from proof-blocked previews and review-ready runtime-equivalence candidates. `runtimeEquivalenceReview.*` records the next fail-closed review boundary: status, eligibility, requirement, first blocker, family summary, disabled production mutation, disabled selected-IR replacement, and manual-review-only state. `reviewPackage.*` records the manual-review package boundary above that review gate, including package status, requirement/completeness, first blocker, proposal-pass count, pending approval count, original/optimized IR and proof-summary requirements, manual-review-only state, and disabled production mutation / selected-IR replacement. `openClValidationReport` aggregates this into `Runtime IR Optimizer Evidence` for CI visibility, including constant-folding, safe-local-CSE, typed dead-code preview totals, preview-readiness status, runtime-equivalence review eligibility, and review-package status. `validateOpenClRuntimeIrOptimizerEvidence` then validates the raw runtime evidence files and fails CI if those fail-closed guardrails are missing, completed early, or production-enabling. The section and validator are evidence-only and do not participate in history, source switching, selected-IR replacement, or production mutation gates. -Runtime peephole/InstCombine remains diagnostic-only. `GpuRuntimeIrPeepholeReplacementPlan` records read-only typed replacement plans for current rule families: `mad/fma`, `clamp`, `step`, `dot`, and `mix`. Built-in rules now use `GpuRuntimeIrTypedNodeGraph` as the shared read-only typed-node traversal surface, and each emitted plan is checked by `GpuRuntimeIrPeepholeReplacementPlanValidation` before proof fields are exported. Proof fields expose the candidate root node, covered nodes, input nodes, completeness, first blocker, and structural validation counters such as `replacementPlan.validation.invalid.count` / `rule.N.replacementPlan.validation.firstBlocker`. `runtime-optimizer-drift.properties` aggregates the complete/partial replacement-plan counts, plan-validation total/valid/invalid counts, first incomplete/invalid blockers, and compact `optimizerRule.*` summaries from existing `rule.N.*` proof fields so CI can detect planning drift while the rewrite engine remains absent. Workload gates, compact workload summaries, OpenCL validation Markdown/history, I3 summaries, and production-promotion explainability surface the same counters as evidence-only telemetry. These plans describe what a future structural rewrite would need to replace; they do not edit typed bodies, select optimized IR, or enable production mutation. +Runtime peephole/InstCombine remains diagnostic-only. `GpuRuntimeIrPeepholeReplacementPlan` records read-only typed replacement plans for current rule families: `mad/fma`, `clamp`, `step`, `dot`, and `mix`. Built-in rules now use `GpuRuntimeIrTypedNodeGraph` as the shared read-only typed-node traversal surface, and each emitted plan is checked by `GpuRuntimeIrPeepholeReplacementPlanValidation` before proof fields are exported. `GpuRuntimeIrPeepholeRewriteSketch` records the mutation-free structural rewrite skeleton above those validated plans: ready sketches prove only that the future rewrite shape is describable, while `rewriteBuilderImplemented`, `mutationAllowed`, and `selectedIrReplacement` remain `false`. `rewriteSelection.*` fields add a read-only selection preflight over those sketches, including sketch/conflict counts, selection status, first blocker, proof/approval requirements, and disabled mutation/selection guardrails. Proof fields expose the candidate root node, covered nodes, input nodes, completeness, first blocker, structural validation counters such as `replacementPlan.validation.invalid.count` / `rule.N.replacementPlan.validation.firstBlocker`, sketch counters such as `rewriteSketch.ready.count` / `rule.N.rewriteSketch.blocked.count`, and selection preflight fields such as `rewriteSelection.status` / `rewriteSelection.firstBlocker`. `runtime-optimizer-drift.properties` aggregates the complete/partial replacement-plan counts, plan-validation total/valid/invalid counts, rewrite-sketch ready/blocked counts, conflict counts, selection-readiness guardrails, first incomplete/invalid/sketch/selection blockers, and compact `optimizerRule.*` summaries from existing `rule.N.*` proof fields so CI can detect planning drift while the rewrite engine remains absent. Workload gates, compact workload summaries, OpenCL validation Markdown/history, I3 summaries, and production-promotion explainability surface the same counters as evidence-only telemetry. These plans and sketches describe what a future structural rewrite would need to replace; they do not edit typed bodies, select optimized IR, or enable production mutation. The same index records `familyBinding.*`. Binding remains `not-bound` unless exactly one optimizer family has passed runtime evidence whose comparison mode is explicitly family-specific (`optimizer-family::...`). Descriptor-versus-reconstructed-source evidence is pipeline validation and is deliberately rejected as optimizer-family proof. diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/OpenCLAttributes.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/OpenCLAttributes.java index d7aedfd9..2019a598 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/OpenCLAttributes.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/OpenCLAttributes.java @@ -20,6 +20,7 @@ */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD}) +@Deprecated public @interface OpenCLAttributes { /** diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/OpenCLQualifiers.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/OpenCLQualifiers.java index 25b4c816..32f7d3e4 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/OpenCLQualifiers.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/OpenCLQualifiers.java @@ -27,6 +27,7 @@ */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) +@Deprecated public @interface OpenCLQualifiers { /** diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatter.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatter.java index ff7ff711..323675fc 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatter.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatter.java @@ -461,6 +461,34 @@ private static void copyRuntimeOptimizerDriftProperties(Properties source, Prope target.setProperty("runtimeOptimizerDrift.replacementPlan.validation.valid.count", "0"); target.setProperty("runtimeOptimizerDrift.replacementPlan.validation.invalid.count", "0"); target.setProperty("runtimeOptimizerDrift.replacementPlan.validation.firstBlocker", "none"); + target.setProperty("runtimeOptimizerDrift.rewriteSketch.count", "0"); + target.setProperty("runtimeOptimizerDrift.rewriteSketch.ready.count", "0"); + target.setProperty("runtimeOptimizerDrift.rewriteSketch.blocked.count", "0"); + target.setProperty("runtimeOptimizerDrift.rewriteSketch.firstBlocker", "none"); + target.setProperty("runtimeOptimizerDrift.rewriteSketch.rewriteBuilderImplemented", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteSketch.mutationAllowed", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteSketch.selectedIrReplacement", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteSketch.conflict.count", "0"); + target.setProperty("runtimeOptimizerDrift.rewriteSketch.conflict.firstBlocker", "none"); + target.setProperty("runtimeOptimizerDrift.rewriteSketch.conflict.conflictResolutionImplemented", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteSketch.conflict.selectionApplied", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteSketch.conflict.mutationAllowed", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteSketch.conflict.selectedIrReplacement", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteSelection.sketch.count", "0"); + target.setProperty("runtimeOptimizerDrift.rewriteSelection.sketch.ready.count", "0"); + target.setProperty("runtimeOptimizerDrift.rewriteSelection.sketch.blocked.count", "0"); + target.setProperty("runtimeOptimizerDrift.rewriteSelection.conflict.count", "0"); + target.setProperty("runtimeOptimizerDrift.rewriteSelection.status", "not-required"); + target.setProperty("runtimeOptimizerDrift.rewriteSelection.firstBlocker", "no-rewrite-sketches"); + target.setProperty("runtimeOptimizerDrift.rewriteSelection.rewriteBuilderImplemented", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteSelection.conflictResolutionImplemented", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteSelection.runtimeEquivalenceRequired", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteSelection.runtimeEquivalenceProven", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteSelection.approvalRequired", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteSelection.approvalAccepted", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteSelection.mutationAllowed", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteSelection.selectionApplied", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteSelection.selectedIrReplacement", "false"); target.setProperty("runtimeOptimizerDrift.optimizerRule.count", "0"); target.setProperty("runtimeOptimizerDrift.optimizerRule.summary", "none"); target.setProperty("runtimeOptimizerDrift.fallbackDecision", target.getProperty("runtimeIrHandoff.fallbackDecision", "none")); @@ -490,6 +518,34 @@ private static void copyRuntimeOptimizerDriftProperties(Properties source, Prope copyRuntimeOptimizerDriftProperty(source, target, "replacementPlan.validation.valid.count", "0"); copyRuntimeOptimizerDriftProperty(source, target, "replacementPlan.validation.invalid.count", "0"); copyRuntimeOptimizerDriftProperty(source, target, "replacementPlan.validation.firstBlocker", "none"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.ready.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.blocked.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.firstBlocker", "none"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.rewriteBuilderImplemented", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.mutationAllowed", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.selectedIrReplacement", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.conflict.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.conflict.firstBlocker", "none"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.conflict.conflictResolutionImplemented", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.conflict.selectionApplied", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.conflict.mutationAllowed", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.conflict.selectedIrReplacement", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.sketch.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.sketch.ready.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.sketch.blocked.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.conflict.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.status", "not-required"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.firstBlocker", "no-rewrite-sketches"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.rewriteBuilderImplemented", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.conflictResolutionImplemented", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.runtimeEquivalenceRequired", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.runtimeEquivalenceProven", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.approvalRequired", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.approvalAccepted", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.mutationAllowed", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.selectionApplied", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.selectedIrReplacement", "false"); copyRuntimeOptimizerDriftProperty(source, target, "optimizerRule.count", "0"); copyRuntimeOptimizerDriftProperty(source, target, "optimizerRule.summary", "none"); copyIndexedPropertyGroup(source, target, "runtimeOptimizerDrift.optimizerRule", "optimizerRule"); @@ -827,6 +883,34 @@ private static void appendRuntimeOptimizerDrift(StringBuilder builder, String pr builder.append(prefix).append("runtimeOptimizerDrift.replacementPlan.validation.valid.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementPlan.validation.valid.count", "0")).append('\n'); builder.append(prefix).append("runtimeOptimizerDrift.replacementPlan.validation.invalid.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementPlan.validation.invalid.count", "0")).append('\n'); builder.append(prefix).append("runtimeOptimizerDrift.replacementPlan.validation.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.replacementPlan.validation.firstBlocker", "none")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.ready.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.ready.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.blocked.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.blocked.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.firstBlocker", "none")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.rewriteBuilderImplemented=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.rewriteBuilderImplemented", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.mutationAllowed", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.selectedIrReplacement", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.conflict.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.conflict.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.conflict.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.conflict.firstBlocker", "none")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.conflict.conflictResolutionImplemented=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.conflict.conflictResolutionImplemented", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.conflict.selectionApplied=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.conflict.selectionApplied", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.conflict.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.conflict.mutationAllowed", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.conflict.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.conflict.selectedIrReplacement", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.sketch.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.sketch.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.sketch.ready.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.sketch.ready.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.sketch.blocked.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.sketch.blocked.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.conflict.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.conflict.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.status=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.status", "not-required")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.firstBlocker", "no-rewrite-sketches")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.rewriteBuilderImplemented=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.rewriteBuilderImplemented", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.conflictResolutionImplemented=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.conflictResolutionImplemented", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.runtimeEquivalenceRequired=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.runtimeEquivalenceRequired", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.runtimeEquivalenceProven=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.runtimeEquivalenceProven", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.approvalRequired=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.approvalRequired", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.approvalAccepted=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.approvalAccepted", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.mutationAllowed", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.selectionApplied=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.selectionApplied", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.selectedIrReplacement", "false")).append('\n'); builder.append(prefix).append("runtimeOptimizerDrift.optimizerRule.count=").append(entry.getProperty("runtimeOptimizerDrift.optimizerRule.count", "0")).append('\n'); builder.append(prefix).append("runtimeOptimizerDrift.optimizerRule.summary=").append(entry.getProperty("runtimeOptimizerDrift.optimizerRule.summary", "none")).append('\n'); appendIndexedPropertyGroup(builder, prefix, entry, "runtimeOptimizerDrift.optimizerRule"); diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummary.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummary.java index 51edc5e1..767339cc 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummary.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummary.java @@ -31,6 +31,14 @@ public record GpuBackendSourcePromotionWorkloadSummary( int optimizerReplacementPlanValidationValidCount, int optimizerReplacementPlanValidationInvalidCount, String optimizerReplacementPlanValidationFirstBlockers, + int optimizerRewriteSketchCount, + int optimizerRewriteSketchReadyCount, + int optimizerRewriteSketchBlockedCount, + String optimizerRewriteSketchFirstBlockers, + int optimizerRewriteSketchConflictCount, + String optimizerRewriteSketchConflictFirstBlockers, + String optimizerRewriteSelectionStatuses, + String optimizerRewriteSelectionFirstBlockers, int optimizerRuleCount, String optimizerRuleSummary, String optimizerRuleDetails, @@ -73,6 +81,14 @@ public static GpuBackendSourcePromotionWorkloadSummary notRecorded() { 0, "", 0, + 0, + 0, + "", + 0, + "", + "", + "", + 0, "none", "none", 0, @@ -129,6 +145,14 @@ public static GpuBackendSourcePromotionWorkloadSummary fromProperties(Properties sumKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.replacementPlan.validation.valid.count"), sumKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.replacementPlan.validation.invalid.count"), summarizeKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.replacementPlan.validation.firstBlocker"), + sumKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.rewriteSketch.count"), + sumKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.rewriteSketch.ready.count"), + sumKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.rewriteSketch.blocked.count"), + summarizeKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.rewriteSketch.firstBlocker"), + sumKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.rewriteSketch.conflict.count"), + summarizeKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.rewriteSketch.conflict.firstBlocker"), + summarizeRewriteSelectionStatuses(properties, kernelCount), + summarizeRewriteSelectionFirstBlockers(properties, kernelCount), sumKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.optimizerRule.count"), summarizeKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.optimizerRule.summary"), summarizeOptimizerRules(properties, kernelCount), @@ -180,6 +204,7 @@ public String historyStatus() { + ", optimizerPayloadCompleteAll=" + optimizerFamilyPayloadCompleteAll + optimizerReplacementPlanEvidenceText() + + optimizerRewriteSketchEvidenceText() + optimizerRuleSummaryText() + optimizerFamilySummaryText() + runtimeExtensionParticipationEvidenceText() @@ -209,6 +234,7 @@ public String historyStatus() { + ", optimizerPayloadCompleteAll=" + optimizerFamilyPayloadCompleteAll + optimizerReplacementPlanEvidenceText() + + optimizerRewriteSketchEvidenceText() + optimizerRuleSummaryText() + optimizerFamilySummaryText() + runtimeExtensionParticipationEvidenceText() @@ -312,6 +338,44 @@ private String optimizerReplacementPlanEvidenceText() { return builder.toString(); } + private String optimizerRewriteSketchEvidenceText() { + if (optimizerRewriteSketchCount == 0 + && optimizerRewriteSketchReadyCount == 0 + && optimizerRewriteSketchBlockedCount == 0 + && optimizerRewriteSketchConflictCount == 0 + && optimizerRewriteSketchFirstBlockers.isBlank() + && optimizerRewriteSketchConflictFirstBlockers.isBlank() + && optimizerRewriteSelectionStatuses.isBlank() + && optimizerRewriteSelectionFirstBlockers.isBlank()) { + return ""; + } + StringBuilder builder = new StringBuilder(", optimizerRewriteSketches=ready=") + .append(optimizerRewriteSketchReadyCount) + .append("/total=") + .append(optimizerRewriteSketchCount) + .append("/blocked=") + .append(optimizerRewriteSketchBlockedCount) + .append("/conflicts=") + .append(optimizerRewriteSketchConflictCount) + .append("/rewriteBuilderImplemented=false") + .append("/mutationAllowed=false") + .append("/selectedIrReplacement=false"); + if (!optimizerRewriteSketchFirstBlockers.isBlank()) { + builder.append("/firstBlockers=").append(optimizerRewriteSketchFirstBlockers); + } + if (!optimizerRewriteSketchConflictFirstBlockers.isBlank()) { + builder.append("/conflictFirstBlockers=").append(optimizerRewriteSketchConflictFirstBlockers); + } + if (!optimizerRewriteSelectionStatuses.isBlank()) { + builder.append("/selectionStatus=").append(optimizerRewriteSelectionStatuses); + } + if (!optimizerRewriteSelectionFirstBlockers.isBlank()) { + builder.append("/selectionFirstBlockers=").append(optimizerRewriteSelectionFirstBlockers); + } + builder.append("/selectionApplied=false"); + return builder.toString(); + } + private static String summarizeReplacementPlanFirstBlockers(Properties properties, int kernelCount) { Map blockerCounts = new LinkedHashMap<>(); for (int index = 0; index < kernelCount; index++) { @@ -339,6 +403,36 @@ private static String summarizeKernelProperty(Properties properties, int kernelC return summarizeCounts(counts); } + private static String summarizeRewriteSelectionStatuses(Properties properties, int kernelCount) { + Map counts = new LinkedHashMap<>(); + for (int index = 0; index < kernelCount; index++) { + String value = properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteSelection.status", + "" + ); + if (value.isBlank() || "unknown".equals(value) || "not-required".equals(value)) { + continue; + } + counts.merge(value, 1, Integer::sum); + } + return summarizeCounts(counts); + } + + private static String summarizeRewriteSelectionFirstBlockers(Properties properties, int kernelCount) { + Map counts = new LinkedHashMap<>(); + for (int index = 0; index < kernelCount; index++) { + String value = properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteSelection.firstBlocker", + "" + ); + if (value.isBlank() || "none".equals(value) || "unknown".equals(value) || "no-rewrite-sketches".equals(value)) { + continue; + } + counts.merge(value, 1, Integer::sum); + } + return summarizeCounts(counts); + } + private static String summarizeOptimizerFamilies(Properties properties, int kernelCount) { Map families = new LinkedHashMap<>(); for (int index = 0; index < kernelCount; index++) { @@ -595,6 +689,71 @@ private static String summarizeKernelEvidence(Properties properties, int kernelC "kernel." + index + ".runtimeOptimizerDrift.replacementPlan.validation.firstBlocker", "none" )) + .append("/rewriteSketchReady=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteSketch.ready.count", + "0" + )) + .append("/rewriteSketchTotal=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteSketch.count", + "0" + )) + .append("/rewriteSketchBlocked=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteSketch.blocked.count", + "0" + )) + .append("/rewriteSketchFirstBlocker=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteSketch.firstBlocker", + "none" + )) + .append("/rewriteSketchConflicts=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteSketch.conflict.count", + "0" + )) + .append("/rewriteSketchConflictFirstBlocker=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteSketch.conflict.firstBlocker", + "none" + )) + .append("/rewriteSketchConflictResolutionImplemented=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteSketch.conflict.conflictResolutionImplemented", + "false" + )) + .append("/rewriteSketchSelectionApplied=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteSketch.conflict.selectionApplied", + "false" + )) + .append("/rewriteSelectionStatus=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteSelection.status", + "not-required" + )) + .append("/rewriteSelectionFirstBlocker=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteSelection.firstBlocker", + "no-rewrite-sketches" + )) + .append("/rewriteSelectionApplied=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteSelection.selectionApplied", + "false" + )) + .append("/rewriteBuilderImplemented=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteSketch.rewriteBuilderImplemented", + "false" + )) + .append("/selectedIrReplacement=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteSketch.selectedIrReplacement", + "false" + )) .append("/optimizerRules=") .append(properties.getProperty("kernel." + index + ".runtimeOptimizerDrift.optimizerRule.count", "0")) .append("/optimizerRuleDetails=") @@ -753,6 +912,10 @@ private static void collectKernelOptimizerRules( parsePositiveInt(properties.getProperty(prefix + "replacementPlan.validation.valid.count", "0")), parsePositiveInt(properties.getProperty(prefix + "replacementPlan.validation.invalid.count", "0")), properties.getProperty(prefix + "replacementPlan.validation.firstBlocker", "none"), + parsePositiveInt(properties.getProperty(prefix + "rewriteSketch.count", "0")), + parsePositiveInt(properties.getProperty(prefix + "rewriteSketch.ready.count", "0")), + parsePositiveInt(properties.getProperty(prefix + "rewriteSketch.blocked.count", "0")), + properties.getProperty(prefix + "rewriteSketch.firstBlocker", "none"), properties.getProperty(prefix + "firstBlocker", properties.getProperty(prefix + "replacementPlan.firstBlocker", "none")) )); } @@ -790,6 +953,14 @@ private static String formatOptimizerRules(Map r .append(rule.replacementPlanValidationInvalidCount()) .append(", planValidationFirstBlocker=") .append(rule.replacementPlanValidationFirstBlocker()) + .append(", rewriteSketches=") + .append(rule.rewriteSketchCount()) + .append(", readySketches=") + .append(rule.rewriteSketchReadyCount()) + .append(", blockedSketches=") + .append(rule.rewriteSketchBlockedCount()) + .append(", rewriteSketchFirstBlocker=") + .append(rule.rewriteSketchFirstBlocker()) .append(", firstBlocker=") .append(rule.firstBlocker()) .append(']'); @@ -876,11 +1047,15 @@ private record OptimizerRuleAggregate( int replacementPlanValidationValidCount, int replacementPlanValidationInvalidCount, String replacementPlanValidationFirstBlocker, + int rewriteSketchCount, + int rewriteSketchReadyCount, + int rewriteSketchBlockedCount, + String rewriteSketchFirstBlocker, String firstBlocker ) { private static OptimizerRuleAggregate empty(String id) { - return new OptimizerRuleAggregate(id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "none", "none"); + return new OptimizerRuleAggregate(id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "none", 0, 0, 0, "none", "none"); } private OptimizerRuleAggregate add( @@ -896,6 +1071,10 @@ private OptimizerRuleAggregate add( int replacementPlanValidationValidCount, int replacementPlanValidationInvalidCount, String nextValidationBlocker, + int rewriteSketchCount, + int rewriteSketchReadyCount, + int rewriteSketchBlockedCount, + String nextRewriteSketchBlocker, String nextBlocker ) { String blocker = firstBlocker; @@ -906,6 +1085,13 @@ private OptimizerRuleAggregate add( && !"none".equals(nextValidationBlocker)) { validationBlocker = nextValidationBlocker; } + String sketchBlocker = rewriteSketchFirstBlocker; + if ("none".equals(sketchBlocker) + && nextRewriteSketchBlocker != null + && !nextRewriteSketchBlocker.isBlank() + && !"none".equals(nextRewriteSketchBlocker)) { + sketchBlocker = nextRewriteSketchBlocker; + } if ("none".equals(blocker) && nextBlocker != null && !nextBlocker.isBlank() @@ -926,6 +1112,10 @@ private OptimizerRuleAggregate add( this.replacementPlanValidationValidCount + replacementPlanValidationValidCount, this.replacementPlanValidationInvalidCount + replacementPlanValidationInvalidCount, validationBlocker, + this.rewriteSketchCount + rewriteSketchCount, + this.rewriteSketchReadyCount + rewriteSketchReadyCount, + this.rewriteSketchBlockedCount + rewriteSketchBlockedCount, + sketchBlocker, blocker ); } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilityFormatter.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilityFormatter.java index 143eccdd..e0d6c585 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilityFormatter.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilityFormatter.java @@ -207,6 +207,38 @@ public static String format( "optimizerReplacementPlan.validation.firstBlockers", summarizeKernelProperty(gate, kernelCount, "runtimeOptimizerDrift.replacementPlan.validation.firstBlocker") ); + int optimizerRewriteSketchCount = parsePositiveInt(gate.getProperty( + "optimizerRewriteSketch.count", + Integer.toString(sumKernelProperty(gate, kernelCount, "runtimeOptimizerDrift.rewriteSketch.count")) + )); + int optimizerRewriteSketchReadyCount = parsePositiveInt(gate.getProperty( + "optimizerRewriteSketch.ready.count", + Integer.toString(sumKernelProperty(gate, kernelCount, "runtimeOptimizerDrift.rewriteSketch.ready.count")) + )); + int optimizerRewriteSketchBlockedCount = parsePositiveInt(gate.getProperty( + "optimizerRewriteSketch.blocked.count", + Integer.toString(sumKernelProperty(gate, kernelCount, "runtimeOptimizerDrift.rewriteSketch.blocked.count")) + )); + String optimizerRewriteSketchFirstBlockers = gate.getProperty( + "optimizerRewriteSketch.firstBlockers", + summarizeKernelProperty(gate, kernelCount, "runtimeOptimizerDrift.rewriteSketch.firstBlocker") + ); + int optimizerRewriteSketchConflictCount = parsePositiveInt(gate.getProperty( + "optimizerRewriteSketch.conflict.count", + Integer.toString(sumKernelProperty(gate, kernelCount, "runtimeOptimizerDrift.rewriteSketch.conflict.count")) + )); + String optimizerRewriteSketchConflictFirstBlockers = gate.getProperty( + "optimizerRewriteSketch.conflict.firstBlockers", + summarizeKernelProperty(gate, kernelCount, "runtimeOptimizerDrift.rewriteSketch.conflict.firstBlocker") + ); + String optimizerRewriteSelectionStatuses = gate.getProperty( + "optimizerRewriteSelection.statuses", + summarizeRewriteSelectionStatuses(gate, kernelCount) + ); + String optimizerRewriteSelectionFirstBlockers = gate.getProperty( + "optimizerRewriteSelection.firstBlockers", + summarizeRewriteSelectionFirstBlockers(gate, kernelCount) + ); int optimizerRuleCount = parsePositiveInt(gate.getProperty( "optimizerRule.count", Integer.toString(sumKernelProperty(gate, kernelCount, "runtimeOptimizerDrift.optimizerRule.count")) @@ -483,6 +515,25 @@ public static String format( builder.append("optimizerReplacementPlan.validation.valid.count=").append(optimizerReplacementPlanValidationValidCount).append('\n'); builder.append("optimizerReplacementPlan.validation.invalid.count=").append(optimizerReplacementPlanValidationInvalidCount).append('\n'); builder.append("optimizerReplacementPlan.validation.firstBlockers=").append(optimizerReplacementPlanValidationFirstBlockers).append('\n'); + builder.append("optimizerRewriteSketch.count=").append(optimizerRewriteSketchCount).append('\n'); + builder.append("optimizerRewriteSketch.ready.count=").append(optimizerRewriteSketchReadyCount).append('\n'); + builder.append("optimizerRewriteSketch.blocked.count=").append(optimizerRewriteSketchBlockedCount).append('\n'); + builder.append("optimizerRewriteSketch.firstBlockers=").append(optimizerRewriteSketchFirstBlockers).append('\n'); + builder.append("optimizerRewriteSketch.conflict.count=").append(optimizerRewriteSketchConflictCount).append('\n'); + builder.append("optimizerRewriteSketch.conflict.firstBlockers=").append(optimizerRewriteSketchConflictFirstBlockers).append('\n'); + builder.append("optimizerRewriteSketch.conflict.conflictResolutionImplemented=false\n"); + builder.append("optimizerRewriteSketch.conflict.selectionApplied=false\n"); + builder.append("optimizerRewriteSketch.conflict.selectedIrReplacement=false\n"); + builder.append("optimizerRewriteSketch.rewriteBuilderImplemented=false\n"); + builder.append("optimizerRewriteSketch.mutationAllowed=false\n"); + builder.append("optimizerRewriteSketch.selectedIrReplacement=false\n"); + builder.append("optimizerRewriteSelection.statuses=").append(optimizerRewriteSelectionStatuses).append('\n'); + builder.append("optimizerRewriteSelection.firstBlockers=").append(optimizerRewriteSelectionFirstBlockers).append('\n'); + builder.append("optimizerRewriteSelection.rewriteBuilderImplemented=false\n"); + builder.append("optimizerRewriteSelection.conflictResolutionImplemented=false\n"); + builder.append("optimizerRewriteSelection.mutationAllowed=false\n"); + builder.append("optimizerRewriteSelection.selectionApplied=false\n"); + builder.append("optimizerRewriteSelection.selectedIrReplacement=false\n"); builder.append("optimizerRule.count=").append(optimizerRuleCount).append('\n'); builder.append("optimizerRule.summary=").append(optimizerRuleSummary).append('\n'); builder.append("optimizerRule.details=").append(optimizerRuleDetails).append('\n'); @@ -827,6 +878,57 @@ private static String summarizeKernelProperty(Properties properties, int kernelC return builder.toString(); } + private static String summarizeRewriteSelectionStatuses(Properties properties, int kernelCount) { + if (kernelCount <= 0) { + return "none"; + } + java.util.LinkedHashMap counts = new java.util.LinkedHashMap<>(); + for (int index = 0; index < kernelCount; index++) { + String value = properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteSelection.status", + "not-required" + ); + if (!value.isBlank() && !"unknown".equals(value) && !"not-required".equals(value)) { + counts.merge(value, 1, Integer::sum); + } + } + return summarizeCounts(counts); + } + + private static String summarizeRewriteSelectionFirstBlockers(Properties properties, int kernelCount) { + if (kernelCount <= 0) { + return "none"; + } + java.util.LinkedHashMap counts = new java.util.LinkedHashMap<>(); + for (int index = 0; index < kernelCount; index++) { + String value = properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteSelection.firstBlocker", + "no-rewrite-sketches" + ); + if (!value.isBlank() + && !"none".equals(value) + && !"unknown".equals(value) + && !"no-rewrite-sketches".equals(value)) { + counts.merge(value, 1, Integer::sum); + } + } + return summarizeCounts(counts); + } + + private static String summarizeCounts(java.util.LinkedHashMap counts) { + if (counts.isEmpty()) { + return "none"; + } + StringBuilder builder = new StringBuilder(); + for (java.util.Map.Entry entry : counts.entrySet()) { + if (!builder.isEmpty()) { + builder.append(", "); + } + builder.append(entry.getKey()).append(" x").append(entry.getValue()); + } + return builder.toString(); + } + private static String summarizeOptimizerRules(Properties properties, int kernelCount) { if (kernelCount <= 0) { return "none"; @@ -855,6 +957,10 @@ private static String summarizeOptimizerRules(Properties properties, int kernelC parsePositiveInt(properties.getProperty(prefix + "replacementPlan.validation.valid.count", "0")), parsePositiveInt(properties.getProperty(prefix + "replacementPlan.validation.invalid.count", "0")), properties.getProperty(prefix + "replacementPlan.validation.firstBlocker", "none"), + parsePositiveInt(properties.getProperty(prefix + "rewriteSketch.count", "0")), + parsePositiveInt(properties.getProperty(prefix + "rewriteSketch.ready.count", "0")), + parsePositiveInt(properties.getProperty(prefix + "rewriteSketch.blocked.count", "0")), + properties.getProperty(prefix + "rewriteSketch.firstBlocker", "none"), properties.getProperty(prefix + "firstBlocker", properties.getProperty(prefix + "replacementPlan.firstBlocker", "none")) )); } @@ -890,6 +996,14 @@ private static String summarizeOptimizerRules(Properties properties, int kernelC .append(rule.replacementPlanValidationInvalidCount()) .append(", planValidationFirstBlocker=") .append(rule.replacementPlanValidationFirstBlocker()) + .append(", rewriteSketches=") + .append(rule.rewriteSketchCount()) + .append(", readySketches=") + .append(rule.rewriteSketchReadyCount()) + .append(", blockedSketches=") + .append(rule.rewriteSketchBlockedCount()) + .append(", rewriteSketchFirstBlocker=") + .append(rule.rewriteSketchFirstBlocker()) .append(", firstBlocker=") .append(rule.firstBlocker()) .append(']'); @@ -1008,11 +1122,15 @@ private record OptimizerRuleAggregate( int replacementPlanValidationValidCount, int replacementPlanValidationInvalidCount, String replacementPlanValidationFirstBlocker, + int rewriteSketchCount, + int rewriteSketchReadyCount, + int rewriteSketchBlockedCount, + String rewriteSketchFirstBlocker, String firstBlocker ) { private static OptimizerRuleAggregate empty(String id) { - return new OptimizerRuleAggregate(id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "none", "none"); + return new OptimizerRuleAggregate(id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "none", 0, 0, 0, "none", "none"); } private OptimizerRuleAggregate add( @@ -1028,6 +1146,10 @@ private OptimizerRuleAggregate add( int replacementPlanValidationValidCount, int replacementPlanValidationInvalidCount, String nextValidationBlocker, + int rewriteSketchCount, + int rewriteSketchReadyCount, + int rewriteSketchBlockedCount, + String nextRewriteSketchBlocker, String nextBlocker ) { String blocker = firstBlocker; @@ -1038,6 +1160,13 @@ private OptimizerRuleAggregate add( && !"none".equals(nextValidationBlocker)) { validationBlocker = nextValidationBlocker; } + String sketchBlocker = rewriteSketchFirstBlocker; + if ("none".equals(sketchBlocker) + && nextRewriteSketchBlocker != null + && !nextRewriteSketchBlocker.isBlank() + && !"none".equals(nextRewriteSketchBlocker)) { + sketchBlocker = nextRewriteSketchBlocker; + } if ("none".equals(blocker) && nextBlocker != null && !nextBlocker.isBlank() @@ -1058,6 +1187,10 @@ private OptimizerRuleAggregate add( this.replacementPlanValidationValidCount + replacementPlanValidationValidCount, this.replacementPlanValidationInvalidCount + replacementPlanValidationInvalidCount, validationBlocker, + this.rewriteSketchCount + rewriteSketchCount, + this.rewriteSketchReadyCount + rewriteSketchReadyCount, + this.rewriteSketchBlockedCount + rewriteSketchBlockedCount, + sketchBlocker, blocker ); } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilitySummary.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilitySummary.java index d6537d8a..89d9406a 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilitySummary.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilitySummary.java @@ -34,6 +34,14 @@ public record GpuProductionPromotionExplainabilitySummary( int optimizerReplacementPlanValidationValidCount, int optimizerReplacementPlanValidationInvalidCount, String optimizerReplacementPlanValidationFirstBlockers, + int optimizerRewriteSketchCount, + int optimizerRewriteSketchReadyCount, + int optimizerRewriteSketchBlockedCount, + String optimizerRewriteSketchFirstBlockers, + int optimizerRewriteSketchConflictCount, + String optimizerRewriteSketchConflictFirstBlockers, + String optimizerRewriteSelectionStatuses, + String optimizerRewriteSelectionFirstBlockers, int optimizerRuleCount, String optimizerRuleSummary, String optimizerRuleDetails, @@ -107,6 +115,14 @@ public static GpuProductionPromotionExplainabilitySummary notRecorded() { 0, "", 0, + 0, + 0, + "", + 0, + "", + "none", + "none", + 0, "none", "none", 0, @@ -185,6 +201,14 @@ public static GpuProductionPromotionExplainabilitySummary fromProperties(Propert parsePositiveInt(properties.getProperty("optimizerReplacementPlan.validation.valid.count", "0")), parsePositiveInt(properties.getProperty("optimizerReplacementPlan.validation.invalid.count", "0")), properties.getProperty("optimizerReplacementPlan.validation.firstBlockers", ""), + parsePositiveInt(properties.getProperty("optimizerRewriteSketch.count", "0")), + parsePositiveInt(properties.getProperty("optimizerRewriteSketch.ready.count", "0")), + parsePositiveInt(properties.getProperty("optimizerRewriteSketch.blocked.count", "0")), + properties.getProperty("optimizerRewriteSketch.firstBlockers", ""), + parsePositiveInt(properties.getProperty("optimizerRewriteSketch.conflict.count", "0")), + properties.getProperty("optimizerRewriteSketch.conflict.firstBlockers", ""), + properties.getProperty("optimizerRewriteSelection.statuses", "none"), + properties.getProperty("optimizerRewriteSelection.firstBlockers", "none"), parsePositiveInt(properties.getProperty("optimizerRule.count", "0")), properties.getProperty("optimizerRule.summary", "none"), properties.getProperty("optimizerRule.details", "none"), @@ -310,6 +334,7 @@ public String historyStatus() { + optimizerFamilyPromotionPreflightReady + optimizerFamilySummaryText() + optimizerReplacementPlanSummaryText() + + optimizerRewriteSketchSummaryText() + optimizerRuleSummaryText() + ", backendPromotionArtifactSupportComplete=" + backendPromotionArtifactSupportComplete + ", backendPromotionArtifactSupportMissing=" + backendPromotionArtifactSupportMissingCount @@ -415,6 +440,54 @@ private String optimizerReplacementPlanSummaryText() { return builder.toString(); } + private String optimizerRewriteSketchSummaryText() { + if (optimizerRewriteSketchCount == 0 + && optimizerRewriteSketchReadyCount == 0 + && optimizerRewriteSketchBlockedCount == 0 + && optimizerRewriteSketchConflictCount == 0 + && (optimizerRewriteSketchFirstBlockers == null || optimizerRewriteSketchFirstBlockers.isBlank()) + && (optimizerRewriteSketchConflictFirstBlockers == null + || optimizerRewriteSketchConflictFirstBlockers.isBlank()) + && (optimizerRewriteSelectionStatuses == null + || optimizerRewriteSelectionStatuses.isBlank() + || "none".equals(optimizerRewriteSelectionStatuses)) + && (optimizerRewriteSelectionFirstBlockers == null + || optimizerRewriteSelectionFirstBlockers.isBlank() + || "none".equals(optimizerRewriteSelectionFirstBlockers))) { + return ""; + } + StringBuilder builder = new StringBuilder(", optimizerRewriteSketches=ready=") + .append(optimizerRewriteSketchReadyCount) + .append("/total=") + .append(optimizerRewriteSketchCount) + .append("/blocked=") + .append(optimizerRewriteSketchBlockedCount) + .append("/conflicts=") + .append(optimizerRewriteSketchConflictCount) + .append("/rewriteBuilderImplemented=false") + .append("/mutationAllowed=false") + .append("/selectedIrReplacement=false"); + if (optimizerRewriteSketchFirstBlockers != null && !optimizerRewriteSketchFirstBlockers.isBlank()) { + builder.append("/firstBlockers=").append(optimizerRewriteSketchFirstBlockers); + } + if (optimizerRewriteSketchConflictFirstBlockers != null + && !optimizerRewriteSketchConflictFirstBlockers.isBlank()) { + builder.append("/conflictFirstBlockers=").append(optimizerRewriteSketchConflictFirstBlockers); + } + if (optimizerRewriteSelectionStatuses != null + && !optimizerRewriteSelectionStatuses.isBlank() + && !"none".equals(optimizerRewriteSelectionStatuses)) { + builder.append("/selectionStatus=").append(optimizerRewriteSelectionStatuses); + } + if (optimizerRewriteSelectionFirstBlockers != null + && !optimizerRewriteSelectionFirstBlockers.isBlank() + && !"none".equals(optimizerRewriteSelectionFirstBlockers)) { + builder.append("/selectionFirstBlockers=").append(optimizerRewriteSelectionFirstBlockers); + } + builder.append("/selectionApplied=false"); + return builder.toString(); + } + private static int parsePositiveInt(String value) { try { return Math.max(0, Integer.parseInt(value == null ? "0" : value.trim())); diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilitySummaryCli.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilitySummaryCli.java index 81643272..9d5a90b9 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilitySummaryCli.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilitySummaryCli.java @@ -65,6 +65,59 @@ public static String format(GpuProductionPromotionExplainabilitySummary summary) .append(summary.optimizerFamilyPromotionReadyCount()) .append('\n'); builder.append("optimizerFamily.summary=").append(summary.optimizerFamilySummary()).append('\n'); + builder.append("optimizerReplacementPlan.complete.count=") + .append(summary.optimizerReplacementPlanCompleteCount()) + .append('\n'); + builder.append("optimizerReplacementPlan.partial.count=") + .append(summary.optimizerReplacementPlanPartialCount()) + .append('\n'); + builder.append("optimizerReplacementPlan.firstBlockers=") + .append(summary.optimizerReplacementPlanFirstBlockers()) + .append('\n'); + builder.append("optimizerReplacementPlan.validation.count=") + .append(summary.optimizerReplacementPlanValidationCount()) + .append('\n'); + builder.append("optimizerReplacementPlan.validation.valid.count=") + .append(summary.optimizerReplacementPlanValidationValidCount()) + .append('\n'); + builder.append("optimizerReplacementPlan.validation.invalid.count=") + .append(summary.optimizerReplacementPlanValidationInvalidCount()) + .append('\n'); + builder.append("optimizerReplacementPlan.validation.firstBlockers=") + .append(summary.optimizerReplacementPlanValidationFirstBlockers()) + .append('\n'); + builder.append("optimizerRewriteSketch.count=").append(summary.optimizerRewriteSketchCount()).append('\n'); + builder.append("optimizerRewriteSketch.ready.count=").append(summary.optimizerRewriteSketchReadyCount()).append('\n'); + builder.append("optimizerRewriteSketch.blocked.count=").append(summary.optimizerRewriteSketchBlockedCount()).append('\n'); + builder.append("optimizerRewriteSketch.firstBlockers=") + .append(summary.optimizerRewriteSketchFirstBlockers()) + .append('\n'); + builder.append("optimizerRewriteSketch.conflict.count=") + .append(summary.optimizerRewriteSketchConflictCount()) + .append('\n'); + builder.append("optimizerRewriteSketch.conflict.firstBlockers=") + .append(summary.optimizerRewriteSketchConflictFirstBlockers()) + .append('\n'); + builder.append("optimizerRewriteSketch.conflict.conflictResolutionImplemented=false\n"); + builder.append("optimizerRewriteSketch.conflict.selectionApplied=false\n"); + builder.append("optimizerRewriteSketch.conflict.selectedIrReplacement=false\n"); + builder.append("optimizerRewriteSketch.rewriteBuilderImplemented=false\n"); + builder.append("optimizerRewriteSketch.mutationAllowed=false\n"); + builder.append("optimizerRewriteSketch.selectedIrReplacement=false\n"); + builder.append("optimizerRewriteSelection.statuses=") + .append(summary.optimizerRewriteSelectionStatuses()) + .append('\n'); + builder.append("optimizerRewriteSelection.firstBlockers=") + .append(summary.optimizerRewriteSelectionFirstBlockers()) + .append('\n'); + builder.append("optimizerRewriteSelection.rewriteBuilderImplemented=false\n"); + builder.append("optimizerRewriteSelection.conflictResolutionImplemented=false\n"); + builder.append("optimizerRewriteSelection.mutationAllowed=false\n"); + builder.append("optimizerRewriteSelection.selectionApplied=false\n"); + builder.append("optimizerRewriteSelection.selectedIrReplacement=false\n"); + builder.append("optimizerRule.count=").append(summary.optimizerRuleCount()).append('\n'); + builder.append("optimizerRule.summary=").append(summary.optimizerRuleSummary()).append('\n'); + builder.append("optimizerRule.details=").append(summary.optimizerRuleDetails()).append('\n'); builder.append("optimizerFamilyPayload.complete.count=") .append(summary.optimizerFamilyPayloadCompleteCount()) .append('\n'); diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholePass.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholePass.java index ca899a3a..24253a56 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholePass.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholePass.java @@ -97,6 +97,13 @@ private Map diagnosticFields( Integer.toString(analysis.invalidReplacementPlanValidationCount()) ); fields.put("replacementPlan.validation.firstBlocker", analysis.firstReplacementPlanValidationBlocker()); + List rewriteSketches = rewriteSketches(analysis.ruleReports()); + List rewriteSketchConflicts = rewriteSketchConflicts(rewriteSketches); + appendRewriteSketchFields(fields, "rewriteSketch", rewriteSketches); + appendRewriteSketchConflictFields(fields, "rewriteSketch.conflict", rewriteSketchConflicts); + fields.putAll(GpuRuntimeIrPeepholeRewriteSelectionReadiness + .from(rewriteSketches, rewriteSketchConflicts) + .fields("rewriteSelection")); fields.put("rule.mix.candidate.count", Integer.toString(candidateCount(analysis, "mix"))); fields.put("rule.madFma.candidate.count", Integer.toString(candidateCount(analysis, "madFma"))); fields.put("rule.clamp.candidate.count", Integer.toString(candidateCount(analysis, "clamp"))); @@ -167,6 +174,7 @@ private void appendRuleFields( ? executionProofStatus(rule, analysis) : reports.get(0).proofStatus()); appendReplacementPlanFields(fields, prefix, reports); + appendRewriteSketchFields(fields, prefix + ".rewriteSketch", rewriteSketches(reports)); fields.put(prefix + ".firstBlocker", ruleFirstBlocker(fields, prefix, reports.isEmpty())); } } @@ -236,6 +244,103 @@ private static void appendReplacementPlanFields( } } + private static List rewriteSketches( + List reports + ) { + java.util.ArrayList sketches = new java.util.ArrayList<>(); + for (GpuRuntimeIrPeepholeRuleReport report : reports) { + List plans = report.replacementPlans(); + List validations = report.replacementPlanValidations(); + for (int index = 0; index < plans.size(); index++) { + GpuRuntimeIrPeepholeReplacementPlanValidation validation = index < validations.size() + ? validations.get(index) + : null; + sketches.add(GpuRuntimeIrPeepholeRewriteSketch.from(plans.get(index), validation)); + } + } + return List.copyOf(sketches); + } + + private static void appendRewriteSketchFields( + LinkedHashMap fields, + String prefix, + List sketches + ) { + fields.put(prefix + ".count", Integer.toString(sketches.size())); + fields.put(prefix + ".ready.count", Long.toString(sketches.stream() + .filter(GpuRuntimeIrPeepholeRewriteSketch::sketchReady) + .count())); + fields.put(prefix + ".blocked.count", Long.toString(sketches.stream() + .filter(sketch -> !sketch.sketchReady()) + .count())); + fields.put(prefix + ".firstBlocker", sketches.stream() + .filter(sketch -> !sketch.sketchReady()) + .map(GpuRuntimeIrPeepholeRewriteSketch::firstBlocker) + .findFirst() + .orElse("none")); + fields.put(prefix + ".rewriteBuilderImplemented", "false"); + fields.put(prefix + ".mutationAllowed", "false"); + fields.put(prefix + ".selectedIrReplacement", "false"); + fields.put(prefix + ".runtimeEquivalenceRequired", Boolean.toString(!sketches.isEmpty())); + fields.put(prefix + ".approvalRequired", Boolean.toString(!sketches.isEmpty())); + if (!sketches.isEmpty()) { + fields.putAll(sketches.get(0).fields(prefix + ".0")); + } + } + + private static List rewriteSketchConflicts( + List sketches + ) { + java.util.ArrayList conflicts = new java.util.ArrayList<>(); + for (int leftIndex = 0; leftIndex < sketches.size(); leftIndex++) { + GpuRuntimeIrPeepholeRewriteSketch left = sketches.get(leftIndex); + if (!left.sketchReady()) { + continue; + } + for (int rightIndex = leftIndex + 1; rightIndex < sketches.size(); rightIndex++) { + GpuRuntimeIrPeepholeRewriteSketch right = sketches.get(rightIndex); + if (!right.sketchReady() || !left.methodName().equals(right.methodName())) { + continue; + } + List overlapping = overlappingNodeIds(left.coveredNodeIds(), right.coveredNodeIds()); + if (!overlapping.isEmpty()) { + conflicts.add(GpuRuntimeIrPeepholeRewriteSketchConflict.between(left, right, overlapping)); + } + } + } + return List.copyOf(conflicts); + } + + private static List overlappingNodeIds(List first, List second) { + java.util.LinkedHashSet secondIds = new java.util.LinkedHashSet<>(second == null ? List.of() : second); + java.util.ArrayList overlap = new java.util.ArrayList<>(); + for (Integer id : first == null ? List.of() : first) { + if (id != null && secondIds.contains(id) && !overlap.contains(id)) { + overlap.add(id); + } + } + return List.copyOf(overlap); + } + + private static void appendRewriteSketchConflictFields( + LinkedHashMap fields, + String prefix, + List conflicts + ) { + fields.put(prefix + ".count", Integer.toString(conflicts.size())); + fields.put(prefix + ".firstBlocker", conflicts.stream() + .map(GpuRuntimeIrPeepholeRewriteSketchConflict::firstBlocker) + .findFirst() + .orElse("none")); + fields.put(prefix + ".conflictResolutionImplemented", "false"); + fields.put(prefix + ".selectionApplied", "false"); + fields.put(prefix + ".mutationAllowed", "false"); + fields.put(prefix + ".selectedIrReplacement", "false"); + if (!conflicts.isEmpty()) { + fields.putAll(conflicts.get(0).fields(prefix + ".0")); + } + } + private static String executionProofStatus( GpuRuntimeIrPeepholeRule rule, GpuRuntimeIrPeepholeRuleRegistry.Analysis analysis diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteSelectionReadiness.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteSelectionReadiness.java new file mode 100644 index 00000000..135b4f34 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteSelectionReadiness.java @@ -0,0 +1,126 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import java.util.List; +import java.util.Map; + +/** + * Read-only preflight that explains why future peephole rewrite sketches are not selectable yet. + */ +public record GpuRuntimeIrPeepholeRewriteSelectionReadiness( + int sketchCount, + int readySketchCount, + int blockedSketchCount, + int conflictCount, + String status, + String firstBlocker, + boolean rewriteBuilderImplemented, + boolean conflictResolutionImplemented, + boolean runtimeEquivalenceRequired, + boolean runtimeEquivalenceProven, + boolean approvalRequired, + boolean approvalAccepted, + boolean mutationAllowed, + boolean selectionApplied, + boolean selectedIrReplacement +) { + + public GpuRuntimeIrPeepholeRewriteSelectionReadiness { + sketchCount = Math.max(0, sketchCount); + readySketchCount = Math.max(0, readySketchCount); + blockedSketchCount = Math.max(0, blockedSketchCount); + conflictCount = Math.max(0, conflictCount); + status = normalize(status, sketchCount == 0 ? "not-required" : "blocked"); + firstBlocker = normalize(firstBlocker, defaultBlocker(sketchCount, readySketchCount, blockedSketchCount, conflictCount)); + } + + public static GpuRuntimeIrPeepholeRewriteSelectionReadiness from( + List sketches, + List conflicts + ) { + List normalizedSketches = sketches == null ? List.of() : sketches; + List normalizedConflicts = conflicts == null ? List.of() : conflicts; + int readyCount = (int) normalizedSketches.stream() + .filter(GpuRuntimeIrPeepholeRewriteSketch::sketchReady) + .count(); + int blockedCount = normalizedSketches.size() - readyCount; + String firstBlocker = firstBlocker(normalizedSketches, readyCount, blockedCount, normalizedConflicts.size()); + return new GpuRuntimeIrPeepholeRewriteSelectionReadiness( + normalizedSketches.size(), + readyCount, + blockedCount, + normalizedConflicts.size(), + normalizedSketches.isEmpty() ? "not-required" : "blocked", + firstBlocker, + false, + false, + readyCount > 0, + false, + readyCount > 0, + false, + false, + false, + false + ); + } + + public Map fields(String prefix) { + String normalizedPrefix = normalize(prefix, "rewriteSelection"); + return Map.ofEntries( + Map.entry(normalizedPrefix + ".sketch.count", Integer.toString(sketchCount)), + Map.entry(normalizedPrefix + ".sketch.ready.count", Integer.toString(readySketchCount)), + Map.entry(normalizedPrefix + ".sketch.blocked.count", Integer.toString(blockedSketchCount)), + Map.entry(normalizedPrefix + ".conflict.count", Integer.toString(conflictCount)), + Map.entry(normalizedPrefix + ".status", status), + Map.entry(normalizedPrefix + ".firstBlocker", firstBlocker), + Map.entry(normalizedPrefix + ".rewriteBuilderImplemented", Boolean.toString(rewriteBuilderImplemented)), + Map.entry(normalizedPrefix + ".conflictResolutionImplemented", Boolean.toString(conflictResolutionImplemented)), + Map.entry(normalizedPrefix + ".runtimeEquivalenceRequired", Boolean.toString(runtimeEquivalenceRequired)), + Map.entry(normalizedPrefix + ".runtimeEquivalenceProven", Boolean.toString(runtimeEquivalenceProven)), + Map.entry(normalizedPrefix + ".approvalRequired", Boolean.toString(approvalRequired)), + Map.entry(normalizedPrefix + ".approvalAccepted", Boolean.toString(approvalAccepted)), + Map.entry(normalizedPrefix + ".mutationAllowed", Boolean.toString(mutationAllowed)), + Map.entry(normalizedPrefix + ".selectionApplied", Boolean.toString(selectionApplied)), + Map.entry(normalizedPrefix + ".selectedIrReplacement", Boolean.toString(selectedIrReplacement)) + ); + } + + private static String firstBlocker( + List sketches, + int readyCount, + int blockedCount, + int conflictCount + ) { + if (sketches.isEmpty()) { + return "no-rewrite-sketches"; + } + if (blockedCount > 0) { + return sketches.stream() + .filter(sketch -> !sketch.sketchReady()) + .map(GpuRuntimeIrPeepholeRewriteSketch::firstBlocker) + .filter(blocker -> blocker != null && !blocker.isBlank() && !"none".equals(blocker)) + .findFirst() + .orElse("rewrite-sketch-blocked"); + } + if (conflictCount > 0) { + return "rewrite-sketch-conflict-resolution-required"; + } + return readyCount > 0 ? "rewrite-builder-not-implemented" : "no-rewrite-sketches"; + } + + private static String defaultBlocker(int sketchCount, int readySketchCount, int blockedSketchCount, int conflictCount) { + if (sketchCount == 0) { + return "no-rewrite-sketches"; + } + if (blockedSketchCount > 0) { + return "rewrite-sketch-blocked"; + } + if (conflictCount > 0) { + return "rewrite-sketch-conflict-resolution-required"; + } + return readySketchCount > 0 ? "rewrite-builder-not-implemented" : "no-rewrite-sketches"; + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteSketch.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteSketch.java new file mode 100644 index 00000000..cb5fee86 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteSketch.java @@ -0,0 +1,193 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Read-only skeleton for a future structural typed-IR peephole rewrite operation. + * + *

A sketch can prove that a replacement plan has enough graph information to describe a future rewrite, but it does + * not build replacement nodes, mutate typed bodies, select optimized IR, or authorize production mutation.

+ */ +public record GpuRuntimeIrPeepholeRewriteSketch( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + List coveredNodeIds, + List inputNodeIds, + boolean planComplete, + boolean planValid, + boolean sketchReady, + String status, + String firstBlocker, + boolean rewriteBuilderImplemented, + boolean mutationAllowed, + boolean selectedIrReplacement, + boolean runtimeEquivalenceRequired, + boolean approvalRequired +) { + + public GpuRuntimeIrPeepholeRewriteSketch { + ruleId = normalize(ruleId, "rule:unknown"); + methodName = normalize(methodName, "unknown"); + rootNodeId = Math.max(0, rootNodeId); + replacementKind = normalize(replacementKind, "unknown"); + coveredNodeIds = normalizeNodeIds(coveredNodeIds); + inputNodeIds = normalizeNodeIds(inputNodeIds); + status = normalize(status, sketchReady ? "sketch-ready" : "blocked"); + firstBlocker = normalize(firstBlocker, sketchReady ? "none" : "rewrite-sketch-blocked"); + } + + public static GpuRuntimeIrPeepholeRewriteSketch from( + GpuRuntimeIrPeepholeReplacementPlan plan, + GpuRuntimeIrPeepholeReplacementPlanValidation validation + ) { + if (plan == null) { + return blocked( + "rule:unknown", + "unknown", + 0, + "unknown", + List.of(), + List.of(), + false, + false, + "replacement-plan-missing" + ); + } + boolean planValid = validation != null && validation.valid(); + if (!plan.complete()) { + return blocked( + plan.ruleId(), + plan.methodName(), + plan.rootNodeId(), + plan.replacementKind(), + plan.coveredNodeIds(), + plan.inputNodeIds(), + false, + planValid, + plan.firstBlocker() + ); + } + if (validation == null) { + return blocked( + plan.ruleId(), + plan.methodName(), + plan.rootNodeId(), + plan.replacementKind(), + plan.coveredNodeIds(), + plan.inputNodeIds(), + true, + false, + "replacement-plan-validation-missing" + ); + } + if (!validation.valid()) { + return blocked( + plan.ruleId(), + plan.methodName(), + plan.rootNodeId(), + plan.replacementKind(), + plan.coveredNodeIds(), + plan.inputNodeIds(), + true, + false, + validation.firstBlocker() + ); + } + return new GpuRuntimeIrPeepholeRewriteSketch( + plan.ruleId(), + plan.methodName(), + plan.rootNodeId(), + plan.replacementKind(), + plan.coveredNodeIds(), + plan.inputNodeIds(), + true, + true, + true, + "sketch-ready", + "none", + false, + false, + false, + true, + true + ); + } + + public Map fields(String prefix) { + String normalizedPrefix = normalize(prefix, "rewriteSketch"); + return Map.ofEntries( + Map.entry(normalizedPrefix + ".ruleId", ruleId), + Map.entry(normalizedPrefix + ".methodName", methodName), + Map.entry(normalizedPrefix + ".rootNodeId", Integer.toString(rootNodeId)), + Map.entry(normalizedPrefix + ".replacementKind", replacementKind), + Map.entry(normalizedPrefix + ".coveredNodeIds", join(coveredNodeIds)), + Map.entry(normalizedPrefix + ".inputNodeIds", join(inputNodeIds)), + Map.entry(normalizedPrefix + ".planComplete", Boolean.toString(planComplete)), + Map.entry(normalizedPrefix + ".planValid", Boolean.toString(planValid)), + Map.entry(normalizedPrefix + ".sketchReady", Boolean.toString(sketchReady)), + Map.entry(normalizedPrefix + ".status", status), + Map.entry(normalizedPrefix + ".firstBlocker", firstBlocker), + Map.entry(normalizedPrefix + ".rewriteBuilderImplemented", Boolean.toString(rewriteBuilderImplemented)), + Map.entry(normalizedPrefix + ".mutationAllowed", Boolean.toString(mutationAllowed)), + Map.entry(normalizedPrefix + ".selectedIrReplacement", Boolean.toString(selectedIrReplacement)), + Map.entry(normalizedPrefix + ".runtimeEquivalenceRequired", Boolean.toString(runtimeEquivalenceRequired)), + Map.entry(normalizedPrefix + ".approvalRequired", Boolean.toString(approvalRequired)) + ); + } + + private static GpuRuntimeIrPeepholeRewriteSketch blocked( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + List coveredNodeIds, + List inputNodeIds, + boolean planComplete, + boolean planValid, + String firstBlocker + ) { + return new GpuRuntimeIrPeepholeRewriteSketch( + ruleId, + methodName, + rootNodeId, + replacementKind, + coveredNodeIds, + inputNodeIds, + planComplete, + planValid, + false, + "blocked", + firstBlocker, + false, + false, + false, + true, + true + ); + } + + private static List normalizeNodeIds(List ids) { + if (ids == null || ids.isEmpty()) { + return List.of(); + } + return ids.stream() + .filter(id -> id != null && id >= 0) + .distinct() + .toList(); + } + + private static String join(List ids) { + if (ids == null || ids.isEmpty()) { + return "none"; + } + return ids.stream().map(String::valueOf).collect(Collectors.joining(",")); + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteSketchConflict.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteSketchConflict.java new file mode 100644 index 00000000..2a27809d --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteSketchConflict.java @@ -0,0 +1,77 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Read-only overlap evidence between two future peephole rewrite sketches. + */ +public record GpuRuntimeIrPeepholeRewriteSketchConflict( + String methodName, + String firstRuleId, + int firstRootNodeId, + String firstReplacementKind, + String secondRuleId, + int secondRootNodeId, + String secondReplacementKind, + List overlappingNodeIds, + String firstBlocker +) { + + public GpuRuntimeIrPeepholeRewriteSketchConflict { + methodName = normalize(methodName, "unknown"); + firstRuleId = normalize(firstRuleId, "rule:unknown"); + firstRootNodeId = Math.max(0, firstRootNodeId); + firstReplacementKind = normalize(firstReplacementKind, "unknown"); + secondRuleId = normalize(secondRuleId, "rule:unknown"); + secondRootNodeId = Math.max(0, secondRootNodeId); + secondReplacementKind = normalize(secondReplacementKind, "unknown"); + overlappingNodeIds = overlappingNodeIds == null ? List.of() : List.copyOf(overlappingNodeIds); + firstBlocker = normalize(firstBlocker, "rewrite-sketch-covered-node-overlap"); + } + + public static GpuRuntimeIrPeepholeRewriteSketchConflict between( + GpuRuntimeIrPeepholeRewriteSketch first, + GpuRuntimeIrPeepholeRewriteSketch second, + List overlappingNodeIds + ) { + return new GpuRuntimeIrPeepholeRewriteSketchConflict( + first.methodName(), + first.ruleId(), + first.rootNodeId(), + first.replacementKind(), + second.ruleId(), + second.rootNodeId(), + second.replacementKind(), + overlappingNodeIds, + "rewrite-sketch-covered-node-overlap" + ); + } + + public Map fields(String prefix) { + String normalizedPrefix = normalize(prefix, "rewriteSketch.conflict"); + return Map.of( + normalizedPrefix + ".methodName", methodName, + normalizedPrefix + ".firstRuleId", firstRuleId, + normalizedPrefix + ".firstRootNodeId", Integer.toString(firstRootNodeId), + normalizedPrefix + ".firstReplacementKind", firstReplacementKind, + normalizedPrefix + ".secondRuleId", secondRuleId, + normalizedPrefix + ".secondRootNodeId", Integer.toString(secondRootNodeId), + normalizedPrefix + ".secondReplacementKind", secondReplacementKind, + normalizedPrefix + ".overlappingNodeIds", join(overlappingNodeIds), + normalizedPrefix + ".firstBlocker", firstBlocker + ); + } + + private static String join(List ids) { + if (ids == null || ids.isEmpty()) { + return "none"; + } + return ids.stream().map(String::valueOf).collect(Collectors.joining(",")); + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeOptimizerDriftArtifact.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeOptimizerDriftArtifact.java index 8bb6cb90..0cea4afe 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeOptimizerDriftArtifact.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeOptimizerDriftArtifact.java @@ -30,6 +30,14 @@ public record GpuRuntimeOptimizerDriftArtifact( int replacementPlanValidationValidCount, int replacementPlanValidationInvalidCount, String replacementPlanValidationFirstBlocker, + int rewriteSketchCount, + int rewriteSketchReadyCount, + int rewriteSketchBlockedCount, + String rewriteSketchFirstBlocker, + int rewriteSketchConflictCount, + String rewriteSketchConflictFirstBlocker, + String rewriteSelectionStatus, + String rewriteSelectionFirstBlocker, int optimizerRuleCount, String optimizerRuleSummary, String optimizerRuleDetailsProperties, @@ -67,6 +75,14 @@ public static GpuRuntimeOptimizerDriftArtifact from(GpuRuntimeCompileArtifactSna 0, "none", 0, + 0, + 0, + "none", + 0, + "none", + "not-required", + "no-rewrite-sketches", + 0, "none", "", 0, @@ -84,6 +100,12 @@ public static GpuRuntimeOptimizerDriftArtifact from(GpuRuntimeCompileArtifactSna GpuRuntimeIrSelection selection = snapshot.runtimeIrSelection(); Map optimizerFamilies = optimizerFamilies(report); Map optimizerRules = optimizerRules(report); + int rewriteSketchCount = rewriteSketchCount(report); + int rewriteSketchReadyCount = rewriteSketchReadyCount(report); + int rewriteSketchBlockedCount = rewriteSketchBlockedCount(report); + String rewriteSketchFirstBlocker = rewriteSketchFirstBlocker(report); + int rewriteSketchConflictCount = rewriteSketchConflictCount(report); + String rewriteSketchConflictFirstBlocker = rewriteSketchConflictFirstBlocker(report); return new GpuRuntimeOptimizerDriftArtifact( optimizationPassCount(report), count(report, GpuRuntimeIrOptimizationOutcome.APPLIED), @@ -108,6 +130,21 @@ public static GpuRuntimeOptimizerDriftArtifact from(GpuRuntimeCompileArtifactSna replacementPlanValidationValidCount(report), replacementPlanValidationInvalidCount(report), replacementPlanValidationFirstBlocker(report), + rewriteSketchCount, + rewriteSketchReadyCount, + rewriteSketchBlockedCount, + rewriteSketchFirstBlocker, + rewriteSketchConflictCount, + rewriteSketchConflictFirstBlocker, + rewriteSelectionStatus(report, rewriteSketchCount), + rewriteSelectionFirstBlocker( + report, + rewriteSketchCount, + rewriteSketchReadyCount, + rewriteSketchBlockedCount, + rewriteSketchFirstBlocker, + rewriteSketchConflictCount + ), optimizerRules.size(), formatOptimizerRuleSummary(optimizerRules), formatOptimizerRuleProperties(optimizerRules), @@ -150,6 +187,34 @@ public String toPropertiesText() { builder.append("replacementPlan.validation.valid.count=").append(replacementPlanValidationValidCount).append('\n'); builder.append("replacementPlan.validation.invalid.count=").append(replacementPlanValidationInvalidCount).append('\n'); builder.append("replacementPlan.validation.firstBlocker=").append(replacementPlanValidationFirstBlocker).append('\n'); + builder.append("rewriteSketch.count=").append(rewriteSketchCount).append('\n'); + builder.append("rewriteSketch.ready.count=").append(rewriteSketchReadyCount).append('\n'); + builder.append("rewriteSketch.blocked.count=").append(rewriteSketchBlockedCount).append('\n'); + builder.append("rewriteSketch.firstBlocker=").append(rewriteSketchFirstBlocker).append('\n'); + builder.append("rewriteSketch.rewriteBuilderImplemented=false\n"); + builder.append("rewriteSketch.mutationAllowed=false\n"); + builder.append("rewriteSketch.selectedIrReplacement=false\n"); + builder.append("rewriteSketch.conflict.count=").append(rewriteSketchConflictCount).append('\n'); + builder.append("rewriteSketch.conflict.firstBlocker=").append(rewriteSketchConflictFirstBlocker).append('\n'); + builder.append("rewriteSketch.conflict.conflictResolutionImplemented=false\n"); + builder.append("rewriteSketch.conflict.selectionApplied=false\n"); + builder.append("rewriteSketch.conflict.mutationAllowed=false\n"); + builder.append("rewriteSketch.conflict.selectedIrReplacement=false\n"); + builder.append("rewriteSelection.sketch.count=").append(rewriteSketchCount).append('\n'); + builder.append("rewriteSelection.sketch.ready.count=").append(rewriteSketchReadyCount).append('\n'); + builder.append("rewriteSelection.sketch.blocked.count=").append(rewriteSketchBlockedCount).append('\n'); + builder.append("rewriteSelection.conflict.count=").append(rewriteSketchConflictCount).append('\n'); + builder.append("rewriteSelection.status=").append(rewriteSelectionStatus).append('\n'); + builder.append("rewriteSelection.firstBlocker=").append(rewriteSelectionFirstBlocker).append('\n'); + builder.append("rewriteSelection.rewriteBuilderImplemented=false\n"); + builder.append("rewriteSelection.conflictResolutionImplemented=false\n"); + builder.append("rewriteSelection.runtimeEquivalenceRequired=").append(rewriteSketchReadyCount > 0).append('\n'); + builder.append("rewriteSelection.runtimeEquivalenceProven=false\n"); + builder.append("rewriteSelection.approvalRequired=").append(rewriteSketchReadyCount > 0).append('\n'); + builder.append("rewriteSelection.approvalAccepted=false\n"); + builder.append("rewriteSelection.mutationAllowed=false\n"); + builder.append("rewriteSelection.selectionApplied=false\n"); + builder.append("rewriteSelection.selectedIrReplacement=false\n"); builder.append("optimizerRule.count=").append(optimizerRuleCount).append('\n'); builder.append("optimizerRule.summary=").append(optimizerRuleSummary).append('\n'); if (optimizerRuleDetailsProperties != null && !optimizerRuleDetailsProperties.isBlank()) { @@ -276,6 +341,152 @@ private static String replacementPlanValidationFirstBlocker(GpuRuntimeIrOptimiza return "none"; } + private static int rewriteSketchCount(GpuRuntimeIrOptimizationReport report) { + return rewriteSketchRuleCount(report, ".rewriteSketch.count"); + } + + private static int rewriteSketchReadyCount(GpuRuntimeIrOptimizationReport report) { + return rewriteSketchRuleCount(report, ".rewriteSketch.ready.count"); + } + + private static int rewriteSketchBlockedCount(GpuRuntimeIrOptimizationReport report) { + int total = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + Map fields = passReport.proofArtifact().fields(); + int passTotal = parseInt(fields.get("rewriteSketch.blocked.count")); + total += passTotal > 0 ? passTotal : rewriteSketchRuleCount(fields, ".rewriteSketch.blocked.count"); + } + } + return total; + } + + private static int rewriteSketchRuleCount(GpuRuntimeIrOptimizationReport report, String suffix) { + int total = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + total += rewriteSketchRuleCount(passReport.proofArtifact().fields(), suffix); + } + } + return total; + } + + private static int rewriteSketchRuleCount(Map fields, String suffix) { + return fields.entrySet().stream() + .filter(entry -> entry.getKey().startsWith("rule.")) + .filter(entry -> entry.getKey().endsWith(suffix)) + .mapToInt(entry -> parseInt(entry.getValue())) + .sum(); + } + + private static String rewriteSketchFirstBlocker(GpuRuntimeIrOptimizationReport report) { + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + Map fields = passReport.proofArtifact().fields(); + String blocker = fields.getOrDefault("rewriteSketch.firstBlocker", "none"); + if (!blocker.isBlank() && !"none".equals(blocker)) { + return blocker; + } + String ruleBlocker = firstRuleBlocker(fields, ".rewriteSketch.firstBlocker"); + if (!"none".equals(ruleBlocker)) { + return ruleBlocker; + } + } + } + return "none"; + } + + private static int rewriteSketchConflictCount(GpuRuntimeIrOptimizationReport report) { + int total = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + total += parseInt(passReport.proofArtifact().fields().get("rewriteSketch.conflict.count")); + } + } + return total; + } + + private static String rewriteSketchConflictFirstBlocker(GpuRuntimeIrOptimizationReport report) { + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + String blocker = passReport.proofArtifact().fields().getOrDefault( + "rewriteSketch.conflict.firstBlocker", + "none" + ); + if (!blocker.isBlank() && !"none".equals(blocker)) { + return blocker; + } + } + } + return "none"; + } + + private static String rewriteSelectionStatus(GpuRuntimeIrOptimizationReport report, int rewriteSketchCount) { + String fallback = rewriteSketchCount == 0 ? "not-required" : "blocked"; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + String status = passReport.proofArtifact().fields().getOrDefault("rewriteSelection.status", ""); + if ("blocked".equals(status)) { + return status; + } + if (!status.isBlank()) { + fallback = status; + } + } + } + return fallback; + } + + private static String rewriteSelectionFirstBlocker( + GpuRuntimeIrOptimizationReport report, + int rewriteSketchCount, + int rewriteSketchReadyCount, + int rewriteSketchBlockedCount, + String rewriteSketchFirstBlocker, + int rewriteSketchConflictCount + ) { + String fallback = derivedRewriteSelectionFirstBlocker( + rewriteSketchCount, + rewriteSketchReadyCount, + rewriteSketchBlockedCount, + rewriteSketchFirstBlocker, + rewriteSketchConflictCount + ); + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + String blocker = passReport.proofArtifact().fields().getOrDefault("rewriteSelection.firstBlocker", ""); + if (!blocker.isBlank() && !"none".equals(blocker) && !"no-rewrite-sketches".equals(blocker)) { + return blocker; + } + if (!blocker.isBlank()) { + fallback = blocker; + } + } + } + return fallback; + } + + private static String derivedRewriteSelectionFirstBlocker( + int rewriteSketchCount, + int rewriteSketchReadyCount, + int rewriteSketchBlockedCount, + String rewriteSketchFirstBlocker, + int rewriteSketchConflictCount + ) { + if (rewriteSketchCount == 0) { + return "no-rewrite-sketches"; + } + if (rewriteSketchBlockedCount > 0) { + return rewriteSketchFirstBlocker == null || rewriteSketchFirstBlocker.isBlank() || "none".equals(rewriteSketchFirstBlocker) + ? "rewrite-sketch-blocked" + : rewriteSketchFirstBlocker; + } + if (rewriteSketchConflictCount > 0) { + return "rewrite-sketch-conflict-resolution-required"; + } + return rewriteSketchReadyCount > 0 ? "rewrite-builder-not-implemented" : "no-rewrite-sketches"; + } + private static String firstRuleBlocker(Map fields, String suffix) { for (Map.Entry entry : fields.entrySet()) { String blocker = entry.getValue(); @@ -345,6 +556,14 @@ private static String formatOptimizerRuleSummary(Map fields, String prefix) { && !"none".equals(nextValidationBlocker)) { validationBlocker = nextValidationBlocker; } + String sketchBlocker = rewriteSketchFirstBlocker; + String nextSketchBlocker = fields.getOrDefault(prefix + ".rewriteSketch.firstBlocker", "none"); + if ("none".equals(sketchBlocker) + && !nextSketchBlocker.isBlank() + && !"none".equals(nextSketchBlocker)) { + sketchBlocker = nextSketchBlocker; + } String nextBlocker = fields.getOrDefault( prefix + ".firstBlocker", fields.getOrDefault(prefix + ".replacementPlan.firstBlocker", "none") @@ -589,6 +830,10 @@ private OptimizerRuleEvidence add(Map fields, String prefix) { replacementPlanValidationValidCount + parseInt(fields.get(prefix + ".replacementPlan.validation.valid.count")), replacementPlanValidationInvalidCount + parseInt(fields.get(prefix + ".replacementPlan.validation.invalid.count")), validationBlocker, + rewriteSketchCount + parseInt(fields.get(prefix + ".rewriteSketch.count")), + rewriteSketchReadyCount + parseInt(fields.get(prefix + ".rewriteSketch.ready.count")), + rewriteSketchBlockedCount + parseInt(fields.get(prefix + ".rewriteSketch.blocked.count")), + sketchBlocker, blocker ); } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReporter.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReporter.java index bdb2da17..532380cb 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReporter.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReporter.java @@ -1087,6 +1087,22 @@ private static void appendBackendSourcePromotionWorkloadKernelSummary( .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.replacementPlan.validation.invalid.count", "0"))) .append("`, replacementPlanValidationFirstBlocker=`") .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.replacementPlan.validation.firstBlocker", "none"))) + .append("`, rewriteSketch=`") + .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.rewriteSketch.ready.count", "0"))) + .append("/") + .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.rewriteSketch.count", "0"))) + .append(" ready, blocked=") + .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.rewriteSketch.blocked.count", "0"))) + .append("`, rewriteSketchFirstBlocker=`") + .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.rewriteSketch.firstBlocker", "none"))) + .append("`, rewriteSketchConflicts=`") + .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.rewriteSketch.conflict.count", "0"))) + .append("`, rewriteSketchConflictFirstBlocker=`") + .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.rewriteSketch.conflict.firstBlocker", "none"))) + .append("`, rewriteSelection=`") + .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.rewriteSelection.status", "not-required"))) + .append("`, rewriteSelectionFirstBlocker=`") + .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.rewriteSelection.firstBlocker", "no-rewrite-sketches"))) .append("`, optimizerRules=`") .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.optimizerRule.count", "0"))) .append("`, optimizerRuleDetails=`") @@ -1457,6 +1473,19 @@ private static String formatI3ReadinessWorkloadSummary(java.util.Properties gate builder.append(prefix).append("optimizerDriftReplacementPlanValidationValidCount=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.replacementPlan.validation.valid.count", "0")).append('\n'); builder.append(prefix).append("optimizerDriftReplacementPlanValidationInvalidCount=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.replacementPlan.validation.invalid.count", "0")).append('\n'); builder.append(prefix).append("optimizerDriftReplacementPlanValidationFirstBlocker=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.replacementPlan.validation.firstBlocker", "none")).append('\n'); + builder.append(prefix).append("optimizerDriftRewriteSketchCount=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.rewriteSketch.count", "0")).append('\n'); + builder.append(prefix).append("optimizerDriftRewriteSketchReadyCount=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.rewriteSketch.ready.count", "0")).append('\n'); + builder.append(prefix).append("optimizerDriftRewriteSketchBlockedCount=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.rewriteSketch.blocked.count", "0")).append('\n'); + builder.append(prefix).append("optimizerDriftRewriteSketchFirstBlocker=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.rewriteSketch.firstBlocker", "none")).append('\n'); + builder.append(prefix).append("optimizerDriftRewriteSketchConflictCount=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.rewriteSketch.conflict.count", "0")).append('\n'); + builder.append(prefix).append("optimizerDriftRewriteSketchConflictFirstBlocker=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.rewriteSketch.conflict.firstBlocker", "none")).append('\n'); + builder.append(prefix).append("optimizerDriftRewriteSketchConflictResolutionImplemented=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.rewriteSketch.conflict.conflictResolutionImplemented", "false")).append('\n'); + builder.append(prefix).append("optimizerDriftRewriteSketchSelectionApplied=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.rewriteSketch.conflict.selectionApplied", "false")).append('\n'); + builder.append(prefix).append("optimizerDriftRewriteSelectionStatus=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.rewriteSelection.status", "not-required")).append('\n'); + builder.append(prefix).append("optimizerDriftRewriteSelectionFirstBlocker=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.rewriteSelection.firstBlocker", "no-rewrite-sketches")).append('\n'); + builder.append(prefix).append("optimizerDriftRewriteSelectionApplied=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.rewriteSelection.selectionApplied", "false")).append('\n'); + builder.append(prefix).append("optimizerDriftRewriteBuilderImplemented=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.rewriteSketch.rewriteBuilderImplemented", "false")).append('\n'); + builder.append(prefix).append("optimizerDriftSelectedIrReplacement=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.rewriteSketch.selectedIrReplacement", "false")).append('\n'); builder.append(prefix).append("optimizerDriftRuleCount=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.optimizerRule.count", "0")).append('\n'); builder.append(prefix).append("optimizerDriftRuleSummary=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.optimizerRule.summary", "none")).append('\n'); builder.append(prefix).append("optimizerDriftRuleDetails=").append(kernelOptimizerRuleDetails(gate, prefix)).append('\n'); @@ -1518,6 +1547,14 @@ private static String kernelOptimizerRuleDetails(Properties properties, String k .append(properties.getProperty(prefix + "replacementPlan.validation.invalid.count", "0")) .append(", planValidationFirstBlocker=") .append(properties.getProperty(prefix + "replacementPlan.validation.firstBlocker", "none")) + .append(", rewriteSketches=") + .append(properties.getProperty(prefix + "rewriteSketch.count", "0")) + .append(", readySketches=") + .append(properties.getProperty(prefix + "rewriteSketch.ready.count", "0")) + .append(", blockedSketches=") + .append(properties.getProperty(prefix + "rewriteSketch.blocked.count", "0")) + .append(", rewriteSketchFirstBlocker=") + .append(properties.getProperty(prefix + "rewriteSketch.firstBlocker", "none")) .append(", firstBlocker=") .append(properties.getProperty( prefix + "firstBlocker", diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatterTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatterTest.java index a7b27279..e7ebe744 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatterTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatterTest.java @@ -409,12 +409,26 @@ void mergesSourceSwitchingDecisionEvidencePerKernel() throws IOException { assertEquals("4", gate.getProperty("kernel.0.runtimeOptimizerDrift.replacementPlan.validation.valid.count")); assertEquals("0", gate.getProperty("kernel.0.runtimeOptimizerDrift.replacementPlan.validation.invalid.count")); assertEquals("none", gate.getProperty("kernel.0.runtimeOptimizerDrift.replacementPlan.validation.firstBlocker")); + assertEquals("4", gate.getProperty("kernel.0.runtimeOptimizerDrift.rewriteSketch.count")); + assertEquals("4", gate.getProperty("kernel.0.runtimeOptimizerDrift.rewriteSketch.ready.count")); + assertEquals("0", gate.getProperty("kernel.0.runtimeOptimizerDrift.rewriteSketch.blocked.count")); + assertEquals("none", gate.getProperty("kernel.0.runtimeOptimizerDrift.rewriteSketch.firstBlocker")); + assertEquals("false", gate.getProperty("kernel.0.runtimeOptimizerDrift.rewriteSketch.rewriteBuilderImplemented")); + assertEquals("false", gate.getProperty("kernel.0.runtimeOptimizerDrift.rewriteSketch.mutationAllowed")); + assertEquals("false", gate.getProperty("kernel.0.runtimeOptimizerDrift.rewriteSketch.selectedIrReplacement")); + assertEquals("1", gate.getProperty("kernel.0.runtimeOptimizerDrift.rewriteSketch.conflict.count")); + assertEquals("rewrite-sketch-covered-node-overlap", gate.getProperty("kernel.0.runtimeOptimizerDrift.rewriteSketch.conflict.firstBlocker")); + assertEquals("false", gate.getProperty("kernel.0.runtimeOptimizerDrift.rewriteSketch.conflict.conflictResolutionImplemented")); + assertEquals("false", gate.getProperty("kernel.0.runtimeOptimizerDrift.rewriteSketch.conflict.selectionApplied")); assertEquals("1", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizerRule.count")); assertEquals("madFma", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizerRule.0.id")); assertEquals("3", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizerRule.0.candidate.count")); assertEquals("1", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizerRule.0.blocked.count")); assertEquals("4", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizerRule.0.replacementPlan.validation.count")); assertEquals("0", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizerRule.0.replacementPlan.validation.invalid.count")); + assertEquals("4", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizerRule.0.rewriteSketch.count")); + assertEquals("4", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizerRule.0.rewriteSketch.ready.count")); + assertEquals("0", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizerRule.0.rewriteSketch.blocked.count")); assertEquals("multiply-operands-incomplete", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizerRule.0.firstBlocker")); assertEquals("2", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizerFamily.count")); assertEquals("1", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizerFamily.promotionReady.count")); @@ -441,6 +455,12 @@ void mergesSourceSwitchingDecisionEvidencePerKernel() throws IOException { assertEquals("0", gate.getProperty("kernel.1.runtimeOptimizerDrift.replacementPlan.validation.valid.count")); assertEquals("0", gate.getProperty("kernel.1.runtimeOptimizerDrift.replacementPlan.validation.invalid.count")); assertEquals("none", gate.getProperty("kernel.1.runtimeOptimizerDrift.replacementPlan.validation.firstBlocker")); + assertEquals("0", gate.getProperty("kernel.1.runtimeOptimizerDrift.rewriteSketch.count")); + assertEquals("0", gate.getProperty("kernel.1.runtimeOptimizerDrift.rewriteSketch.ready.count")); + assertEquals("0", gate.getProperty("kernel.1.runtimeOptimizerDrift.rewriteSketch.blocked.count")); + assertEquals("none", gate.getProperty("kernel.1.runtimeOptimizerDrift.rewriteSketch.firstBlocker")); + assertEquals("0", gate.getProperty("kernel.1.runtimeOptimizerDrift.rewriteSketch.conflict.count")); + assertEquals("none", gate.getProperty("kernel.1.runtimeOptimizerDrift.rewriteSketch.conflict.firstBlocker")); assertEquals("0", gate.getProperty("kernel.1.runtimeOptimizerDrift.optimizerRule.count")); assertEquals(null, gate.getProperty("kernel.1.runtimeOptimizerDrift.optimizerRule.0.id")); assertEquals("1", gate.getProperty("kernel.1.runtimeOptimizerDrift.optimizerFamily.count")); @@ -848,9 +868,22 @@ private static String runtimeOptimizerDriftProperties( "replacementPlan.validation.valid.count=" + ("optimized".equals(selectedRuntimeIrStage) ? "4" : "0"), "replacementPlan.validation.invalid.count=0", "replacementPlan.validation.firstBlocker=none", + "rewriteSketch.count=" + ("optimized".equals(selectedRuntimeIrStage) ? "4" : "0"), + "rewriteSketch.ready.count=" + ("optimized".equals(selectedRuntimeIrStage) ? "4" : "0"), + "rewriteSketch.blocked.count=0", + "rewriteSketch.firstBlocker=none", + "rewriteSketch.rewriteBuilderImplemented=false", + "rewriteSketch.mutationAllowed=false", + "rewriteSketch.selectedIrReplacement=false", + "rewriteSketch.conflict.count=" + ("optimized".equals(selectedRuntimeIrStage) ? "1" : "0"), + "rewriteSketch.conflict.firstBlocker=" + ("optimized".equals(selectedRuntimeIrStage) ? "rewrite-sketch-covered-node-overlap" : "none"), + "rewriteSketch.conflict.conflictResolutionImplemented=false", + "rewriteSketch.conflict.selectionApplied=false", + "rewriteSketch.conflict.mutationAllowed=false", + "rewriteSketch.conflict.selectedIrReplacement=false", "optimizerRule.count=" + ("optimized".equals(selectedRuntimeIrStage) ? "1" : "0"), "optimizerRule.summary=" + ("optimized".equals(selectedRuntimeIrStage) - ? "madFma[candidates=3, proposals=0, applied=0, skipped=0, blocked=1, mutationProposed=false, replacementPlans=4, completePlans=3, partialPlans=1, planValidations=4, invalidPlanValidations=0, planValidationFirstBlocker=none, firstBlocker=multiply-operands-incomplete]" + ? "madFma[candidates=3, proposals=0, applied=0, skipped=0, blocked=1, mutationProposed=false, replacementPlans=4, completePlans=3, partialPlans=1, planValidations=4, invalidPlanValidations=0, planValidationFirstBlocker=none, rewriteSketches=4, readySketches=4, blockedSketches=0, rewriteSketchFirstBlocker=none, firstBlocker=multiply-operands-incomplete]" : "none"), "optimizerRule.0.id=" + ("optimized".equals(selectedRuntimeIrStage) ? "madFma" : ""), "optimizerRule.0.version=peephole-rule:mad-fma-v1", @@ -871,6 +904,13 @@ private static String runtimeOptimizerDriftProperties( "optimizerRule.0.replacementPlan.validation.valid.count=" + ("optimized".equals(selectedRuntimeIrStage) ? "4" : "0"), "optimizerRule.0.replacementPlan.validation.invalid.count=0", "optimizerRule.0.replacementPlan.validation.firstBlocker=none", + "optimizerRule.0.rewriteSketch.count=" + ("optimized".equals(selectedRuntimeIrStage) ? "4" : "0"), + "optimizerRule.0.rewriteSketch.ready.count=" + ("optimized".equals(selectedRuntimeIrStage) ? "4" : "0"), + "optimizerRule.0.rewriteSketch.blocked.count=0", + "optimizerRule.0.rewriteSketch.firstBlocker=none", + "optimizerRule.0.rewriteSketch.rewriteBuilderImplemented=false", + "optimizerRule.0.rewriteSketch.mutationAllowed=false", + "optimizerRule.0.rewriteSketch.selectedIrReplacement=false", "optimizerRule.0.firstBlocker=" + ("optimized".equals(selectedRuntimeIrStage) ? "multiply-operands-incomplete" : "none"), "optimizerFamily.count=" + ("optimized".equals(selectedRuntimeIrStage) ? "2" : "1"), "optimizerFamily.promotionReady.count=" + ("optimized".equals(selectedRuntimeIrStage) ? "1" : "0"), diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummaryTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummaryTest.java index 88e8d913..a97aba7d 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummaryTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummaryTest.java @@ -43,6 +43,16 @@ void summarizesIndexedSourceSwitchingEvidenceForHistoryAndReports() { properties.setProperty("kernel.0.runtimeOptimizerDrift.replacementPlan.validation.valid.count", "3"); properties.setProperty("kernel.0.runtimeOptimizerDrift.replacementPlan.validation.invalid.count", "1"); properties.setProperty("kernel.0.runtimeOptimizerDrift.replacementPlan.validation.firstBlocker", "replacement-plan-root-missing"); + properties.setProperty("kernel.0.runtimeOptimizerDrift.rewriteSketch.count", "4"); + properties.setProperty("kernel.0.runtimeOptimizerDrift.rewriteSketch.ready.count", "3"); + properties.setProperty("kernel.0.runtimeOptimizerDrift.rewriteSketch.blocked.count", "1"); + properties.setProperty("kernel.0.runtimeOptimizerDrift.rewriteSketch.firstBlocker", "replacement-plan-root-missing"); + properties.setProperty("kernel.0.runtimeOptimizerDrift.rewriteSketch.conflict.count", "1"); + properties.setProperty("kernel.0.runtimeOptimizerDrift.rewriteSketch.conflict.firstBlocker", "rewrite-sketch-covered-node-overlap"); + properties.setProperty("kernel.0.runtimeOptimizerDrift.rewriteSketch.conflict.conflictResolutionImplemented", "false"); + properties.setProperty("kernel.0.runtimeOptimizerDrift.rewriteSketch.conflict.selectionApplied", "false"); + properties.setProperty("kernel.0.runtimeOptimizerDrift.rewriteSketch.rewriteBuilderImplemented", "false"); + properties.setProperty("kernel.0.runtimeOptimizerDrift.rewriteSketch.selectedIrReplacement", "false"); properties.setProperty("kernel.0.runtimeOptimizerDrift.optimizerFamily.count", "2"); properties.setProperty("kernel.0.runtimeOptimizerDrift.optimizerFamily.promotionReady.count", "1"); properties.setProperty( @@ -98,6 +108,12 @@ void summarizesIndexedSourceSwitchingEvidenceForHistoryAndReports() { assertEquals(3, summary.optimizerReplacementPlanValidationValidCount()); assertEquals(1, summary.optimizerReplacementPlanValidationInvalidCount()); assertEquals("replacement-plan-root-missing=1", summary.optimizerReplacementPlanValidationFirstBlockers()); + assertEquals(4, summary.optimizerRewriteSketchCount()); + assertEquals(3, summary.optimizerRewriteSketchReadyCount()); + assertEquals(1, summary.optimizerRewriteSketchBlockedCount()); + assertEquals("replacement-plan-root-missing=1", summary.optimizerRewriteSketchFirstBlockers()); + assertEquals(1, summary.optimizerRewriteSketchConflictCount()); + assertEquals("rewrite-sketch-covered-node-overlap=1", summary.optimizerRewriteSketchConflictFirstBlockers()); assertEquals(2, summary.optimizerFamilyCount()); assertEquals(1, summary.optimizerFamilyPromotionReadyCount()); assertEquals( @@ -122,11 +138,12 @@ void summarizesIndexedSourceSwitchingEvidenceForHistoryAndReports() { assertTrue(summary.historyStatus().contains("optimizerFamilies=2")); assertTrue(summary.historyStatus().contains("optimizerPromotionReadyFamilies=1")); assertTrue(summary.historyStatus().contains("optimizerReplacementPlans=complete=3/partial=1/validation=valid=3/total=4/invalid=1/validationFirstBlockers=replacement-plan-root-missing=1/firstBlockers=multiply-operands-incomplete=1")); + assertTrue(summary.historyStatus().contains("optimizerRewriteSketches=ready=3/total=4/blocked=1/conflicts=1/rewriteBuilderImplemented=false/mutationAllowed=false/selectedIrReplacement=false/firstBlockers=replacement-plan-root-missing=1/conflictFirstBlockers=rewrite-sketch-covered-node-overlap=1/selectionApplied=false")); assertTrue(summary.historyStatus().contains("optimizerFamilySummary=cse[passes=1")); assertTrue(summary.historyStatus().contains("kernelCount=1")); assertTrue(summary.historyStatus().contains("extensionParticipation=recorded/3executions/failedContinued=1/failedClosed=0")); assertTrue(summary.historyStatus().contains("sourceSwitching=reject-production-irgpu-source/operatorAccepted=true")); - assertTrue(summary.historyStatus().contains("proof=2/acceptedProof=1/blockingProof=1/replacementPlanComplete=3/replacementPlanPartial=1/replacementPlanFirstBlocker=multiply-operands-incomplete/replacementPlanValidationValid=3/replacementPlanValidationTotal=4/replacementPlanValidationInvalid=1/replacementPlanValidationFirstBlocker=replacement-plan-root-missing/optimizerRules=0/optimizerRuleDetails=none/optimizerFamilies=2/promotionReadyFamilies=1")); + assertTrue(summary.historyStatus().contains("proof=2/acceptedProof=1/blockingProof=1/replacementPlanComplete=3/replacementPlanPartial=1/replacementPlanFirstBlocker=multiply-operands-incomplete/replacementPlanValidationValid=3/replacementPlanValidationTotal=4/replacementPlanValidationInvalid=1/replacementPlanValidationFirstBlocker=replacement-plan-root-missing/rewriteSketchReady=3/rewriteSketchTotal=4/rewriteSketchBlocked=1/rewriteSketchFirstBlocker=replacement-plan-root-missing/rewriteSketchConflicts=1/rewriteSketchConflictFirstBlocker=rewrite-sketch-covered-node-overlap/rewriteSketchConflictResolutionImplemented=false/rewriteSketchSelectionApplied=false/rewriteSelectionStatus=not-required/rewriteSelectionFirstBlocker=no-rewrite-sketches/rewriteSelectionApplied=false/rewriteBuilderImplemented=false/selectedIrReplacement=false/optimizerRules=0/optimizerRuleDetails=none/optimizerFamilies=2/promotionReadyFamilies=1")); assertTrue(summary.historyStatus().contains("families=source-parity=1")); } diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilityFormatterTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilityFormatterTest.java index 5758968a..6dda03f4 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilityFormatterTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilityFormatterTest.java @@ -20,6 +20,15 @@ void emitsValidBlockedArtifactWithContractFields() { assertTrue(formatted.contains("optimizerFamily.count=2")); assertTrue(formatted.contains("optimizerFamily.promotionReady.count=1")); assertTrue(formatted.contains("optimizerFamily.summary=cse[passes=1, acceptedProof=1")); + assertTrue(formatted.contains("optimizerRewriteSketch.count=0")); + assertTrue(formatted.contains("optimizerRewriteSketch.ready.count=0")); + assertTrue(formatted.contains("optimizerRewriteSketch.blocked.count=0")); + assertTrue(formatted.contains("optimizerRewriteSketch.conflict.count=0")); + assertTrue(formatted.contains("optimizerRewriteSketch.conflict.conflictResolutionImplemented=false")); + assertTrue(formatted.contains("optimizerRewriteSketch.conflict.selectionApplied=false")); + assertTrue(formatted.contains("optimizerRewriteSketch.rewriteBuilderImplemented=false")); + assertTrue(formatted.contains("optimizerRewriteSketch.mutationAllowed=false")); + assertTrue(formatted.contains("optimizerRewriteSketch.selectedIrReplacement=false")); assertTrue(formatted.contains("optimizerFamily.runtimeEquivalenceHistoryBaselineReady=false")); assertTrue(formatted.contains("optimizerFamily.promotionPreflightReady=false")); assertTrue(formatted.contains("optimizer-family-runtime-equivalence-history-baseline-missing")); diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilitySummaryTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilitySummaryTest.java index 58aa6716..7f44f330 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilitySummaryTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilitySummaryTest.java @@ -31,6 +31,12 @@ void summarizesBlockedExplainabilityForReportsAndCi() { "optimizerFamily.summary", "cse[passes=1, acceptedProof=1, blockingProof=0, rolledBack=0, failed=0, promotionReady=true], vector[passes=1, acceptedProof=0, blockingProof=1, rolledBack=0, failed=0, promotionReady=false]" ); + properties.setProperty("optimizerRewriteSketch.count", "4"); + properties.setProperty("optimizerRewriteSketch.ready.count", "3"); + properties.setProperty("optimizerRewriteSketch.blocked.count", "1"); + properties.setProperty("optimizerRewriteSketch.firstBlockers", "replacement-plan-root-missing x1"); + properties.setProperty("optimizerRewriteSketch.conflict.count", "1"); + properties.setProperty("optimizerRewriteSketch.conflict.firstBlockers", "rewrite-sketch-covered-node-overlap x1"); properties.setProperty("blocker.count", "2"); properties.setProperty("blocker.0", "workload-source-promotion-gate-not-review-ready"); properties.setProperty("blocker.1", "production-mutation-disabled"); @@ -48,8 +54,13 @@ void summarizesBlockedExplainabilityForReportsAndCi() { assertTrue(summary.historyStatus().contains("i3SourceReadyAll=false")); assertTrue(summary.historyStatus().contains("optimizerFamilies=2")); assertTrue(summary.historyStatus().contains("optimizerPromotionReadyFamilies=1")); + assertEquals(4, summary.optimizerRewriteSketchCount()); + assertEquals(3, summary.optimizerRewriteSketchReadyCount()); + assertEquals(1, summary.optimizerRewriteSketchBlockedCount()); + assertEquals(1, summary.optimizerRewriteSketchConflictCount()); assertTrue(summary.historyStatus().contains("optimizerRuntimeEquivalenceHistoryBaselineReady=false")); assertTrue(summary.historyStatus().contains("optimizerPromotionPreflightReady=true")); + assertTrue(summary.historyStatus().contains("optimizerRewriteSketches=ready=3/total=4/blocked=1/conflicts=1/rewriteBuilderImplemented=false/mutationAllowed=false/selectedIrReplacement=false/firstBlockers=replacement-plan-root-missing x1/conflictFirstBlockers=rewrite-sketch-covered-node-overlap x1")); assertTrue(summary.historyStatus().contains("optimizerFamilySummary=cse[passes=1")); assertTrue(summary.historyStatus().contains("backendPromotionArtifactSupportComplete=unknown")); assertTrue(summary.historyStatus().contains("controlledSourceSwitching=not-recorded")); @@ -103,6 +114,12 @@ void cliFormatKeepsCiFriendlyScalarFields() { "optimizerFamily.summary", "cse[passes=1, acceptedProof=1, blockingProof=0, rolledBack=0, failed=0, promotionReady=true]" ); + properties.setProperty("optimizerRewriteSketch.count", "2"); + properties.setProperty("optimizerRewriteSketch.ready.count", "2"); + properties.setProperty("optimizerRewriteSketch.blocked.count", "0"); + properties.setProperty("optimizerRewriteSketch.firstBlockers", ""); + properties.setProperty("optimizerRewriteSketch.conflict.count", "0"); + properties.setProperty("optimizerRewriteSketch.conflict.firstBlockers", ""); properties.setProperty("blocker.count", "1"); properties.setProperty("blocker.0", "production-mutation-disabled"); properties.setProperty("controlledProductionSourceSwitching.status", "passed"); @@ -153,6 +170,14 @@ void cliFormatKeepsCiFriendlyScalarFields() { assertTrue(formatted.contains("optimizerFamily.count=2\n")); assertTrue(formatted.contains("optimizerFamily.promotionReady.count=1\n")); assertTrue(formatted.contains("optimizerFamily.summary=cse[passes=1")); + assertTrue(formatted.contains("optimizerRewriteSketch.count=2\n")); + assertTrue(formatted.contains("optimizerRewriteSketch.ready.count=2\n")); + assertTrue(formatted.contains("optimizerRewriteSketch.blocked.count=0\n")); + assertTrue(formatted.contains("optimizerRewriteSketch.conflict.count=0\n")); + assertTrue(formatted.contains("optimizerRewriteSketch.conflict.conflictResolutionImplemented=false\n")); + assertTrue(formatted.contains("optimizerRewriteSketch.conflict.selectionApplied=false\n")); + assertTrue(formatted.contains("optimizerRewriteSketch.rewriteBuilderImplemented=false\n")); + assertTrue(formatted.contains("optimizerRewriteSketch.selectedIrReplacement=false\n")); assertTrue(formatted.contains("optimizerFamily.runtimeEquivalenceHistoryBaselineReady=false\n")); assertTrue(formatted.contains("optimizerFamily.promotionPreflightReady=true\n")); assertTrue(formatted.contains("backendPromotionArtifactSupport.complete=unknown\n")); diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java index a17479c9..bd56982d 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java @@ -2017,6 +2017,14 @@ void optimizerDriftArtifactCapturesReadOnlyReplacementPlanTelemetry() { Map.entry("replacementPlan.firstBlocker", "multiply-operands-incomplete"), Map.entry("replacementPlan.validation.invalid.count", "1"), Map.entry("replacementPlan.validation.firstBlocker", "replacement-plan-root-missing"), + Map.entry("rewriteSketch.count", "6"), + Map.entry("rewriteSketch.ready.count", "5"), + Map.entry("rewriteSketch.blocked.count", "1"), + Map.entry("rewriteSketch.firstBlocker", "replacement-plan-root-missing"), + Map.entry("rewriteSketch.conflict.count", "1"), + Map.entry("rewriteSketch.conflict.firstBlocker", "rewrite-sketch-covered-node-overlap"), + Map.entry("rewriteSelection.status", "blocked"), + Map.entry("rewriteSelection.firstBlocker", "rewrite-sketch-conflict-resolution-required"), Map.entry("rule.count", "2"), Map.entry("rule.0.id", "madFma"), Map.entry("rule.0.version", "peephole-rule:mad-fma-v1"), @@ -2037,6 +2045,10 @@ void optimizerDriftArtifactCapturesReadOnlyReplacementPlanTelemetry() { Map.entry("rule.0.replacementPlan.validation.valid.count", "2"), Map.entry("rule.0.replacementPlan.validation.invalid.count", "1"), Map.entry("rule.0.replacementPlan.validation.firstBlocker", "replacement-plan-root-missing"), + Map.entry("rule.0.rewriteSketch.count", "3"), + Map.entry("rule.0.rewriteSketch.ready.count", "2"), + Map.entry("rule.0.rewriteSketch.blocked.count", "1"), + Map.entry("rule.0.rewriteSketch.firstBlocker", "replacement-plan-root-missing"), Map.entry("rule.0.firstBlocker", "multiply-operands-incomplete"), Map.entry("rule.1.id", "clamp"), Map.entry("rule.1.version", "peephole-rule:clamp-v1"), @@ -2057,6 +2069,10 @@ void optimizerDriftArtifactCapturesReadOnlyReplacementPlanTelemetry() { Map.entry("rule.1.replacementPlan.validation.valid.count", "3"), Map.entry("rule.1.replacementPlan.validation.invalid.count", "0"), Map.entry("rule.1.replacementPlan.validation.firstBlocker", "none"), + Map.entry("rule.1.rewriteSketch.count", "3"), + Map.entry("rule.1.rewriteSketch.ready.count", "3"), + Map.entry("rule.1.rewriteSketch.blocked.count", "0"), + Map.entry("rule.1.rewriteSketch.firstBlocker", "none"), Map.entry("rule.1.firstBlocker", "none") ) )); @@ -2087,17 +2103,48 @@ void optimizerDriftArtifactCapturesReadOnlyReplacementPlanTelemetry() { assertTrue(drift.contains("replacementPlan.validation.valid.count=5")); assertTrue(drift.contains("replacementPlan.validation.invalid.count=1")); assertTrue(drift.contains("replacementPlan.validation.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("rewriteSketch.count=6")); + assertTrue(drift.contains("rewriteSketch.ready.count=5")); + assertTrue(drift.contains("rewriteSketch.blocked.count=1")); + assertTrue(drift.contains("rewriteSketch.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("rewriteSketch.rewriteBuilderImplemented=false")); + assertTrue(drift.contains("rewriteSketch.mutationAllowed=false")); + assertTrue(drift.contains("rewriteSketch.selectedIrReplacement=false")); + assertTrue(drift.contains("rewriteSketch.conflict.count=1")); + assertTrue(drift.contains("rewriteSketch.conflict.firstBlocker=rewrite-sketch-covered-node-overlap")); + assertTrue(drift.contains("rewriteSketch.conflict.conflictResolutionImplemented=false")); + assertTrue(drift.contains("rewriteSketch.conflict.selectionApplied=false")); + assertTrue(drift.contains("rewriteSelection.sketch.count=6")); + assertTrue(drift.contains("rewriteSelection.sketch.ready.count=5")); + assertTrue(drift.contains("rewriteSelection.sketch.blocked.count=1")); + assertTrue(drift.contains("rewriteSelection.conflict.count=1")); + assertTrue(drift.contains("rewriteSelection.status=blocked")); + assertTrue(drift.contains("rewriteSelection.firstBlocker=rewrite-sketch-conflict-resolution-required")); + assertTrue(drift.contains("rewriteSelection.rewriteBuilderImplemented=false")); + assertTrue(drift.contains("rewriteSelection.conflictResolutionImplemented=false")); + assertTrue(drift.contains("rewriteSelection.runtimeEquivalenceRequired=true")); + assertTrue(drift.contains("rewriteSelection.runtimeEquivalenceProven=false")); + assertTrue(drift.contains("rewriteSelection.approvalRequired=true")); + assertTrue(drift.contains("rewriteSelection.approvalAccepted=false")); + assertTrue(drift.contains("rewriteSelection.mutationAllowed=false")); + assertTrue(drift.contains("rewriteSelection.selectionApplied=false")); + assertTrue(drift.contains("rewriteSelection.selectedIrReplacement=false")); assertTrue(drift.contains("optimizerRule.count=2")); assertTrue(drift.contains("optimizerRule.0.id=madFma")); assertTrue(drift.contains("optimizerRule.0.blocked.count=1")); assertTrue(drift.contains("optimizerRule.0.replacementPlan.validation.count=3")); assertTrue(drift.contains("optimizerRule.0.replacementPlan.validation.invalid.count=1")); assertTrue(drift.contains("optimizerRule.0.replacementPlan.validation.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("optimizerRule.0.rewriteSketch.count=3")); + assertTrue(drift.contains("optimizerRule.0.rewriteSketch.ready.count=2")); + assertTrue(drift.contains("optimizerRule.0.rewriteSketch.blocked.count=1")); + assertTrue(drift.contains("optimizerRule.0.rewriteSketch.firstBlocker=replacement-plan-root-missing")); assertTrue(drift.contains("optimizerRule.0.firstBlocker=multiply-operands-incomplete")); assertTrue(drift.contains("optimizerRule.1.id=clamp")); assertTrue(drift.contains("optimizerRule.1.skipped.count=1")); assertTrue(drift.contains("optimizerRule.summary=madFma[candidates=2, proposals=0, applied=0, skipped=0, blocked=1")); assertTrue(drift.contains("planValidations=3, invalidPlanValidations=1, planValidationFirstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("rewriteSketches=3, readySketches=2, blockedSketches=1, rewriteSketchFirstBlocker=replacement-plan-root-missing")); assertTrue(drift.contains("optimizerFamily.count=1")); assertTrue(drift.contains("optimizerFamily.summary=peephole[passes=1, acceptedProof=0, blockingProof=1, rolledBack=0, failed=0, promotionReady=false]")); assertTrue(drift.contains("selectedRuntimeIrStage=original")); diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistryTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistryTest.java index 7c40727a..f1d02032 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistryTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistryTest.java @@ -579,6 +579,41 @@ void diagnosticPeepholePassFindsTypedMadFmaCandidateWithoutMutatingIr() { assertEquals("true", passReport.proofArtifact().fields().get("rule.0.replacementPlan.validation.0.coveredIncludesRoot")); assertEquals("none", passReport.proofArtifact().fields().get("rule.0.replacementPlan.validation.0.missingCoveredNodeIds")); assertEquals("none", passReport.proofArtifact().fields().get("rule.0.replacementPlan.validation.0.missingInputNodeIds")); + assertEquals("1", passReport.proofArtifact().fields().get("rewriteSketch.count")); + assertEquals("1", passReport.proofArtifact().fields().get("rewriteSketch.ready.count")); + assertEquals("0", passReport.proofArtifact().fields().get("rewriteSketch.blocked.count")); + assertEquals("none", passReport.proofArtifact().fields().get("rewriteSketch.firstBlocker")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteSketch.rewriteBuilderImplemented")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteSketch.mutationAllowed")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteSketch.selectedIrReplacement")); + assertEquals("true", passReport.proofArtifact().fields().get("rewriteSketch.runtimeEquivalenceRequired")); + assertEquals("true", passReport.proofArtifact().fields().get("rewriteSketch.approvalRequired")); + assertEquals("0", passReport.proofArtifact().fields().get("rewriteSketch.conflict.count")); + assertEquals("none", passReport.proofArtifact().fields().get("rewriteSketch.conflict.firstBlocker")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteSketch.conflict.conflictResolutionImplemented")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteSketch.conflict.selectionApplied")); + assertEquals("blocked", passReport.proofArtifact().fields().get("rewriteSelection.status")); + assertEquals("rewrite-builder-not-implemented", passReport.proofArtifact().fields().get("rewriteSelection.firstBlocker")); + assertEquals("1", passReport.proofArtifact().fields().get("rewriteSelection.sketch.count")); + assertEquals("1", passReport.proofArtifact().fields().get("rewriteSelection.sketch.ready.count")); + assertEquals("0", passReport.proofArtifact().fields().get("rewriteSelection.sketch.blocked.count")); + assertEquals("0", passReport.proofArtifact().fields().get("rewriteSelection.conflict.count")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteSelection.rewriteBuilderImplemented")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteSelection.conflictResolutionImplemented")); + assertEquals("true", passReport.proofArtifact().fields().get("rewriteSelection.runtimeEquivalenceRequired")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteSelection.runtimeEquivalenceProven")); + assertEquals("true", passReport.proofArtifact().fields().get("rewriteSelection.approvalRequired")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteSelection.approvalAccepted")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteSelection.mutationAllowed")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteSelection.selectionApplied")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteSelection.selectedIrReplacement")); + assertEquals("sketch-ready", passReport.proofArtifact().fields().get("rule.0.rewriteSketch.0.status")); + assertEquals("true", passReport.proofArtifact().fields().get("rule.0.rewriteSketch.0.planComplete")); + assertEquals("true", passReport.proofArtifact().fields().get("rule.0.rewriteSketch.0.planValid")); + assertEquals("true", passReport.proofArtifact().fields().get("rule.0.rewriteSketch.0.sketchReady")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.rewriteSketch.0.rewriteBuilderImplemented")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.rewriteSketch.0.mutationAllowed")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.rewriteSketch.0.selectedIrReplacement")); assertEquals("clamp", passReport.proofArtifact().fields().get("rule.1.id")); assertEquals("dot", passReport.proofArtifact().fields().get("rule.2.id")); assertEquals("mix", passReport.proofArtifact().fields().get("rule.3.id")); @@ -726,6 +761,31 @@ void diagnosticPeepholePassFindsTypedDotCandidateWithoutMutatingIr() { assertEquals("1,2,5", fields.get("rule.2.replacementPlan.0.coveredNodeIds")); assertEquals("3,4,6,7", fields.get("rule.2.replacementPlan.0.inputNodeIds")); assertEquals("true", fields.get("rule.2.replacementPlan.0.complete")); + assertEquals("2", fields.get("rewriteSketch.count")); + assertEquals("2", fields.get("rewriteSketch.ready.count")); + assertEquals("1", fields.get("rewriteSketch.conflict.count")); + assertEquals("rewrite-sketch-covered-node-overlap", fields.get("rewriteSketch.conflict.firstBlocker")); + assertEquals("false", fields.get("rewriteSketch.conflict.conflictResolutionImplemented")); + assertEquals("false", fields.get("rewriteSketch.conflict.selectionApplied")); + assertEquals("false", fields.get("rewriteSketch.conflict.mutationAllowed")); + assertEquals("false", fields.get("rewriteSketch.conflict.selectedIrReplacement")); + assertEquals("kernel", fields.get("rewriteSketch.conflict.0.methodName")); + assertEquals("madFma", fields.get("rewriteSketch.conflict.0.firstRuleId")); + assertEquals("1", fields.get("rewriteSketch.conflict.0.firstRootNodeId")); + assertEquals("mad-fma", fields.get("rewriteSketch.conflict.0.firstReplacementKind")); + assertEquals("dot", fields.get("rewriteSketch.conflict.0.secondRuleId")); + assertEquals("1", fields.get("rewriteSketch.conflict.0.secondRootNodeId")); + assertEquals("dot", fields.get("rewriteSketch.conflict.0.secondReplacementKind")); + assertEquals("1,2", fields.get("rewriteSketch.conflict.0.overlappingNodeIds")); + assertEquals("blocked", fields.get("rewriteSelection.status")); + assertEquals("rewrite-sketch-conflict-resolution-required", fields.get("rewriteSelection.firstBlocker")); + assertEquals("2", fields.get("rewriteSelection.sketch.count")); + assertEquals("2", fields.get("rewriteSelection.sketch.ready.count")); + assertEquals("0", fields.get("rewriteSelection.sketch.blocked.count")); + assertEquals("1", fields.get("rewriteSelection.conflict.count")); + assertEquals("false", fields.get("rewriteSelection.conflictResolutionImplemented")); + assertEquals("false", fields.get("rewriteSelection.selectionApplied")); + assertEquals("false", fields.get("rewriteSelection.selectedIrReplacement")); assertEquals("rewrite-engine-not-implemented", fields.get("firstBlocker")); assertTrue(passReport.toLine().contains("structural rewrite and proof emission are not implemented")); } @@ -855,6 +915,26 @@ public String extensionId() { assertEquals("1", fields.get("candidate.count")); assertEquals("1", fields.get("replacementPlan.validation.invalid.count")); assertEquals("replacement-plan-root-missing", fields.get("replacementPlan.validation.firstBlocker")); + assertEquals("1", fields.get("rewriteSketch.count")); + assertEquals("0", fields.get("rewriteSketch.ready.count")); + assertEquals("1", fields.get("rewriteSketch.blocked.count")); + assertEquals("replacement-plan-root-missing", fields.get("rewriteSketch.firstBlocker")); + assertEquals("blocked", fields.get("rule.0.rewriteSketch.0.status")); + assertEquals("true", fields.get("rule.0.rewriteSketch.0.planComplete")); + assertEquals("false", fields.get("rule.0.rewriteSketch.0.planValid")); + assertEquals("false", fields.get("rule.0.rewriteSketch.0.sketchReady")); + assertEquals("replacement-plan-root-missing", fields.get("rule.0.rewriteSketch.0.firstBlocker")); + assertEquals("false", fields.get("rule.0.rewriteSketch.0.mutationAllowed")); + assertEquals("false", fields.get("rule.0.rewriteSketch.0.selectedIrReplacement")); + assertEquals("blocked", fields.get("rewriteSelection.status")); + assertEquals("replacement-plan-root-missing", fields.get("rewriteSelection.firstBlocker")); + assertEquals("1", fields.get("rewriteSelection.sketch.count")); + assertEquals("0", fields.get("rewriteSelection.sketch.ready.count")); + assertEquals("1", fields.get("rewriteSelection.sketch.blocked.count")); + assertEquals("0", fields.get("rewriteSelection.conflict.count")); + assertEquals("false", fields.get("rewriteSelection.runtimeEquivalenceRequired")); + assertEquals("false", fields.get("rewriteSelection.approvalRequired")); + assertEquals("false", fields.get("rewriteSelection.selectedIrReplacement")); assertEquals("replacement-plan-root-missing", fields.get("firstBlocker")); assertEquals("replacement-plan-root-missing", fields.get("rule.0.firstBlocker")); assertEquals("1", fields.get("rule.0.replacementPlan.validation.count")); diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java index c125b645..3904682d 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java @@ -955,7 +955,7 @@ void validationReportAndHistoryExposeRuntimeSnapshotWorkloadGateEvidence() throw assertTrue(reportMarkdown.contains("- Kernel `0` runtime IR handoff: stage=`optimized`, transformed=`true`, rollback=`false`, rejected=`false`, fallback=`none`, first=`optimized IrGpu is selected for backend lowering after runtime optimizer passes`")); assertTrue(reportMarkdown.contains("- Kernel `0` production mutation safety: enabled=`false`, gate=`not-requested`, profileRequested=`false`, first=`runtime IR participates in diagnostics, but production mutation is disabled because no production profile was requested`")); assertTrue(reportMarkdown.contains("- Kernel `0` I3 readiness: status=`review-ready`, sourcePromotion=`review-ready`, sourceReady=`true`, optimizerGate=`not-requested`, productionMutation=`false`, first=`I3 source pipeline is review-ready, but production mutation remains disabled until production gates are accepted`")); - assertTrue(reportMarkdown.contains("- Kernel `0` runtime optimizer drift: passes=`2`, applied=`2`, rolledBack=`0`, failed=`0`, proof=`0`, acceptedProof=`0`, blockingProof=`0`, replacementPlanComplete=`0`, replacementPlanPartial=`0`, replacementPlanFirstBlocker=`none`, replacementPlanValidation=`0/0 valid, invalid=0`, replacementPlanValidationFirstBlocker=`none`, optimizerRules=`0`, optimizerRuleDetails=`none`, optimizerFamilies=`0`, promotionReadyFamilies=`0`, selected=`optimized`, fallback=`none`, gate=`not-requested`")); + assertTrue(reportMarkdown.contains("- Kernel `0` runtime optimizer drift: passes=`2`, applied=`2`, rolledBack=`0`, failed=`0`, proof=`0`, acceptedProof=`0`, blockingProof=`0`, replacementPlanComplete=`0`, replacementPlanPartial=`0`, replacementPlanFirstBlocker=`none`, replacementPlanValidation=`0/0 valid, invalid=0`, replacementPlanValidationFirstBlocker=`none`, rewriteSketch=`0/0 ready, blocked=0`, rewriteSketchFirstBlocker=`none`, rewriteSketchConflicts=`0`, rewriteSketchConflictFirstBlocker=`none`, rewriteSelection=`not-required`, rewriteSelectionFirstBlocker=`no-rewrite-sketches`, optimizerRules=`0`, optimizerRuleDetails=`none`, optimizerFamilies=`0`, promotionReadyFamilies=`0`, selected=`optimized`, fallback=`none`, gate=`not-requested`")); assertTrue(reportMarkdown.contains("- Kernel `0` diagnostics: `1`; first=`packaged IrGpu source reconstructed from runtime artifact loader with descriptor parity`")); assertFalse(reportMarkdown.contains("- Kernel `0` reconstruction blockers:")); assertTrue(reportMarkdown.contains("- Kernel `1`: `inline://integration/perlin-kernel.cl`, status=`blocked`, parity=`false`, runtimeEquivalence=`false`, sourceSwitching=`reject-production-irgpu-source`, operatorAccepted=`false`, runtimeIr=`original`, productionMutation=`false`, sourceReady=`false`, i3=`blocked`")); @@ -963,7 +963,7 @@ void validationReportAndHistoryExposeRuntimeSnapshotWorkloadGateEvidence() throw assertTrue(reportMarkdown.contains("- Kernel `1` runtime IR handoff: stage=`original`, transformed=`false`, rollback=`false`, rejected=`true`, fallback=`production-ir-gate-blocked`, first=`optimized IrGpu was rejected by the production IR acceptance gate; original IrGpu remains selected`")); assertTrue(reportMarkdown.contains("- Kernel `1` production mutation safety: enabled=`false`, gate=`blocked`, profileRequested=`true`, first=`runtime IR participates in diagnostics, but production mutation remains fail-closed until production optimizer gates pass`")); assertTrue(reportMarkdown.contains("- Kernel `1` I3 readiness: status=`blocked`, sourcePromotion=`blocked`, sourceReady=`false`, optimizerGate=`blocked`, productionMutation=`false`, first=`I3 pipeline is active for diagnostics, but source promotion or production mutation is still blocked`")); - assertTrue(reportMarkdown.contains("- Kernel `1` runtime optimizer drift: passes=`3`, applied=`1`, rolledBack=`0`, failed=`1`, proof=`0`, acceptedProof=`0`, blockingProof=`0`, replacementPlanComplete=`0`, replacementPlanPartial=`0`, replacementPlanFirstBlocker=`none`, replacementPlanValidation=`0/0 valid, invalid=0`, replacementPlanValidationFirstBlocker=`none`, optimizerRules=`0`, optimizerRuleDetails=`none`, optimizerFamilies=`0`, promotionReadyFamilies=`0`, selected=`original`, fallback=`production-ir-gate-blocked`, gate=`blocked`")); + assertTrue(reportMarkdown.contains("- Kernel `1` runtime optimizer drift: passes=`3`, applied=`1`, rolledBack=`0`, failed=`1`, proof=`0`, acceptedProof=`0`, blockingProof=`0`, replacementPlanComplete=`0`, replacementPlanPartial=`0`, replacementPlanFirstBlocker=`none`, replacementPlanValidation=`0/0 valid, invalid=0`, replacementPlanValidationFirstBlocker=`none`, rewriteSketch=`0/0 ready, blocked=0`, rewriteSketchFirstBlocker=`none`, rewriteSketchConflicts=`0`, rewriteSketchConflictFirstBlocker=`none`, rewriteSelection=`not-required`, rewriteSelectionFirstBlocker=`no-rewrite-sketches`, optimizerRules=`0`, optimizerRuleDetails=`none`, optimizerFamilies=`0`, promotionReadyFamilies=`0`, selected=`original`, fallback=`production-ir-gate-blocked`, gate=`blocked`")); assertTrue(reportMarkdown.contains("- Kernel `1` diagnostics: `1`; first=`reconstructed source must match descriptor source before promotion review`")); assertTrue(reportMarkdown.contains("- Kernel blocker families: `source-parity=1`")); assertFalse(reportMarkdown.contains("runtimeIr=`unknown`")); @@ -980,8 +980,8 @@ void validationReportAndHistoryExposeRuntimeSnapshotWorkloadGateEvidence() throw assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("sourcePromotionFirstBlockers=runtime equivalence must execute and pass before backend source promotion=1")); assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("sourcePromotionFirstBlockerFamilies=runtime-equivalence=1")); assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("kernelCount=2")); - assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("kernel.0=inline://integration/image-kernel.cl[diagnostics=1, sourceSwitching=compile-irgpu-source-review/operatorAccepted=false/sourcePromotionFirstBlocker=none, runtimeIr=optimized, optimizerDrift=recorded/2passes/rollback=0/proof=0/acceptedProof=0/blockingProof=0/replacementPlanComplete=0/replacementPlanPartial=0/replacementPlanFirstBlocker=none/replacementPlanValidationValid=0/replacementPlanValidationTotal=0/replacementPlanValidationInvalid=0/replacementPlanValidationFirstBlocker=none/optimizerRules=0/optimizerRuleDetails=none/optimizerFamilies=0/promotionReadyFamilies=0/payloadCompleteFamilies=0/payloadCompleteAll=false, extensionParticipation=not-recorded/0executions/failedContinued=0/failedClosed=0/fallback=none, productionMutation=false, sourceReady=true, i3=review-ready, families=none]")); - assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("kernel.1=inline://integration/perlin-kernel.cl[diagnostics=1, sourceSwitching=reject-production-irgpu-source/operatorAccepted=false/sourcePromotionFirstBlocker=runtime equivalence must execute and pass before backend source promotion, runtimeIr=original, optimizerDrift=recorded/3passes/rollback=0/proof=0/acceptedProof=0/blockingProof=0/replacementPlanComplete=0/replacementPlanPartial=0/replacementPlanFirstBlocker=none/replacementPlanValidationValid=0/replacementPlanValidationTotal=0/replacementPlanValidationInvalid=0/replacementPlanValidationFirstBlocker=none/optimizerRules=0/optimizerRuleDetails=none/optimizerFamilies=0/promotionReadyFamilies=0/payloadCompleteFamilies=0/payloadCompleteAll=false, extensionParticipation=not-recorded/0executions/failedContinued=0/failedClosed=0/fallback=production-ir-gate-blocked, productionMutation=false, sourceReady=false, i3=blocked, families=source-parity=1]")); + assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("kernel.0=inline://integration/image-kernel.cl[diagnostics=1, sourceSwitching=compile-irgpu-source-review/operatorAccepted=false/sourcePromotionFirstBlocker=none, runtimeIr=optimized, optimizerDrift=recorded/2passes/rollback=0/proof=0/acceptedProof=0/blockingProof=0/replacementPlanComplete=0/replacementPlanPartial=0/replacementPlanFirstBlocker=none/replacementPlanValidationValid=0/replacementPlanValidationTotal=0/replacementPlanValidationInvalid=0/replacementPlanValidationFirstBlocker=none/rewriteSketchReady=0/rewriteSketchTotal=0/rewriteSketchBlocked=0/rewriteSketchFirstBlocker=none/rewriteSketchConflicts=0/rewriteSketchConflictFirstBlocker=none/rewriteSketchConflictResolutionImplemented=false/rewriteSketchSelectionApplied=false/rewriteSelectionStatus=not-required/rewriteSelectionFirstBlocker=no-rewrite-sketches/rewriteSelectionApplied=false/rewriteBuilderImplemented=false/selectedIrReplacement=false/optimizerRules=0/optimizerRuleDetails=none/optimizerFamilies=0/promotionReadyFamilies=0/payloadCompleteFamilies=0/payloadCompleteAll=false, extensionParticipation=not-recorded/0executions/failedContinued=0/failedClosed=0/fallback=none, productionMutation=false, sourceReady=true, i3=review-ready, families=none]")); + assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("kernel.1=inline://integration/perlin-kernel.cl[diagnostics=1, sourceSwitching=reject-production-irgpu-source/operatorAccepted=false/sourcePromotionFirstBlocker=runtime equivalence must execute and pass before backend source promotion, runtimeIr=original, optimizerDrift=recorded/3passes/rollback=0/proof=0/acceptedProof=0/blockingProof=0/replacementPlanComplete=0/replacementPlanPartial=0/replacementPlanFirstBlocker=none/replacementPlanValidationValid=0/replacementPlanValidationTotal=0/replacementPlanValidationInvalid=0/replacementPlanValidationFirstBlocker=none/rewriteSketchReady=0/rewriteSketchTotal=0/rewriteSketchBlocked=0/rewriteSketchFirstBlocker=none/rewriteSketchConflicts=0/rewriteSketchConflictFirstBlocker=none/rewriteSketchConflictResolutionImplemented=false/rewriteSketchSelectionApplied=false/rewriteSelectionStatus=not-required/rewriteSelectionFirstBlocker=no-rewrite-sketches/rewriteSelectionApplied=false/rewriteBuilderImplemented=false/selectedIrReplacement=false/optimizerRules=0/optimizerRuleDetails=none/optimizerFamilies=0/promotionReadyFamilies=0/payloadCompleteFamilies=0/payloadCompleteAll=false, extensionParticipation=not-recorded/0executions/failedContinued=0/failedClosed=0/fallback=production-ir-gate-blocked, productionMutation=false, sourceReady=false, i3=blocked, families=source-parity=1]")); assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("productionSourceSwitching=disabled")); assertTrue(entries.get(0).productionPromotionExplainabilityStatus().contains("contract=valid")); assertTrue(entries.get(0).productionPromotionExplainabilityStatus().contains("decisionMode=diagnostic-only")); From 74cffd4c5dee37128a7ae34af65e891b99c0b7b7 Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Sun, 12 Jul 2026 23:30:39 +0300 Subject: [PATCH 22/32] wip: Safe progress --- ROADMAP.md | 6 +- docs/IR-Optimizer.md | 2 +- docs/Runtime-Guide.md | 2 +- ...dSourcePromotionWorkloadGateFormatter.java | 75 ++++++ ...BackendSourcePromotionWorkloadSummary.java | 165 ++++++++++++- ...ctionPromotionExplainabilityFormatter.java | 149 +++++++++++- ...ductionPromotionExplainabilitySummary.java | 51 +++- ...tionPromotionExplainabilitySummaryCli.java | 27 +++ .../runtime/GpuRuntimeIrPeepholePass.java | 36 ++- ...untimeIrPeepholeRewriteProofReadiness.java | 116 +++++++++ ...RuntimeIrPeepholeRewriteReviewPackage.java | 147 ++++++++++++ .../GpuRuntimeOptimizerDriftArtifact.java | 222 ++++++++++++++++++ .../opencl/OpenClValidationReporter.java | 18 ++ ...endSourcePromotionWorkloadSummaryTest.java | 2 +- .../GpuRuntimeCompileArtifactDumperTest.java | 48 +++- .../GpuRuntimeIrOptimizerRegistryTest.java | 74 ++++++ .../opencl/OpenClValidationReportTest.java | 8 +- 17 files changed, 1125 insertions(+), 23 deletions(-) create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteProofReadiness.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteReviewPackage.java diff --git a/ROADMAP.md b/ROADMAP.md index f8be8881..10770cc0 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -445,7 +445,7 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i Build proof-backed local expression-tree rewrites such as `a + t * (b - a) -> mix(a, b, t)`, `a * b + c -> mad/fma(a, b, c)`, `min(max(x, lo), hi) -> clamp(x, lo, hi)`, ternary step forms -> `step(edge, x)`, and small fixed-width dot-product patterns. Mutation must stay fail-closed until structural replacement, typed-graph validation, runtime-equivalence, rollback, and A1/A2 evidence are stable. - [ ] Add a safe IR pattern-matching and rewrite visitor for peephole passes. The current typed preflight safely detects the initial `mad/fma` candidate shape without editing nodes. Next, add reusable typed-node traversal/replacement APIs that can prove complete-pattern replacement before editing anything. Partial matches must produce diagnostics only. Rule implementations should remain isolated (`mix`, `mad/fma`, `clamp`, `step`, `dot`) and report applied/skipped/blocked counts per rule so optimizer drift artifacts can detect regressions. - `GpuRuntimeIrPeepholeReplacementPlan` now adds the first read-only typed replacement-planning contract. The `mad/fma` rule records root, covered, and input node ids plus complete/partial blocker state for detected `a*b+c` / `c+a*b` shapes, `clamp` records read-only complete plans for `min(max(x, lo), hi)` typed call shapes, `step` records read-only complete plans for simple `x < edge ? 0 : 1` / `x >= edge ? 1 : 0` ternary shapes, `dot` records read-only plans for fixed-width additive multiply trees such as `(a0*b0)+(a1*b1)`, and `mix` records read-only complete plans for `a + t * (b - a)` / `t * (b - a) + a`. Rewrite-sketch overlap/conflict detection and `rewriteSelection.*` selection-readiness preflight now make conflict-resolution, rewrite-builder, runtime-equivalence, approval, mutation, and selected-IR blockers explicit before any rewrite engine exists. The pass still reports `rewrite-engine-not-implemented` and never mutates selected IR. All current peephole families now have diagnostic matchers; the remaining work is shared traversal/rewrite mechanics and proof gates, not family visibility. + `GpuRuntimeIrPeepholeReplacementPlan` now adds the first read-only typed replacement-planning contract. The `mad/fma` rule records root, covered, and input node ids plus complete/partial blocker state for detected `a*b+c` / `c+a*b` shapes, `clamp` records read-only complete plans for `min(max(x, lo), hi)` typed call shapes, `step` records read-only complete plans for simple `x < edge ? 0 : 1` / `x >= edge ? 1 : 0` ternary shapes, `dot` records read-only plans for fixed-width additive multiply trees such as `(a0*b0)+(a1*b1)`, and `mix` records read-only complete plans for `a + t * (b - a)` / `t * (b - a) + a`. Rewrite-sketch overlap/conflict detection, `rewriteSelection.*` selection-readiness preflight, `rewriteProof.*` proof/runtime-equivalence/rollback preflight, and `rewriteReviewPackage.*` manual-review package preflight now make conflict-resolution, rewrite-builder, runtime-equivalence payload, rollback evidence, approval, mutation, selected-IR, and package-completeness blockers explicit before any rewrite engine exists. The pass still reports `rewrite-engine-not-implemented` and never mutates selected IR. All current peephole families now have diagnostic matchers; the remaining work is shared traversal/rewrite mechanics and proof gates, not family visibility. - [x] Add Java-source fast-math policy metadata for runtime optimizer decisions. `@GPUOptimize(fastMath = false)` is now the production-safe default policy surface. Java-source frontend artifacts persist the explicit annotation source or `default-strict`, runtime optimization requests expose the resolved policy, and optimizer hooks can declare `requiresFastMath()` so the registry skips them without invocation unless the method explicitly opts in. This does not enable production mutation by itself. - [x] Preserve fast-math policy through the ASM frontend. @@ -453,9 +453,9 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i - [x] Persist fast-math and optimizer-policy metadata in `IrGpu` storage. `kernel.irgpu.properties` now carries `optimizerPolicy.fastMath` and `optimizerPolicy.source`, with backward-compatible strict defaults in the parser. Runtime optimizer requests read the loaded `IrGpu` policy, and passes marked `requiresFastMath()` are fail-closed/skipped when fast math is false or absent. Future rule-family toggles remain open. - [ ] Add proof, runtime-equivalence, and rollback gates for peephole rewrites. - Peephole rewrites must emit `GpuRuntimeIrOptimizationProofArtifact` fields with optimizer family (`peephole`, `instcombine`, or per-rule families), rule counters, fast-math policy, original/transformed IR identities, and compact/durable runtime-equivalence payload evidence. Failed proofs, incomplete pattern rewrites, runtime-equivalence failures, or rollback diagnostics must keep the original IR selected. + `GpuRuntimeIrPeepholeRewriteProofReadiness` now exposes the read-only proof preflight above selection readiness: proof required/accepted state, runtime-equivalence payload presence/completeness, rollback evidence, approval acceptance, original IR identity, absent transformed IR identity, and disabled mutation / selected-IR replacement. `GpuRuntimeIrPeepholeRewriteReviewPackage` now sits above that as the package boundary: it records required/complete status, first blocker, conflict count, proof acceptance, runtime-equivalence payload, rollback, approval, manual-review-only state, and disabled mutation / selection / selected-IR replacement. The pass now emits these gates both at aggregate level and per rule through `rule.N.rewriteSelection.*`, `rule.N.rewriteProof.*`, and `rule.N.rewriteReviewPackage.*`, so drift artifacts can distinguish a rule blocked by structural validation from one blocked by missing runtime-equivalence payload, rollback evidence, approval, or incomplete review packaging. Real rewrites still must emit accepted proof artifacts with optimizer family (`peephole`, `instcombine`, or per-rule families), rule counters, fast-math policy, original/transformed IR identities, and compact/durable runtime-equivalence payload evidence before this item can close. Failed proofs, incomplete pattern rewrites, runtime-equivalence failures, rollback diagnostics, or incomplete review packages must keep the original IR selected. - [ ] Surface peephole telemetry in optimizer drift and promotion artifacts. - `runtime-optimizer-drift.properties` now surfaces read-only replacement-plan evidence with aggregate complete/partial counts, structural validation counts, rewrite-sketch ready/blocked counts, overlap/conflict counters, `rewriteSelection.*` status/first-blocker guardrails, and compact `optimizerRule.*` summaries from the existing `rule.N.*` proof fields, so CI can detect `mad/fma`, `clamp`, `step`, `dot`, and `mix` planning drift before any rewrite engine exists. Workload gates, compact workload summaries, OpenCL validation Markdown/history, I3 summaries, and production-promotion explainability now carry those replacement-plan counters and per-rule summaries without affecting readiness or production decisions. Production promotion must remain disabled until these rule families have stable A1/A2 runtime evidence and backend/vendor confidence. + `runtime-optimizer-drift.properties` now surfaces read-only replacement-plan evidence with aggregate complete/partial counts, structural validation counts, rewrite-sketch ready/blocked counts, overlap/conflict counters, aggregate and per-rule `rewriteSelection.*` status/first-blocker guardrails, aggregate and per-rule `rewriteProof.*` proof/runtime-equivalence/rollback blockers, aggregate and per-rule `rewriteReviewPackage.*` package-completeness blockers, and compact `optimizerRule.*` summaries from the existing `rule.N.*` proof fields, so CI can detect `mad/fma`, `clamp`, `step`, `dot`, and `mix` planning drift before any rewrite engine exists. Workload gates, compact workload summaries, OpenCL validation Markdown/history, I3 summaries, and production-promotion explainability now carry those replacement-plan counters and per-rule summaries without affecting readiness or production decisions. Production promotion must remain disabled until these rule families have stable A1/A2 runtime evidence and backend/vendor confidence. #### I3.5 Vendor/device-specific optimization strategy diff --git a/docs/IR-Optimizer.md b/docs/IR-Optimizer.md index 093c3646..ea2cc77a 100644 --- a/docs/IR-Optimizer.md +++ b/docs/IR-Optimizer.md @@ -68,7 +68,7 @@ The default bridge is intentionally proposal-only. It can surface canonicalizati Runtime artifact dumping records bridge/provider evidence in `runtime-ir-optimizer-evidence.properties`. The artifact is filtered by the stable `javatogpu.ir-optimizer` provider prefix and includes pass counts, outcomes, proposal-only/selected counts, original/transformed IR identities, proof artifact fields, and diagnostics. This is an evidence surface only; it does not authorize optimized IR selection or production mutation by itself. -Runtime peephole/InstCombine proof is also kept read-only. `GpuRuntimeIrPeepholeReplacementPlan` records future structural replacement intent, including root node, covered node ids, input node ids, replacement kind, completeness, and first blocker. `GpuRuntimeIrTypedNodeGraph` is the shared read-only traversal view for typed peephole rules, and `GpuRuntimeIrPeepholeReplacementPlanValidation` checks each emitted plan against that graph before it is surfaced. `GpuRuntimeIrPeepholeRewriteSketch` is the next mutation-free skeleton: a complete and valid plan can become `sketch-ready`, but the sketch still records `rewriteBuilderImplemented=false`, `mutationAllowed=false`, and `selectedIrReplacement=false`; runtime equivalence and approval remain required. `GpuRuntimeIrPeepholeRewriteSelectionReadiness` now sits above ready/blocked sketches as a read-only selection preflight: it reports sketch counts, conflict counts, status, first blocker, missing rewrite-builder/conflict-resolution/proof/approval gates, and disabled mutation/selection flags without choosing any optimized IR. Validation fields such as `replacementPlan.validation.invalid.count` and `rule.N.replacementPlan.validation.firstBlocker` fail closed on missing roots, uncovered roots, or missing covered/input nodes; they still do not build replacement nodes or authorize mutation. The current matcher set covers `mad/fma` for `a*b+c` / `c+a*b`, `clamp` for `min(max(x, lo), hi)`, `step` for simple `x < edge ? 0 : 1` / `x >= edge ? 1 : 0` ternaries, `dot` for additive multiply trees such as `(a0*b0)+(a1*b1)`, and `mix` for `a + t * (b - a)` / `t * (b - a) + a`. `runtime-optimizer-drift.properties` aggregates complete/partial replacement-plan counts, structural plan-validation total/valid/invalid counts, rewrite-sketch ready/blocked counts, overlap/conflict counters, `rewriteSelection.*` status/first-blocker guardrails, and compact `optimizerRule.*` summaries for CI drift detection. Workload gates, validation reports/history, I3 summaries, and production-promotion explainability carry those counters forward as evidence-only telemetry. The pass still reports `rewrite-engine-not-implemented` and never mutates the selected IR. +Runtime peephole/InstCombine proof is also kept read-only. `GpuRuntimeIrPeepholeReplacementPlan` records future structural replacement intent, including root node, covered node ids, input node ids, replacement kind, completeness, and first blocker. `GpuRuntimeIrTypedNodeGraph` is the shared read-only traversal view for typed peephole rules, and `GpuRuntimeIrPeepholeReplacementPlanValidation` checks each emitted plan against that graph before it is surfaced. `GpuRuntimeIrPeepholeRewriteSketch` is the next mutation-free skeleton: a complete and valid plan can become `sketch-ready`, but the sketch still records `rewriteBuilderImplemented=false`, `mutationAllowed=false`, and `selectedIrReplacement=false`; runtime equivalence and approval remain required. `GpuRuntimeIrPeepholeRewriteSelectionReadiness` now sits above ready/blocked sketches as a read-only selection preflight: it reports sketch counts, conflict counts, status, first blocker, missing rewrite-builder/conflict-resolution/proof/approval gates, and disabled mutation/selection flags without choosing any optimized IR. `GpuRuntimeIrPeepholeRewriteProofReadiness` then records the next fail-closed proof boundary: proof required/accepted state, runtime-equivalence payload presence/completeness, rollback evidence, approval acceptance, original IR identity, absent transformed IR identity, and disabled mutation / selected-IR replacement. `GpuRuntimeIrPeepholeRewriteReviewPackage` sits above proof readiness as the manual-review package boundary: it records required/complete state, conflict count, proof acceptance, runtime-equivalence payload, rollback, approval, manual-review-only state, and disabled mutation / selection / selected-IR replacement. The pass emits aggregate `rewriteSelection.*`, `rewriteProof.*`, and `rewriteReviewPackage.*` plus per-rule `rule.N.rewriteSelection.*`, `rule.N.rewriteProof.*`, and `rule.N.rewriteReviewPackage.*`, so CI can identify which matcher family is blocked by graph validation, conflict resolution, missing runtime-equivalence payloads, missing rollback evidence, missing approval, or incomplete review packaging. Validation fields such as `replacementPlan.validation.invalid.count` and `rule.N.replacementPlan.validation.firstBlocker` fail closed on missing roots, uncovered roots, or missing covered/input nodes; they still do not build replacement nodes or authorize mutation. The current matcher set covers `mad/fma` for `a*b+c` / `c+a*b`, `clamp` for `min(max(x, lo), hi)`, `step` for simple `x < edge ? 0 : 1` / `x >= edge ? 1 : 0` ternaries, `dot` for additive multiply trees such as `(a0*b0)+(a1*b1)`, and `mix` for `a + t * (b - a)` / `t * (b - a) + a`. `runtime-optimizer-drift.properties` aggregates complete/partial replacement-plan counts, structural plan-validation total/valid/invalid counts, rewrite-sketch ready/blocked counts, overlap/conflict counters, aggregate and per-rule `rewriteSelection.*` status/first-blocker guardrails, aggregate and per-rule `rewriteProof.*` proof/runtime-equivalence/rollback blockers, aggregate and per-rule `rewriteReviewPackage.*` package-completeness blockers, and compact `optimizerRule.*` summaries for CI drift detection. Workload gates, validation reports/history, I3 summaries, and production-promotion explainability carry those counters forward as evidence-only telemetry. The pass still reports `rewrite-engine-not-implemented` and never mutates the selected IR. The same evidence artifact now records `approvalTemplate.*` fields for each optimizer pass. Real proposal rewrites with distinct original/optimized identities and accepted proof metadata are marked `pending`; preview and no-change proposals are marked `not-applicable` with a first blocker such as `proposal-decision-not-proposed`. diff --git a/docs/Runtime-Guide.md b/docs/Runtime-Guide.md index cdde8608..eea998f7 100644 --- a/docs/Runtime-Guide.md +++ b/docs/Runtime-Guide.md @@ -235,7 +235,7 @@ Non-analysis optimizer passes with runtime-equivalence payload evidence also pro When the optional IR optimizer bridge participates, each kernel directory may also receive `runtime-ir-optimizer-evidence.properties`. The artifact records only proposal/provider evidence from stable `javatogpu.ir-optimizer` sources: pass counts, proposal-only versus selected optimized counts, rollback counts, IR identities, proof fields, approval-template applicability, and diagnostics. Constant-folding preview proof is also aggregated into `constantFoldingPreview.*` fields for pass count, candidate count, skipped blockers, required runtime-equivalence/approval gates, and unresolved integer-overflow / floating-point-rounding safety flags. Safe-local-CSE preview proof is aggregated into `safeLocalCsePreview.*` fields for pass count, expression/candidate/duplicate/equivalence-class counts, unsupported-operator / impure-operand / control-flow-boundary blockers, required runtime-equivalence/approval gates, and unresolved dominance / side-effect-freedom proof flags. Typed dead-code preview proof is aggregated into `typedDeadCodePreview.*` fields for pass count, typed/reachable/unreachable node counts, missing-root / missing-child-reference / side-effecting-unreachable blockers, required runtime-equivalence/approval gates, and unresolved side-effect-freedom proof flags. The artifact also records `previewReadiness.*` fields with aggregate status, recorded/candidate/blocked family counts, and a per-family status summary so CI can distinguish no-candidate previews from proof-blocked previews and review-ready runtime-equivalence candidates. `runtimeEquivalenceReview.*` records the next fail-closed review boundary: status, eligibility, requirement, first blocker, family summary, disabled production mutation, disabled selected-IR replacement, and manual-review-only state. `reviewPackage.*` records the manual-review package boundary above that review gate, including package status, requirement/completeness, first blocker, proposal-pass count, pending approval count, original/optimized IR and proof-summary requirements, manual-review-only state, and disabled production mutation / selected-IR replacement. `openClValidationReport` aggregates this into `Runtime IR Optimizer Evidence` for CI visibility, including constant-folding, safe-local-CSE, typed dead-code preview totals, preview-readiness status, runtime-equivalence review eligibility, and review-package status. `validateOpenClRuntimeIrOptimizerEvidence` then validates the raw runtime evidence files and fails CI if those fail-closed guardrails are missing, completed early, or production-enabling. The section and validator are evidence-only and do not participate in history, source switching, selected-IR replacement, or production mutation gates. -Runtime peephole/InstCombine remains diagnostic-only. `GpuRuntimeIrPeepholeReplacementPlan` records read-only typed replacement plans for current rule families: `mad/fma`, `clamp`, `step`, `dot`, and `mix`. Built-in rules now use `GpuRuntimeIrTypedNodeGraph` as the shared read-only typed-node traversal surface, and each emitted plan is checked by `GpuRuntimeIrPeepholeReplacementPlanValidation` before proof fields are exported. `GpuRuntimeIrPeepholeRewriteSketch` records the mutation-free structural rewrite skeleton above those validated plans: ready sketches prove only that the future rewrite shape is describable, while `rewriteBuilderImplemented`, `mutationAllowed`, and `selectedIrReplacement` remain `false`. `rewriteSelection.*` fields add a read-only selection preflight over those sketches, including sketch/conflict counts, selection status, first blocker, proof/approval requirements, and disabled mutation/selection guardrails. Proof fields expose the candidate root node, covered nodes, input nodes, completeness, first blocker, structural validation counters such as `replacementPlan.validation.invalid.count` / `rule.N.replacementPlan.validation.firstBlocker`, sketch counters such as `rewriteSketch.ready.count` / `rule.N.rewriteSketch.blocked.count`, and selection preflight fields such as `rewriteSelection.status` / `rewriteSelection.firstBlocker`. `runtime-optimizer-drift.properties` aggregates the complete/partial replacement-plan counts, plan-validation total/valid/invalid counts, rewrite-sketch ready/blocked counts, conflict counts, selection-readiness guardrails, first incomplete/invalid/sketch/selection blockers, and compact `optimizerRule.*` summaries from existing `rule.N.*` proof fields so CI can detect planning drift while the rewrite engine remains absent. Workload gates, compact workload summaries, OpenCL validation Markdown/history, I3 summaries, and production-promotion explainability surface the same counters as evidence-only telemetry. These plans and sketches describe what a future structural rewrite would need to replace; they do not edit typed bodies, select optimized IR, or enable production mutation. +Runtime peephole/InstCombine remains diagnostic-only. `GpuRuntimeIrPeepholeReplacementPlan` records read-only typed replacement plans for current rule families: `mad/fma`, `clamp`, `step`, `dot`, and `mix`. Built-in rules now use `GpuRuntimeIrTypedNodeGraph` as the shared read-only typed-node traversal surface, and each emitted plan is checked by `GpuRuntimeIrPeepholeReplacementPlanValidation` before proof fields are exported. `GpuRuntimeIrPeepholeRewriteSketch` records the mutation-free structural rewrite skeleton above those validated plans: ready sketches prove only that the future rewrite shape is describable, while `rewriteBuilderImplemented`, `mutationAllowed`, and `selectedIrReplacement` remain `false`. `rewriteSelection.*` fields add a read-only selection preflight over those sketches, including sketch/conflict counts, selection status, first blocker, proof/approval requirements, and disabled mutation/selection guardrails. `rewriteProof.*` fields add the matching proof preflight: proof status, first blocker, runtime-equivalence payload presence/completeness, rollback evidence, approval acceptance, original IR identity, absent transformed IR identity, and disabled selected-IR replacement. `rewriteReviewPackage.*` fields add the manual-review package boundary above proof readiness: package required/complete state, conflict count, proof acceptance, runtime-equivalence payload, rollback, approval, manual-review-only state, and disabled mutation / selection / selected-IR replacement. The same selection/proof/package preflight fields are also emitted under `rule.N.rewriteSelection.*`, `rule.N.rewriteProof.*`, and `rule.N.rewriteReviewPackage.*`, making rule-family blockers visible without enabling any rewrite. Proof fields expose the candidate root node, covered nodes, input nodes, completeness, first blocker, structural validation counters such as `replacementPlan.validation.invalid.count` / `rule.N.replacementPlan.validation.firstBlocker`, sketch counters such as `rewriteSketch.ready.count` / `rule.N.rewriteSketch.blocked.count`, selection preflight fields such as `rewriteSelection.status` / `rule.N.rewriteSelection.firstBlocker`, proof preflight fields such as `rewriteProof.status` / `rule.N.rewriteProof.firstBlocker`, and review-package fields such as `rewriteReviewPackage.status` / `rule.N.rewriteReviewPackage.firstBlocker`. `runtime-optimizer-drift.properties` aggregates the complete/partial replacement-plan counts, plan-validation total/valid/invalid counts, rewrite-sketch ready/blocked counts, conflict counts, selection-readiness guardrails, proof/runtime-equivalence/rollback blockers, package-completeness blockers, first incomplete/invalid/sketch/selection/proof/package blockers, and compact `optimizerRule.*` summaries from existing `rule.N.*` proof fields so CI can detect planning drift while the rewrite engine remains absent. Workload gates, compact workload summaries, OpenCL validation Markdown/history, I3 summaries, and production-promotion explainability surface the same counters as evidence-only telemetry. These plans and sketches describe what a future structural rewrite would need to replace; they do not edit typed bodies, select optimized IR, or enable production mutation. The same index records `familyBinding.*`. Binding remains `not-bound` unless exactly one optimizer family has passed runtime evidence whose comparison mode is explicitly family-specific (`optimizer-family::...`). Descriptor-versus-reconstructed-source evidence is pipeline validation and is deliberately rejected as optimizer-family proof. diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatter.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatter.java index 323675fc..7878c333 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatter.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatter.java @@ -489,6 +489,31 @@ private static void copyRuntimeOptimizerDriftProperties(Properties source, Prope target.setProperty("runtimeOptimizerDrift.rewriteSelection.mutationAllowed", "false"); target.setProperty("runtimeOptimizerDrift.rewriteSelection.selectionApplied", "false"); target.setProperty("runtimeOptimizerDrift.rewriteSelection.selectedIrReplacement", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteProof.status", "not-required"); + target.setProperty("runtimeOptimizerDrift.rewriteProof.firstBlocker", "no-proof-candidates"); + target.setProperty("runtimeOptimizerDrift.rewriteProof.proofAccepted", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteProof.runtimeEquivalencePayload.present", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteProof.runtimeEquivalencePayload.complete", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteProof.rollbackEvidence.present", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteProof.rollbackClean", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteProof.approvalAccepted", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteProof.mutationAllowed", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteProof.selectedIrReplacement", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.status", "not-required"); + target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.firstBlocker", "no-review-candidates"); + target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.required", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.complete", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.conflict.count", "0"); + target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.proofAccepted", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.runtimeEquivalencePayload.present", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.runtimeEquivalencePayload.complete", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.rollbackEvidence.present", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.rollbackClean", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.approvalAccepted", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.mutationAllowed", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.selectionApplied", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.selectedIrReplacement", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.manualReviewOnly", "true"); target.setProperty("runtimeOptimizerDrift.optimizerRule.count", "0"); target.setProperty("runtimeOptimizerDrift.optimizerRule.summary", "none"); target.setProperty("runtimeOptimizerDrift.fallbackDecision", target.getProperty("runtimeIrHandoff.fallbackDecision", "none")); @@ -546,6 +571,31 @@ private static void copyRuntimeOptimizerDriftProperties(Properties source, Prope copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.mutationAllowed", "false"); copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.selectionApplied", "false"); copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.selectedIrReplacement", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteProof.status", "not-required"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteProof.firstBlocker", "no-proof-candidates"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteProof.proofAccepted", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteProof.runtimeEquivalencePayload.present", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteProof.runtimeEquivalencePayload.complete", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteProof.rollbackEvidence.present", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteProof.rollbackClean", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteProof.approvalAccepted", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteProof.mutationAllowed", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteProof.selectedIrReplacement", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.status", "not-required"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.firstBlocker", "no-review-candidates"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.required", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.complete", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.conflict.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.proofAccepted", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.runtimeEquivalencePayload.present", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.runtimeEquivalencePayload.complete", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.rollbackEvidence.present", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.rollbackClean", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.approvalAccepted", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.mutationAllowed", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.selectionApplied", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.selectedIrReplacement", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.manualReviewOnly", "true"); copyRuntimeOptimizerDriftProperty(source, target, "optimizerRule.count", "0"); copyRuntimeOptimizerDriftProperty(source, target, "optimizerRule.summary", "none"); copyIndexedPropertyGroup(source, target, "runtimeOptimizerDrift.optimizerRule", "optimizerRule"); @@ -911,6 +961,31 @@ private static void appendRuntimeOptimizerDrift(StringBuilder builder, String pr builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.mutationAllowed", "false")).append('\n'); builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.selectionApplied=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.selectionApplied", "false")).append('\n'); builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.selectedIrReplacement", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteProof.status=").append(entry.getProperty("runtimeOptimizerDrift.rewriteProof.status", "not-required")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteProof.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.rewriteProof.firstBlocker", "no-proof-candidates")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteProof.proofAccepted=").append(entry.getProperty("runtimeOptimizerDrift.rewriteProof.proofAccepted", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteProof.runtimeEquivalencePayload.present=").append(entry.getProperty("runtimeOptimizerDrift.rewriteProof.runtimeEquivalencePayload.present", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteProof.runtimeEquivalencePayload.complete=").append(entry.getProperty("runtimeOptimizerDrift.rewriteProof.runtimeEquivalencePayload.complete", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteProof.rollbackEvidence.present=").append(entry.getProperty("runtimeOptimizerDrift.rewriteProof.rollbackEvidence.present", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteProof.rollbackClean=").append(entry.getProperty("runtimeOptimizerDrift.rewriteProof.rollbackClean", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteProof.approvalAccepted=").append(entry.getProperty("runtimeOptimizerDrift.rewriteProof.approvalAccepted", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteProof.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.rewriteProof.mutationAllowed", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteProof.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.rewriteProof.selectedIrReplacement", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.status=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.status", "not-required")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.firstBlocker", "no-review-candidates")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.required=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.required", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.complete=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.complete", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.conflict.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.conflict.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.proofAccepted=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.proofAccepted", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.runtimeEquivalencePayload.present=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.runtimeEquivalencePayload.present", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.runtimeEquivalencePayload.complete=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.runtimeEquivalencePayload.complete", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.rollbackEvidence.present=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.rollbackEvidence.present", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.rollbackClean=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.rollbackClean", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.approvalAccepted=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.approvalAccepted", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.mutationAllowed", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.selectionApplied=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.selectionApplied", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.selectedIrReplacement", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.manualReviewOnly=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.manualReviewOnly", "true")).append('\n'); builder.append(prefix).append("runtimeOptimizerDrift.optimizerRule.count=").append(entry.getProperty("runtimeOptimizerDrift.optimizerRule.count", "0")).append('\n'); builder.append(prefix).append("runtimeOptimizerDrift.optimizerRule.summary=").append(entry.getProperty("runtimeOptimizerDrift.optimizerRule.summary", "none")).append('\n'); appendIndexedPropertyGroup(builder, prefix, entry, "runtimeOptimizerDrift.optimizerRule"); diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummary.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummary.java index 767339cc..d804b0e3 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummary.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummary.java @@ -39,6 +39,10 @@ public record GpuBackendSourcePromotionWorkloadSummary( String optimizerRewriteSketchConflictFirstBlockers, String optimizerRewriteSelectionStatuses, String optimizerRewriteSelectionFirstBlockers, + String optimizerRewriteProofStatuses, + String optimizerRewriteProofFirstBlockers, + String optimizerRewriteReviewPackageStatuses, + String optimizerRewriteReviewPackageFirstBlockers, int optimizerRuleCount, String optimizerRuleSummary, String optimizerRuleDetails, @@ -88,6 +92,10 @@ public static GpuBackendSourcePromotionWorkloadSummary notRecorded() { "", "", "", + "", + "", + "", + "", 0, "none", "none", @@ -153,6 +161,10 @@ public static GpuBackendSourcePromotionWorkloadSummary fromProperties(Properties summarizeKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.rewriteSketch.conflict.firstBlocker"), summarizeRewriteSelectionStatuses(properties, kernelCount), summarizeRewriteSelectionFirstBlockers(properties, kernelCount), + summarizeRewriteProofStatuses(properties, kernelCount), + summarizeRewriteProofFirstBlockers(properties, kernelCount), + summarizeRewriteReviewPackageStatuses(properties, kernelCount), + summarizeRewriteReviewPackageFirstBlockers(properties, kernelCount), sumKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.optimizerRule.count"), summarizeKernelProperty(properties, kernelCount, "runtimeOptimizerDrift.optimizerRule.summary"), summarizeOptimizerRules(properties, kernelCount), @@ -346,7 +358,11 @@ private String optimizerRewriteSketchEvidenceText() { && optimizerRewriteSketchFirstBlockers.isBlank() && optimizerRewriteSketchConflictFirstBlockers.isBlank() && optimizerRewriteSelectionStatuses.isBlank() - && optimizerRewriteSelectionFirstBlockers.isBlank()) { + && optimizerRewriteSelectionFirstBlockers.isBlank() + && optimizerRewriteProofStatuses.isBlank() + && optimizerRewriteProofFirstBlockers.isBlank() + && optimizerRewriteReviewPackageStatuses.isBlank() + && optimizerRewriteReviewPackageFirstBlockers.isBlank()) { return ""; } StringBuilder builder = new StringBuilder(", optimizerRewriteSketches=ready=") @@ -373,6 +389,23 @@ private String optimizerRewriteSketchEvidenceText() { builder.append("/selectionFirstBlockers=").append(optimizerRewriteSelectionFirstBlockers); } builder.append("/selectionApplied=false"); + if (!optimizerRewriteProofStatuses.isBlank()) { + builder.append("/proofStatus=").append(optimizerRewriteProofStatuses); + } + if (!optimizerRewriteProofFirstBlockers.isBlank()) { + builder.append("/proofFirstBlockers=").append(optimizerRewriteProofFirstBlockers); + } + builder.append("/proofAccepted=false") + .append("/runtimeEquivalencePayloadComplete=false") + .append("/rollbackClean=false"); + if (!optimizerRewriteReviewPackageStatuses.isBlank()) { + builder.append("/reviewPackageStatus=").append(optimizerRewriteReviewPackageStatuses); + } + if (!optimizerRewriteReviewPackageFirstBlockers.isBlank()) { + builder.append("/reviewPackageFirstBlockers=").append(optimizerRewriteReviewPackageFirstBlockers); + } + builder.append("/reviewPackageComplete=false") + .append("/reviewPackageManualReviewOnly=true"); return builder.toString(); } @@ -433,6 +466,66 @@ private static String summarizeRewriteSelectionFirstBlockers(Properties properti return summarizeCounts(counts); } + private static String summarizeRewriteProofStatuses(Properties properties, int kernelCount) { + Map counts = new LinkedHashMap<>(); + for (int index = 0; index < kernelCount; index++) { + String value = properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteProof.status", + "" + ); + if (value.isBlank() || "unknown".equals(value) || "not-required".equals(value)) { + continue; + } + counts.merge(value, 1, Integer::sum); + } + return summarizeCounts(counts); + } + + private static String summarizeRewriteProofFirstBlockers(Properties properties, int kernelCount) { + Map counts = new LinkedHashMap<>(); + for (int index = 0; index < kernelCount; index++) { + String value = properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteProof.firstBlocker", + "" + ); + if (value.isBlank() || "none".equals(value) || "unknown".equals(value) || "no-proof-candidates".equals(value)) { + continue; + } + counts.merge(value, 1, Integer::sum); + } + return summarizeCounts(counts); + } + + private static String summarizeRewriteReviewPackageStatuses(Properties properties, int kernelCount) { + Map counts = new LinkedHashMap<>(); + for (int index = 0; index < kernelCount; index++) { + String value = properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteReviewPackage.status", + "" + ); + if (value.isBlank() || "unknown".equals(value) || "not-required".equals(value)) { + continue; + } + counts.merge(value, 1, Integer::sum); + } + return summarizeCounts(counts); + } + + private static String summarizeRewriteReviewPackageFirstBlockers(Properties properties, int kernelCount) { + Map counts = new LinkedHashMap<>(); + for (int index = 0; index < kernelCount; index++) { + String value = properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteReviewPackage.firstBlocker", + "" + ); + if (value.isBlank() || "none".equals(value) || "unknown".equals(value) || "no-review-candidates".equals(value)) { + continue; + } + counts.merge(value, 1, Integer::sum); + } + return summarizeCounts(counts); + } + private static String summarizeOptimizerFamilies(Properties properties, int kernelCount) { Map families = new LinkedHashMap<>(); for (int index = 0; index < kernelCount; index++) { @@ -744,6 +837,21 @@ private static String summarizeKernelEvidence(Properties properties, int kernelC "kernel." + index + ".runtimeOptimizerDrift.rewriteSelection.selectionApplied", "false" )) + .append("/rewriteProofStatus=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteProof.status", + "not-required" + )) + .append("/rewriteProofFirstBlocker=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteProof.firstBlocker", + "no-proof-candidates" + )) + .append("/rewriteProofAccepted=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteProof.proofAccepted", + "false" + )) .append("/rewriteBuilderImplemented=") .append(properties.getProperty( "kernel." + index + ".runtimeOptimizerDrift.rewriteSketch.rewriteBuilderImplemented", @@ -754,6 +862,21 @@ private static String summarizeKernelEvidence(Properties properties, int kernelC "kernel." + index + ".runtimeOptimizerDrift.rewriteSketch.selectedIrReplacement", "false" )) + .append("/rewriteReviewPackageStatus=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteReviewPackage.status", + "not-required" + )) + .append("/rewriteReviewPackageFirstBlocker=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteReviewPackage.firstBlocker", + "no-review-candidates" + )) + .append("/rewriteReviewPackageComplete=") + .append(properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteReviewPackage.complete", + "false" + )) .append("/optimizerRules=") .append(properties.getProperty("kernel." + index + ".runtimeOptimizerDrift.optimizerRule.count", "0")) .append("/optimizerRuleDetails=") @@ -916,6 +1039,10 @@ private static void collectKernelOptimizerRules( parsePositiveInt(properties.getProperty(prefix + "rewriteSketch.ready.count", "0")), parsePositiveInt(properties.getProperty(prefix + "rewriteSketch.blocked.count", "0")), properties.getProperty(prefix + "rewriteSketch.firstBlocker", "none"), + properties.getProperty(prefix + "rewriteSelection.status", "not-required"), + properties.getProperty(prefix + "rewriteSelection.firstBlocker", "no-rewrite-sketches"), + properties.getProperty(prefix + "rewriteProof.status", "not-required"), + properties.getProperty(prefix + "rewriteProof.firstBlocker", "no-proof-candidates"), properties.getProperty(prefix + "firstBlocker", properties.getProperty(prefix + "replacementPlan.firstBlocker", "none")) )); } @@ -961,6 +1088,14 @@ private static String formatOptimizerRules(Map r .append(rule.rewriteSketchBlockedCount()) .append(", rewriteSketchFirstBlocker=") .append(rule.rewriteSketchFirstBlocker()) + .append(", rewriteSelectionStatus=") + .append(rule.rewriteSelectionStatus()) + .append(", rewriteSelectionFirstBlocker=") + .append(rule.rewriteSelectionFirstBlocker()) + .append(", rewriteProofStatus=") + .append(rule.rewriteProofStatus()) + .append(", rewriteProofFirstBlocker=") + .append(rule.rewriteProofFirstBlocker()) .append(", firstBlocker=") .append(rule.firstBlocker()) .append(']'); @@ -1051,11 +1186,15 @@ private record OptimizerRuleAggregate( int rewriteSketchReadyCount, int rewriteSketchBlockedCount, String rewriteSketchFirstBlocker, + String rewriteSelectionStatus, + String rewriteSelectionFirstBlocker, + String rewriteProofStatus, + String rewriteProofFirstBlocker, String firstBlocker ) { private static OptimizerRuleAggregate empty(String id) { - return new OptimizerRuleAggregate(id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "none", 0, 0, 0, "none", "none"); + return new OptimizerRuleAggregate(id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "none", 0, 0, 0, "none", "not-required", "no-rewrite-sketches", "not-required", "no-proof-candidates", "none"); } private OptimizerRuleAggregate add( @@ -1075,6 +1214,10 @@ private OptimizerRuleAggregate add( int rewriteSketchReadyCount, int rewriteSketchBlockedCount, String nextRewriteSketchBlocker, + String nextRewriteSelectionStatus, + String nextRewriteSelectionBlocker, + String nextRewriteProofStatus, + String nextRewriteProofBlocker, String nextBlocker ) { String blocker = firstBlocker; @@ -1092,6 +1235,10 @@ private OptimizerRuleAggregate add( && !"none".equals(nextRewriteSketchBlocker)) { sketchBlocker = nextRewriteSketchBlocker; } + String selectionStatus = firstNonDefault(rewriteSelectionStatus, nextRewriteSelectionStatus, "not-required"); + String selectionBlocker = firstNonDefault(rewriteSelectionFirstBlocker, nextRewriteSelectionBlocker, "no-rewrite-sketches"); + String proofStatus = firstNonDefault(rewriteProofStatus, nextRewriteProofStatus, "not-required"); + String proofBlocker = firstNonDefault(rewriteProofFirstBlocker, nextRewriteProofBlocker, "no-proof-candidates"); if ("none".equals(blocker) && nextBlocker != null && !nextBlocker.isBlank() @@ -1116,8 +1263,22 @@ private OptimizerRuleAggregate add( this.rewriteSketchReadyCount + rewriteSketchReadyCount, this.rewriteSketchBlockedCount + rewriteSketchBlockedCount, sketchBlocker, + selectionStatus, + selectionBlocker, + proofStatus, + proofBlocker, blocker ); } + + private static String firstNonDefault(String current, String next, String defaultValue) { + if (current != null && !current.isBlank() && !defaultValue.equals(current) && !"unknown".equals(current)) { + return current; + } + if (next != null && !next.isBlank() && !defaultValue.equals(next) && !"unknown".equals(next)) { + return next; + } + return defaultValue; + } } } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilityFormatter.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilityFormatter.java index e0d6c585..0823d434 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilityFormatter.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilityFormatter.java @@ -239,6 +239,22 @@ public static String format( "optimizerRewriteSelection.firstBlockers", summarizeRewriteSelectionFirstBlockers(gate, kernelCount) ); + String optimizerRewriteProofStatuses = gate.getProperty( + "optimizerRewriteProof.statuses", + summarizeRewriteProofStatuses(gate, kernelCount) + ); + String optimizerRewriteProofFirstBlockers = gate.getProperty( + "optimizerRewriteProof.firstBlockers", + summarizeRewriteProofFirstBlockers(gate, kernelCount) + ); + String optimizerRewriteReviewPackageStatuses = gate.getProperty( + "optimizerRewriteReviewPackage.statuses", + summarizeRewriteReviewPackageStatuses(gate, kernelCount) + ); + String optimizerRewriteReviewPackageFirstBlockers = gate.getProperty( + "optimizerRewriteReviewPackage.firstBlockers", + summarizeRewriteReviewPackageFirstBlockers(gate, kernelCount) + ); int optimizerRuleCount = parsePositiveInt(gate.getProperty( "optimizerRule.count", Integer.toString(sumKernelProperty(gate, kernelCount, "runtimeOptimizerDrift.optimizerRule.count")) @@ -534,6 +550,25 @@ public static String format( builder.append("optimizerRewriteSelection.mutationAllowed=false\n"); builder.append("optimizerRewriteSelection.selectionApplied=false\n"); builder.append("optimizerRewriteSelection.selectedIrReplacement=false\n"); + builder.append("optimizerRewriteProof.statuses=").append(optimizerRewriteProofStatuses).append('\n'); + builder.append("optimizerRewriteProof.firstBlockers=").append(optimizerRewriteProofFirstBlockers).append('\n'); + builder.append("optimizerRewriteProof.proofAccepted=false\n"); + builder.append("optimizerRewriteProof.runtimeEquivalencePayload.complete=false\n"); + builder.append("optimizerRewriteProof.rollbackClean=false\n"); + builder.append("optimizerRewriteProof.approvalAccepted=false\n"); + builder.append("optimizerRewriteProof.mutationAllowed=false\n"); + builder.append("optimizerRewriteProof.selectedIrReplacement=false\n"); + builder.append("optimizerRewriteReviewPackage.statuses=").append(optimizerRewriteReviewPackageStatuses).append('\n'); + builder.append("optimizerRewriteReviewPackage.firstBlockers=").append(optimizerRewriteReviewPackageFirstBlockers).append('\n'); + builder.append("optimizerRewriteReviewPackage.complete=false\n"); + builder.append("optimizerRewriteReviewPackage.proofAccepted=false\n"); + builder.append("optimizerRewriteReviewPackage.runtimeEquivalencePayload.complete=false\n"); + builder.append("optimizerRewriteReviewPackage.rollbackClean=false\n"); + builder.append("optimizerRewriteReviewPackage.approvalAccepted=false\n"); + builder.append("optimizerRewriteReviewPackage.mutationAllowed=false\n"); + builder.append("optimizerRewriteReviewPackage.selectionApplied=false\n"); + builder.append("optimizerRewriteReviewPackage.selectedIrReplacement=false\n"); + builder.append("optimizerRewriteReviewPackage.manualReviewOnly=true\n"); builder.append("optimizerRule.count=").append(optimizerRuleCount).append('\n'); builder.append("optimizerRule.summary=").append(optimizerRuleSummary).append('\n'); builder.append("optimizerRule.details=").append(optimizerRuleDetails).append('\n'); @@ -915,6 +950,80 @@ private static String summarizeRewriteSelectionFirstBlockers(Properties properti return summarizeCounts(counts); } + private static String summarizeRewriteProofStatuses(Properties properties, int kernelCount) { + if (kernelCount <= 0) { + return "none"; + } + java.util.LinkedHashMap counts = new java.util.LinkedHashMap<>(); + for (int index = 0; index < kernelCount; index++) { + String value = properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteProof.status", + "not-required" + ); + if (!value.isBlank() && !"unknown".equals(value) && !"not-required".equals(value)) { + counts.merge(value, 1, Integer::sum); + } + } + return summarizeCounts(counts); + } + + private static String summarizeRewriteProofFirstBlockers(Properties properties, int kernelCount) { + if (kernelCount <= 0) { + return "none"; + } + java.util.LinkedHashMap counts = new java.util.LinkedHashMap<>(); + for (int index = 0; index < kernelCount; index++) { + String value = properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteProof.firstBlocker", + "no-proof-candidates" + ); + if (!value.isBlank() + && !"none".equals(value) + && !"unknown".equals(value) + && !"no-proof-candidates".equals(value)) { + counts.merge(value, 1, Integer::sum); + } + } + return summarizeCounts(counts); + } + + private static String summarizeRewriteReviewPackageStatuses(Properties properties, int kernelCount) { + if (kernelCount <= 0) { + return "none"; + } + java.util.LinkedHashMap counts = new java.util.LinkedHashMap<>(); + for (int index = 0; index < kernelCount; index++) { + String value = properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteReviewPackage.status", + "not-required" + ); + if (!value.isBlank() && !"unknown".equals(value) && !"not-required".equals(value)) { + counts.merge(value, 1, Integer::sum); + } + } + return summarizeCounts(counts); + } + + private static String summarizeRewriteReviewPackageFirstBlockers(Properties properties, int kernelCount) { + if (kernelCount <= 0) { + return "none"; + } + java.util.LinkedHashMap counts = new java.util.LinkedHashMap<>(); + for (int index = 0; index < kernelCount; index++) { + String value = properties.getProperty( + "kernel." + index + ".runtimeOptimizerDrift.rewriteReviewPackage.firstBlocker", + "no-review-candidates" + ); + if (!value.isBlank() + && !"none".equals(value) + && !"unknown".equals(value) + && !"no-review-candidates".equals(value)) { + counts.merge(value, 1, Integer::sum); + } + } + return summarizeCounts(counts); + } + private static String summarizeCounts(java.util.LinkedHashMap counts) { if (counts.isEmpty()) { return "none"; @@ -961,6 +1070,10 @@ private static String summarizeOptimizerRules(Properties properties, int kernelC parsePositiveInt(properties.getProperty(prefix + "rewriteSketch.ready.count", "0")), parsePositiveInt(properties.getProperty(prefix + "rewriteSketch.blocked.count", "0")), properties.getProperty(prefix + "rewriteSketch.firstBlocker", "none"), + properties.getProperty(prefix + "rewriteSelection.status", "not-required"), + properties.getProperty(prefix + "rewriteSelection.firstBlocker", "no-rewrite-sketches"), + properties.getProperty(prefix + "rewriteProof.status", "not-required"), + properties.getProperty(prefix + "rewriteProof.firstBlocker", "no-proof-candidates"), properties.getProperty(prefix + "firstBlocker", properties.getProperty(prefix + "replacementPlan.firstBlocker", "none")) )); } @@ -1004,6 +1117,14 @@ private static String summarizeOptimizerRules(Properties properties, int kernelC .append(rule.rewriteSketchBlockedCount()) .append(", rewriteSketchFirstBlocker=") .append(rule.rewriteSketchFirstBlocker()) + .append(", rewriteSelectionStatus=") + .append(rule.rewriteSelectionStatus()) + .append(", rewriteSelectionFirstBlocker=") + .append(rule.rewriteSelectionFirstBlocker()) + .append(", rewriteProofStatus=") + .append(rule.rewriteProofStatus()) + .append(", rewriteProofFirstBlocker=") + .append(rule.rewriteProofFirstBlocker()) .append(", firstBlocker=") .append(rule.firstBlocker()) .append(']'); @@ -1126,11 +1247,15 @@ private record OptimizerRuleAggregate( int rewriteSketchReadyCount, int rewriteSketchBlockedCount, String rewriteSketchFirstBlocker, + String rewriteSelectionStatus, + String rewriteSelectionFirstBlocker, + String rewriteProofStatus, + String rewriteProofFirstBlocker, String firstBlocker ) { private static OptimizerRuleAggregate empty(String id) { - return new OptimizerRuleAggregate(id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "none", 0, 0, 0, "none", "none"); + return new OptimizerRuleAggregate(id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "none", 0, 0, 0, "none", "not-required", "no-rewrite-sketches", "not-required", "no-proof-candidates", "none"); } private OptimizerRuleAggregate add( @@ -1150,6 +1275,10 @@ private OptimizerRuleAggregate add( int rewriteSketchReadyCount, int rewriteSketchBlockedCount, String nextRewriteSketchBlocker, + String nextRewriteSelectionStatus, + String nextRewriteSelectionBlocker, + String nextRewriteProofStatus, + String nextRewriteProofBlocker, String nextBlocker ) { String blocker = firstBlocker; @@ -1167,6 +1296,10 @@ private OptimizerRuleAggregate add( && !"none".equals(nextRewriteSketchBlocker)) { sketchBlocker = nextRewriteSketchBlocker; } + String selectionStatus = firstNonDefault(rewriteSelectionStatus, nextRewriteSelectionStatus, "not-required"); + String selectionBlocker = firstNonDefault(rewriteSelectionFirstBlocker, nextRewriteSelectionBlocker, "no-rewrite-sketches"); + String proofStatus = firstNonDefault(rewriteProofStatus, nextRewriteProofStatus, "not-required"); + String proofBlocker = firstNonDefault(rewriteProofFirstBlocker, nextRewriteProofBlocker, "no-proof-candidates"); if ("none".equals(blocker) && nextBlocker != null && !nextBlocker.isBlank() @@ -1191,9 +1324,23 @@ private OptimizerRuleAggregate add( this.rewriteSketchReadyCount + rewriteSketchReadyCount, this.rewriteSketchBlockedCount + rewriteSketchBlockedCount, sketchBlocker, + selectionStatus, + selectionBlocker, + proofStatus, + proofBlocker, blocker ); } + + private static String firstNonDefault(String current, String next, String defaultValue) { + if (current != null && !current.isBlank() && !defaultValue.equals(current) && !"unknown".equals(current)) { + return current; + } + if (next != null && !next.isBlank() && !defaultValue.equals(next) && !"unknown".equals(next)) { + return next; + } + return defaultValue; + } } private record ControlledSourceSwitchingCoverage( diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilitySummary.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilitySummary.java index 89d9406a..7224a3ad 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilitySummary.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilitySummary.java @@ -42,6 +42,10 @@ public record GpuProductionPromotionExplainabilitySummary( String optimizerRewriteSketchConflictFirstBlockers, String optimizerRewriteSelectionStatuses, String optimizerRewriteSelectionFirstBlockers, + String optimizerRewriteProofStatuses, + String optimizerRewriteProofFirstBlockers, + String optimizerRewriteReviewPackageStatuses, + String optimizerRewriteReviewPackageFirstBlockers, int optimizerRuleCount, String optimizerRuleSummary, String optimizerRuleDetails, @@ -122,6 +126,10 @@ public static GpuProductionPromotionExplainabilitySummary notRecorded() { "", "none", "none", + "none", + "none", + "none", + "none", 0, "none", "none", @@ -209,6 +217,10 @@ public static GpuProductionPromotionExplainabilitySummary fromProperties(Propert properties.getProperty("optimizerRewriteSketch.conflict.firstBlockers", ""), properties.getProperty("optimizerRewriteSelection.statuses", "none"), properties.getProperty("optimizerRewriteSelection.firstBlockers", "none"), + properties.getProperty("optimizerRewriteProof.statuses", "none"), + properties.getProperty("optimizerRewriteProof.firstBlockers", "none"), + properties.getProperty("optimizerRewriteReviewPackage.statuses", "none"), + properties.getProperty("optimizerRewriteReviewPackage.firstBlockers", "none"), parsePositiveInt(properties.getProperty("optimizerRule.count", "0")), properties.getProperty("optimizerRule.summary", "none"), properties.getProperty("optimizerRule.details", "none"), @@ -453,7 +465,19 @@ private String optimizerRewriteSketchSummaryText() { || "none".equals(optimizerRewriteSelectionStatuses)) && (optimizerRewriteSelectionFirstBlockers == null || optimizerRewriteSelectionFirstBlockers.isBlank() - || "none".equals(optimizerRewriteSelectionFirstBlockers))) { + || "none".equals(optimizerRewriteSelectionFirstBlockers)) + && (optimizerRewriteProofStatuses == null + || optimizerRewriteProofStatuses.isBlank() + || "none".equals(optimizerRewriteProofStatuses)) + && (optimizerRewriteProofFirstBlockers == null + || optimizerRewriteProofFirstBlockers.isBlank() + || "none".equals(optimizerRewriteProofFirstBlockers)) + && (optimizerRewriteReviewPackageStatuses == null + || optimizerRewriteReviewPackageStatuses.isBlank() + || "none".equals(optimizerRewriteReviewPackageStatuses)) + && (optimizerRewriteReviewPackageFirstBlockers == null + || optimizerRewriteReviewPackageFirstBlockers.isBlank() + || "none".equals(optimizerRewriteReviewPackageFirstBlockers))) { return ""; } StringBuilder builder = new StringBuilder(", optimizerRewriteSketches=ready=") @@ -485,6 +509,31 @@ private String optimizerRewriteSketchSummaryText() { builder.append("/selectionFirstBlockers=").append(optimizerRewriteSelectionFirstBlockers); } builder.append("/selectionApplied=false"); + if (optimizerRewriteProofStatuses != null + && !optimizerRewriteProofStatuses.isBlank() + && !"none".equals(optimizerRewriteProofStatuses)) { + builder.append("/proofStatus=").append(optimizerRewriteProofStatuses); + } + if (optimizerRewriteProofFirstBlockers != null + && !optimizerRewriteProofFirstBlockers.isBlank() + && !"none".equals(optimizerRewriteProofFirstBlockers)) { + builder.append("/proofFirstBlockers=").append(optimizerRewriteProofFirstBlockers); + } + builder.append("/proofAccepted=false") + .append("/runtimeEquivalencePayloadComplete=false") + .append("/rollbackClean=false"); + if (optimizerRewriteReviewPackageStatuses != null + && !optimizerRewriteReviewPackageStatuses.isBlank() + && !"none".equals(optimizerRewriteReviewPackageStatuses)) { + builder.append("/reviewPackageStatus=").append(optimizerRewriteReviewPackageStatuses); + } + if (optimizerRewriteReviewPackageFirstBlockers != null + && !optimizerRewriteReviewPackageFirstBlockers.isBlank() + && !"none".equals(optimizerRewriteReviewPackageFirstBlockers)) { + builder.append("/reviewPackageFirstBlockers=").append(optimizerRewriteReviewPackageFirstBlockers); + } + builder.append("/reviewPackageComplete=false") + .append("/reviewPackageManualReviewOnly=true"); return builder.toString(); } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilitySummaryCli.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilitySummaryCli.java index 9d5a90b9..d7ade97c 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilitySummaryCli.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuProductionPromotionExplainabilitySummaryCli.java @@ -115,6 +115,33 @@ public static String format(GpuProductionPromotionExplainabilitySummary summary) builder.append("optimizerRewriteSelection.mutationAllowed=false\n"); builder.append("optimizerRewriteSelection.selectionApplied=false\n"); builder.append("optimizerRewriteSelection.selectedIrReplacement=false\n"); + builder.append("optimizerRewriteProof.statuses=") + .append(summary.optimizerRewriteProofStatuses()) + .append('\n'); + builder.append("optimizerRewriteProof.firstBlockers=") + .append(summary.optimizerRewriteProofFirstBlockers()) + .append('\n'); + builder.append("optimizerRewriteProof.proofAccepted=false\n"); + builder.append("optimizerRewriteProof.runtimeEquivalencePayload.complete=false\n"); + builder.append("optimizerRewriteProof.rollbackClean=false\n"); + builder.append("optimizerRewriteProof.approvalAccepted=false\n"); + builder.append("optimizerRewriteProof.mutationAllowed=false\n"); + builder.append("optimizerRewriteProof.selectedIrReplacement=false\n"); + builder.append("optimizerRewriteReviewPackage.statuses=") + .append(summary.optimizerRewriteReviewPackageStatuses()) + .append('\n'); + builder.append("optimizerRewriteReviewPackage.firstBlockers=") + .append(summary.optimizerRewriteReviewPackageFirstBlockers()) + .append('\n'); + builder.append("optimizerRewriteReviewPackage.complete=false\n"); + builder.append("optimizerRewriteReviewPackage.proofAccepted=false\n"); + builder.append("optimizerRewriteReviewPackage.runtimeEquivalencePayload.complete=false\n"); + builder.append("optimizerRewriteReviewPackage.rollbackClean=false\n"); + builder.append("optimizerRewriteReviewPackage.approvalAccepted=false\n"); + builder.append("optimizerRewriteReviewPackage.mutationAllowed=false\n"); + builder.append("optimizerRewriteReviewPackage.selectionApplied=false\n"); + builder.append("optimizerRewriteReviewPackage.selectedIrReplacement=false\n"); + builder.append("optimizerRewriteReviewPackage.manualReviewOnly=true\n"); builder.append("optimizerRule.count=").append(summary.optimizerRuleCount()).append('\n'); builder.append("optimizerRule.summary=").append(summary.optimizerRuleSummary()).append('\n'); builder.append("optimizerRule.details=").append(summary.optimizerRuleDetails()).append('\n'); diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholePass.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholePass.java index 24253a56..cea893cd 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholePass.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholePass.java @@ -34,7 +34,7 @@ public GpuRuntimeIrOptimizationReport run(GpuRuntimeIrOptimizationRequest reques GpuRuntimeIrPeepholeRuleRegistry.Analysis analysis = artifact .map(value -> ruleRegistry.analyze(request, value)) .orElseGet(() -> new GpuRuntimeIrPeepholeRuleRegistry.Analysis(List.of(), List.of(), false)); - Map fields = diagnosticFields(artifact, request, analysis); + Map fields = diagnosticFields(artifact, request, analysis, identity); GpuRuntimeIrOptimizationPassReport passReport = GpuRuntimeIrOptimizationPassReport.skipped( VERSION, identity, @@ -65,7 +65,8 @@ public boolean requiresFastMath() { private Map diagnosticFields( Optional artifact, GpuRuntimeIrOptimizationRequest request, - GpuRuntimeIrPeepholeRuleRegistry.Analysis analysis + GpuRuntimeIrPeepholeRuleRegistry.Analysis analysis, + String originalIrIdentity ) { LinkedHashMap fields = new LinkedHashMap<>(); fields.put("optimizerFamily", "peephole"); @@ -101,15 +102,21 @@ private Map diagnosticFields( List rewriteSketchConflicts = rewriteSketchConflicts(rewriteSketches); appendRewriteSketchFields(fields, "rewriteSketch", rewriteSketches); appendRewriteSketchConflictFields(fields, "rewriteSketch.conflict", rewriteSketchConflicts); - fields.putAll(GpuRuntimeIrPeepholeRewriteSelectionReadiness - .from(rewriteSketches, rewriteSketchConflicts) - .fields("rewriteSelection")); + GpuRuntimeIrPeepholeRewriteSelectionReadiness selectionReadiness = + GpuRuntimeIrPeepholeRewriteSelectionReadiness.from(rewriteSketches, rewriteSketchConflicts); + fields.putAll(selectionReadiness.fields("rewriteSelection")); + GpuRuntimeIrPeepholeRewriteProofReadiness proofReadiness = + GpuRuntimeIrPeepholeRewriteProofReadiness.from(selectionReadiness, originalIrIdentity); + fields.putAll(proofReadiness.fields("rewriteProof")); + fields.putAll(GpuRuntimeIrPeepholeRewriteReviewPackage + .from(selectionReadiness, proofReadiness) + .fields("rewriteReviewPackage")); fields.put("rule.mix.candidate.count", Integer.toString(candidateCount(analysis, "mix"))); fields.put("rule.madFma.candidate.count", Integer.toString(candidateCount(analysis, "madFma"))); fields.put("rule.clamp.candidate.count", Integer.toString(candidateCount(analysis, "clamp"))); fields.put("rule.step.candidate.count", Integer.toString(candidateCount(analysis, "step"))); fields.put("rule.dot.candidate.count", Integer.toString(candidateCount(analysis, "dot"))); - appendRuleFields(fields, analysis); + appendRuleFields(fields, analysis, originalIrIdentity); fields.put("rule.execution.count", Integer.toString(analysis.executionReports().size())); fields.put("rule.execution.failedContinued.count", Long.toString(analysis.executionReports().stream() .filter(report -> report.outcome() == net.sixik.ga_utils.javatogpu.extension.GpuExtensionExecutionOutcome.FAILED_CONTINUED) @@ -139,7 +146,8 @@ private static int candidateCount(GpuRuntimeIrPeepholeRuleRegistry.Analysis anal private void appendRuleFields( LinkedHashMap fields, - GpuRuntimeIrPeepholeRuleRegistry.Analysis analysis + GpuRuntimeIrPeepholeRuleRegistry.Analysis analysis, + String originalIrIdentity ) { java.util.LinkedHashMap> reportsByRule = new java.util.LinkedHashMap<>(); for (GpuRuntimeIrPeepholeRuleReport report : analysis.ruleReports()) { @@ -174,7 +182,19 @@ private void appendRuleFields( ? executionProofStatus(rule, analysis) : reports.get(0).proofStatus()); appendReplacementPlanFields(fields, prefix, reports); - appendRewriteSketchFields(fields, prefix + ".rewriteSketch", rewriteSketches(reports)); + List ruleSketches = rewriteSketches(reports); + List ruleConflicts = rewriteSketchConflicts(ruleSketches); + appendRewriteSketchFields(fields, prefix + ".rewriteSketch", ruleSketches); + appendRewriteSketchConflictFields(fields, prefix + ".rewriteSketch.conflict", ruleConflicts); + GpuRuntimeIrPeepholeRewriteSelectionReadiness ruleSelectionReadiness = + GpuRuntimeIrPeepholeRewriteSelectionReadiness.from(ruleSketches, ruleConflicts); + fields.putAll(ruleSelectionReadiness.fields(prefix + ".rewriteSelection")); + GpuRuntimeIrPeepholeRewriteProofReadiness ruleProofReadiness = + GpuRuntimeIrPeepholeRewriteProofReadiness.from(ruleSelectionReadiness, originalIrIdentity); + fields.putAll(ruleProofReadiness.fields(prefix + ".rewriteProof")); + fields.putAll(GpuRuntimeIrPeepholeRewriteReviewPackage + .from(ruleSelectionReadiness, ruleProofReadiness) + .fields(prefix + ".rewriteReviewPackage")); fields.put(prefix + ".firstBlocker", ruleFirstBlocker(fields, prefix, reports.isEmpty())); } } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteProofReadiness.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteProofReadiness.java new file mode 100644 index 00000000..cfdd7486 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteProofReadiness.java @@ -0,0 +1,116 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import java.util.Map; + +/** + * Read-only proof gate for future peephole rewrites. + * + *

This records the runtime-equivalence, rollback, and approval evidence that would be required before a structural + * rewrite could ever be selected. It does not build transformed IR or mark proof as accepted.

+ */ +public record GpuRuntimeIrPeepholeRewriteProofReadiness( + String optimizerFamily, + int readySketchCount, + String status, + String firstBlocker, + boolean proofRequired, + boolean proofAccepted, + boolean runtimeEquivalenceRequired, + boolean runtimeEquivalencePayloadPresent, + boolean runtimeEquivalencePayloadComplete, + boolean rollbackRequired, + boolean rollbackEvidencePresent, + boolean rollbackClean, + boolean approvalRequired, + boolean approvalAccepted, + boolean mutationAllowed, + boolean selectedIrReplacement, + String originalIrIdentity, + String transformedIrIdentity +) { + + public GpuRuntimeIrPeepholeRewriteProofReadiness { + optimizerFamily = normalize(optimizerFamily, "peephole"); + readySketchCount = Math.max(0, readySketchCount); + status = normalize(status, readySketchCount == 0 ? "not-required" : "blocked"); + firstBlocker = normalize(firstBlocker, readySketchCount == 0 ? "no-proof-candidates" : "runtime-equivalence-payload-missing"); + originalIrIdentity = normalize(originalIrIdentity, "irgpu:missing"); + transformedIrIdentity = normalize(transformedIrIdentity, "not-built"); + } + + public static GpuRuntimeIrPeepholeRewriteProofReadiness from( + GpuRuntimeIrPeepholeRewriteSelectionReadiness selectionReadiness, + String originalIrIdentity + ) { + if (selectionReadiness == null || selectionReadiness.readySketchCount() == 0) { + return new GpuRuntimeIrPeepholeRewriteProofReadiness( + "peephole", + 0, + "not-required", + selectionReadiness == null ? "no-proof-candidates" : selectionReadiness.firstBlocker(), + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + originalIrIdentity, + "not-built" + ); + } + return new GpuRuntimeIrPeepholeRewriteProofReadiness( + "peephole", + selectionReadiness.readySketchCount(), + "blocked", + selectionReadiness.firstBlocker(), + true, + false, + true, + false, + false, + true, + false, + false, + true, + false, + false, + false, + originalIrIdentity, + "not-built" + ); + } + + public Map fields(String prefix) { + String normalizedPrefix = normalize(prefix, "rewriteProof"); + return Map.ofEntries( + Map.entry(normalizedPrefix + ".optimizerFamily", optimizerFamily), + Map.entry(normalizedPrefix + ".sketch.ready.count", Integer.toString(readySketchCount)), + Map.entry(normalizedPrefix + ".status", status), + Map.entry(normalizedPrefix + ".firstBlocker", firstBlocker), + Map.entry(normalizedPrefix + ".proofRequired", Boolean.toString(proofRequired)), + Map.entry(normalizedPrefix + ".proofAccepted", Boolean.toString(proofAccepted)), + Map.entry(normalizedPrefix + ".runtimeEquivalenceRequired", Boolean.toString(runtimeEquivalenceRequired)), + Map.entry(normalizedPrefix + ".runtimeEquivalencePayload.present", Boolean.toString(runtimeEquivalencePayloadPresent)), + Map.entry(normalizedPrefix + ".runtimeEquivalencePayload.complete", Boolean.toString(runtimeEquivalencePayloadComplete)), + Map.entry(normalizedPrefix + ".rollbackRequired", Boolean.toString(rollbackRequired)), + Map.entry(normalizedPrefix + ".rollbackEvidence.present", Boolean.toString(rollbackEvidencePresent)), + Map.entry(normalizedPrefix + ".rollbackClean", Boolean.toString(rollbackClean)), + Map.entry(normalizedPrefix + ".approvalRequired", Boolean.toString(approvalRequired)), + Map.entry(normalizedPrefix + ".approvalAccepted", Boolean.toString(approvalAccepted)), + Map.entry(normalizedPrefix + ".mutationAllowed", Boolean.toString(mutationAllowed)), + Map.entry(normalizedPrefix + ".selectedIrReplacement", Boolean.toString(selectedIrReplacement)), + Map.entry(normalizedPrefix + ".originalIrIdentity", originalIrIdentity), + Map.entry(normalizedPrefix + ".transformedIrIdentity", transformedIrIdentity) + ); + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteReviewPackage.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteReviewPackage.java new file mode 100644 index 00000000..e3659245 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteReviewPackage.java @@ -0,0 +1,147 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import java.util.Map; + +/** + * Read-only review package preflight for future peephole rewrites. + * + *

The package intentionally remains incomplete until runtime-equivalence payload, rollback evidence, approval, and + * proof acceptance exist. It never enables mutation or selects transformed IR.

+ */ +public record GpuRuntimeIrPeepholeRewriteReviewPackage( + String optimizerFamily, + int readySketchCount, + int conflictCount, + String status, + String firstBlocker, + boolean required, + boolean complete, + boolean proofAccepted, + boolean runtimeEquivalencePayloadPresent, + boolean runtimeEquivalencePayloadComplete, + boolean rollbackEvidencePresent, + boolean rollbackClean, + boolean approvalAccepted, + boolean mutationAllowed, + boolean selectionApplied, + boolean selectedIrReplacement, + boolean manualReviewOnly, + String originalIrIdentity, + String transformedIrIdentity +) { + + public GpuRuntimeIrPeepholeRewriteReviewPackage { + optimizerFamily = normalize(optimizerFamily, "peephole"); + readySketchCount = Math.max(0, readySketchCount); + conflictCount = Math.max(0, conflictCount); + status = normalize(status, readySketchCount == 0 ? "not-required" : "blocked"); + firstBlocker = normalize(firstBlocker, readySketchCount == 0 ? "no-review-candidates" : "runtime-equivalence-payload-missing"); + originalIrIdentity = normalize(originalIrIdentity, "irgpu:missing"); + transformedIrIdentity = normalize(transformedIrIdentity, "not-built"); + } + + public static GpuRuntimeIrPeepholeRewriteReviewPackage from( + GpuRuntimeIrPeepholeRewriteSelectionReadiness selectionReadiness, + GpuRuntimeIrPeepholeRewriteProofReadiness proofReadiness + ) { + int readySketches = selectionReadiness == null ? 0 : selectionReadiness.readySketchCount(); + int conflicts = selectionReadiness == null ? 0 : selectionReadiness.conflictCount(); + boolean required = readySketches > 0; + boolean complete = required + && conflicts == 0 + && proofReadiness != null + && proofReadiness.proofAccepted() + && proofReadiness.runtimeEquivalencePayloadComplete() + && proofReadiness.rollbackEvidencePresent() + && proofReadiness.rollbackClean() + && proofReadiness.approvalAccepted() + && !proofReadiness.mutationAllowed() + && !proofReadiness.selectedIrReplacement(); + return new GpuRuntimeIrPeepholeRewriteReviewPackage( + proofReadiness == null ? "peephole" : proofReadiness.optimizerFamily(), + readySketches, + conflicts, + !required ? "not-required" : complete ? "ready-for-manual-review" : "blocked", + firstBlocker(selectionReadiness, proofReadiness, required, complete), + required, + complete, + proofReadiness != null && proofReadiness.proofAccepted(), + proofReadiness != null && proofReadiness.runtimeEquivalencePayloadPresent(), + proofReadiness != null && proofReadiness.runtimeEquivalencePayloadComplete(), + proofReadiness != null && proofReadiness.rollbackEvidencePresent(), + proofReadiness != null && proofReadiness.rollbackClean(), + proofReadiness != null && proofReadiness.approvalAccepted(), + false, + false, + false, + true, + proofReadiness == null ? "irgpu:missing" : proofReadiness.originalIrIdentity(), + proofReadiness == null ? "not-built" : proofReadiness.transformedIrIdentity() + ); + } + + public Map fields(String prefix) { + String normalizedPrefix = normalize(prefix, "rewriteReviewPackage"); + return Map.ofEntries( + Map.entry(normalizedPrefix + ".optimizerFamily", optimizerFamily), + Map.entry(normalizedPrefix + ".sketch.ready.count", Integer.toString(readySketchCount)), + Map.entry(normalizedPrefix + ".conflict.count", Integer.toString(conflictCount)), + Map.entry(normalizedPrefix + ".status", status), + Map.entry(normalizedPrefix + ".firstBlocker", firstBlocker), + Map.entry(normalizedPrefix + ".required", Boolean.toString(required)), + Map.entry(normalizedPrefix + ".complete", Boolean.toString(complete)), + Map.entry(normalizedPrefix + ".proofAccepted", Boolean.toString(proofAccepted)), + Map.entry(normalizedPrefix + ".runtimeEquivalencePayload.present", Boolean.toString(runtimeEquivalencePayloadPresent)), + Map.entry(normalizedPrefix + ".runtimeEquivalencePayload.complete", Boolean.toString(runtimeEquivalencePayloadComplete)), + Map.entry(normalizedPrefix + ".rollbackEvidence.present", Boolean.toString(rollbackEvidencePresent)), + Map.entry(normalizedPrefix + ".rollbackClean", Boolean.toString(rollbackClean)), + Map.entry(normalizedPrefix + ".approvalAccepted", Boolean.toString(approvalAccepted)), + Map.entry(normalizedPrefix + ".mutationAllowed", Boolean.toString(mutationAllowed)), + Map.entry(normalizedPrefix + ".selectionApplied", Boolean.toString(selectionApplied)), + Map.entry(normalizedPrefix + ".selectedIrReplacement", Boolean.toString(selectedIrReplacement)), + Map.entry(normalizedPrefix + ".manualReviewOnly", Boolean.toString(manualReviewOnly)), + Map.entry(normalizedPrefix + ".originalIrIdentity", originalIrIdentity), + Map.entry(normalizedPrefix + ".transformedIrIdentity", transformedIrIdentity) + ); + } + + private static String firstBlocker( + GpuRuntimeIrPeepholeRewriteSelectionReadiness selectionReadiness, + GpuRuntimeIrPeepholeRewriteProofReadiness proofReadiness, + boolean required, + boolean complete + ) { + if (!required) { + return selectionReadiness == null ? "no-review-candidates" : selectionReadiness.firstBlocker(); + } + if (complete) { + return "none"; + } + if (selectionReadiness != null && selectionReadiness.conflictCount() > 0) { + return "rewrite-sketch-conflict-resolution-required"; + } + if (proofReadiness == null || !proofReadiness.runtimeEquivalencePayloadPresent()) { + return "runtime-equivalence-payload-missing"; + } + if (!proofReadiness.runtimeEquivalencePayloadComplete()) { + return "runtime-equivalence-payload-incomplete"; + } + if (!proofReadiness.rollbackEvidencePresent()) { + return "rollback-evidence-missing"; + } + if (!proofReadiness.rollbackClean()) { + return "rollback-not-clean"; + } + if (!proofReadiness.approvalAccepted()) { + return "approval-not-accepted"; + } + if (!proofReadiness.proofAccepted()) { + return "proof-not-accepted"; + } + return "manual-review-required"; + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeOptimizerDriftArtifact.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeOptimizerDriftArtifact.java index 0cea4afe..dd6453cb 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeOptimizerDriftArtifact.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeOptimizerDriftArtifact.java @@ -38,6 +38,10 @@ public record GpuRuntimeOptimizerDriftArtifact( String rewriteSketchConflictFirstBlocker, String rewriteSelectionStatus, String rewriteSelectionFirstBlocker, + String rewriteProofStatus, + String rewriteProofFirstBlocker, + String rewriteReviewPackageStatus, + String rewriteReviewPackageFirstBlocker, int optimizerRuleCount, String optimizerRuleSummary, String optimizerRuleDetailsProperties, @@ -82,6 +86,10 @@ public static GpuRuntimeOptimizerDriftArtifact from(GpuRuntimeCompileArtifactSna "none", "not-required", "no-rewrite-sketches", + "not-required", + "no-proof-candidates", + "not-required", + "no-review-candidates", 0, "none", "", @@ -106,6 +114,8 @@ public static GpuRuntimeOptimizerDriftArtifact from(GpuRuntimeCompileArtifactSna String rewriteSketchFirstBlocker = rewriteSketchFirstBlocker(report); int rewriteSketchConflictCount = rewriteSketchConflictCount(report); String rewriteSketchConflictFirstBlocker = rewriteSketchConflictFirstBlocker(report); + String rewriteProofStatus = rewriteProofStatus(report, rewriteSketchReadyCount); + String rewriteProofFirstBlocker = rewriteProofFirstBlocker(report); return new GpuRuntimeOptimizerDriftArtifact( optimizationPassCount(report), count(report, GpuRuntimeIrOptimizationOutcome.APPLIED), @@ -145,6 +155,15 @@ public static GpuRuntimeOptimizerDriftArtifact from(GpuRuntimeCompileArtifactSna rewriteSketchFirstBlocker, rewriteSketchConflictCount ), + rewriteProofStatus, + rewriteProofFirstBlocker, + rewriteReviewPackageStatus(report, rewriteSketchReadyCount), + rewriteReviewPackageFirstBlocker( + report, + rewriteSketchReadyCount, + rewriteSketchConflictCount, + rewriteProofFirstBlocker + ), optimizerRules.size(), formatOptimizerRuleSummary(optimizerRules), formatOptimizerRuleProperties(optimizerRules), @@ -215,6 +234,31 @@ public String toPropertiesText() { builder.append("rewriteSelection.mutationAllowed=false\n"); builder.append("rewriteSelection.selectionApplied=false\n"); builder.append("rewriteSelection.selectedIrReplacement=false\n"); + builder.append("rewriteProof.status=").append(rewriteProofStatus).append('\n'); + builder.append("rewriteProof.firstBlocker=").append(rewriteProofFirstBlocker).append('\n'); + builder.append("rewriteProof.proofAccepted=false\n"); + builder.append("rewriteProof.runtimeEquivalencePayload.present=false\n"); + builder.append("rewriteProof.runtimeEquivalencePayload.complete=false\n"); + builder.append("rewriteProof.rollbackEvidence.present=false\n"); + builder.append("rewriteProof.rollbackClean=false\n"); + builder.append("rewriteProof.approvalAccepted=false\n"); + builder.append("rewriteProof.mutationAllowed=false\n"); + builder.append("rewriteProof.selectedIrReplacement=false\n"); + builder.append("rewriteReviewPackage.status=").append(rewriteReviewPackageStatus).append('\n'); + builder.append("rewriteReviewPackage.firstBlocker=").append(rewriteReviewPackageFirstBlocker).append('\n'); + builder.append("rewriteReviewPackage.required=").append(rewriteSketchReadyCount > 0).append('\n'); + builder.append("rewriteReviewPackage.complete=false\n"); + builder.append("rewriteReviewPackage.conflict.count=").append(rewriteSketchConflictCount).append('\n'); + builder.append("rewriteReviewPackage.proofAccepted=false\n"); + builder.append("rewriteReviewPackage.runtimeEquivalencePayload.present=false\n"); + builder.append("rewriteReviewPackage.runtimeEquivalencePayload.complete=false\n"); + builder.append("rewriteReviewPackage.rollbackEvidence.present=false\n"); + builder.append("rewriteReviewPackage.rollbackClean=false\n"); + builder.append("rewriteReviewPackage.approvalAccepted=false\n"); + builder.append("rewriteReviewPackage.mutationAllowed=false\n"); + builder.append("rewriteReviewPackage.selectionApplied=false\n"); + builder.append("rewriteReviewPackage.selectedIrReplacement=false\n"); + builder.append("rewriteReviewPackage.manualReviewOnly=true\n"); builder.append("optimizerRule.count=").append(optimizerRuleCount).append('\n'); builder.append("optimizerRule.summary=").append(optimizerRuleSummary).append('\n'); if (optimizerRuleDetailsProperties != null && !optimizerRuleDetailsProperties.isBlank()) { @@ -487,6 +531,100 @@ private static String derivedRewriteSelectionFirstBlocker( return rewriteSketchReadyCount > 0 ? "rewrite-builder-not-implemented" : "no-rewrite-sketches"; } + private static String rewriteProofStatus(GpuRuntimeIrOptimizationReport report, int rewriteSketchReadyCount) { + String fallback = rewriteSketchReadyCount == 0 ? "not-required" : "blocked"; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + String status = passReport.proofArtifact().fields().getOrDefault("rewriteProof.status", ""); + if ("blocked".equals(status)) { + return status; + } + if (!status.isBlank()) { + fallback = status; + } + } + } + return fallback; + } + + private static String rewriteProofFirstBlocker(GpuRuntimeIrOptimizationReport report) { + String fallback = "no-proof-candidates"; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + Map fields = passReport.proofArtifact().fields(); + String blocker = fields.getOrDefault("rewriteProof.firstBlocker", ""); + if (!blocker.isBlank() && !"none".equals(blocker) && !"no-proof-candidates".equals(blocker)) { + return blocker; + } + if (!blocker.isBlank()) { + fallback = blocker; + } + } + } + return fallback; + } + + private static String rewriteReviewPackageStatus(GpuRuntimeIrOptimizationReport report, int rewriteSketchReadyCount) { + String fallback = rewriteSketchReadyCount == 0 ? "not-required" : "blocked"; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + String status = passReport.proofArtifact().fields().getOrDefault("rewriteReviewPackage.status", ""); + if ("blocked".equals(status)) { + return status; + } + if (!status.isBlank()) { + fallback = status; + } + } + } + return fallback; + } + + private static String rewriteReviewPackageFirstBlocker( + GpuRuntimeIrOptimizationReport report, + int rewriteSketchReadyCount, + int rewriteSketchConflictCount, + String rewriteProofFirstBlocker + ) { + String fallback = derivedRewriteReviewPackageFirstBlocker( + rewriteSketchReadyCount, + rewriteSketchConflictCount, + rewriteProofFirstBlocker + ); + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + String blocker = passReport.proofArtifact().fields().getOrDefault("rewriteReviewPackage.firstBlocker", ""); + if (!blocker.isBlank() && !"none".equals(blocker) && !"no-review-candidates".equals(blocker)) { + return blocker; + } + if (!blocker.isBlank()) { + fallback = blocker; + } + } + } + return fallback; + } + + private static String derivedRewriteReviewPackageFirstBlocker( + int rewriteSketchReadyCount, + int rewriteSketchConflictCount, + String rewriteProofFirstBlocker + ) { + if (rewriteSketchReadyCount == 0) { + return "no-review-candidates"; + } + if (rewriteSketchConflictCount > 0) { + return "rewrite-sketch-conflict-resolution-required"; + } + if (rewriteProofFirstBlocker == null + || rewriteProofFirstBlocker.isBlank() + || "none".equals(rewriteProofFirstBlocker) + || "no-proof-candidates".equals(rewriteProofFirstBlocker)) { + return "runtime-equivalence-payload-missing"; + } + return rewriteProofFirstBlocker; + } + private static String firstRuleBlocker(Map fields, String suffix) { for (Map.Entry entry : fields.entrySet()) { String blocker = entry.getValue(); @@ -564,6 +702,18 @@ private static String formatOptimizerRuleSummary(Map fields, String prefix) { && !"none".equals(nextSketchBlocker)) { sketchBlocker = nextSketchBlocker; } + String selectionStatus = firstNonDefault( + rewriteSelectionStatus, + fields.getOrDefault(prefix + ".rewriteSelection.status", "not-required"), + "not-required" + ); + String selectionBlocker = firstNonDefault( + rewriteSelectionFirstBlocker, + fields.getOrDefault(prefix + ".rewriteSelection.firstBlocker", "no-rewrite-sketches"), + "no-rewrite-sketches" + ); + String proofStatus = firstNonDefault( + rewriteProofStatus, + fields.getOrDefault(prefix + ".rewriteProof.status", "not-required"), + "not-required" + ); + String proofBlocker = firstNonDefault( + rewriteProofFirstBlocker, + fields.getOrDefault(prefix + ".rewriteProof.firstBlocker", "no-proof-candidates"), + "no-proof-candidates" + ); + String reviewPackageStatus = firstNonDefault( + rewriteReviewPackageStatus, + fields.getOrDefault(prefix + ".rewriteReviewPackage.status", "not-required"), + "not-required" + ); + String reviewPackageBlocker = firstNonDefault( + rewriteReviewPackageFirstBlocker, + fields.getOrDefault(prefix + ".rewriteReviewPackage.firstBlocker", "no-review-candidates"), + "no-review-candidates" + ); String nextBlocker = fields.getOrDefault( prefix + ".firstBlocker", fields.getOrDefault(prefix + ".replacementPlan.firstBlocker", "none") @@ -834,10 +1040,26 @@ private OptimizerRuleEvidence add(Map fields, String prefix) { rewriteSketchReadyCount + parseInt(fields.get(prefix + ".rewriteSketch.ready.count")), rewriteSketchBlockedCount + parseInt(fields.get(prefix + ".rewriteSketch.blocked.count")), sketchBlocker, + selectionStatus, + selectionBlocker, + proofStatus, + proofBlocker, + reviewPackageStatus, + reviewPackageBlocker, blocker ); } + private static String firstNonDefault(String current, String next, String defaultValue) { + if (current != null && !current.isBlank() && !defaultValue.equals(current) && !"unknown".equals(current)) { + return current; + } + if (next != null && !next.isBlank() && !defaultValue.equals(next) && !"unknown".equals(next)) { + return next; + } + return defaultValue; + } + private static String firstKnown(String current, String next) { if (current != null && !current.isBlank() && !"unknown".equals(current)) { return current; diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReporter.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReporter.java index 532380cb..07269d7f 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReporter.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReporter.java @@ -1103,6 +1103,14 @@ private static void appendBackendSourcePromotionWorkloadKernelSummary( .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.rewriteSelection.status", "not-required"))) .append("`, rewriteSelectionFirstBlocker=`") .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.rewriteSelection.firstBlocker", "no-rewrite-sketches"))) + .append("`, rewriteProof=`") + .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.rewriteProof.status", "not-required"))) + .append("`, rewriteProofFirstBlocker=`") + .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.rewriteProof.firstBlocker", "no-proof-candidates"))) + .append("`, rewriteReviewPackage=`") + .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.rewriteReviewPackage.status", "not-required"))) + .append("`, rewriteReviewPackageFirstBlocker=`") + .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.rewriteReviewPackage.firstBlocker", "no-review-candidates"))) .append("`, optimizerRules=`") .append(sanitizeInline(properties.getProperty(prefix + "runtimeOptimizerDrift.optimizerRule.count", "0"))) .append("`, optimizerRuleDetails=`") @@ -1484,6 +1492,12 @@ private static String formatI3ReadinessWorkloadSummary(java.util.Properties gate builder.append(prefix).append("optimizerDriftRewriteSelectionStatus=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.rewriteSelection.status", "not-required")).append('\n'); builder.append(prefix).append("optimizerDriftRewriteSelectionFirstBlocker=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.rewriteSelection.firstBlocker", "no-rewrite-sketches")).append('\n'); builder.append(prefix).append("optimizerDriftRewriteSelectionApplied=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.rewriteSelection.selectionApplied", "false")).append('\n'); + builder.append(prefix).append("optimizerDriftRewriteProofStatus=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.rewriteProof.status", "not-required")).append('\n'); + builder.append(prefix).append("optimizerDriftRewriteProofFirstBlocker=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.rewriteProof.firstBlocker", "no-proof-candidates")).append('\n'); + builder.append(prefix).append("optimizerDriftRewriteProofAccepted=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.rewriteProof.proofAccepted", "false")).append('\n'); + builder.append(prefix).append("optimizerDriftRewriteReviewPackageStatus=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.rewriteReviewPackage.status", "not-required")).append('\n'); + builder.append(prefix).append("optimizerDriftRewriteReviewPackageFirstBlocker=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.rewriteReviewPackage.firstBlocker", "no-review-candidates")).append('\n'); + builder.append(prefix).append("optimizerDriftRewriteReviewPackageComplete=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.rewriteReviewPackage.complete", "false")).append('\n'); builder.append(prefix).append("optimizerDriftRewriteBuilderImplemented=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.rewriteSketch.rewriteBuilderImplemented", "false")).append('\n'); builder.append(prefix).append("optimizerDriftSelectedIrReplacement=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.rewriteSketch.selectedIrReplacement", "false")).append('\n'); builder.append(prefix).append("optimizerDriftRuleCount=").append(gate.getProperty(prefix + "runtimeOptimizerDrift.optimizerRule.count", "0")).append('\n'); @@ -1555,6 +1569,10 @@ private static String kernelOptimizerRuleDetails(Properties properties, String k .append(properties.getProperty(prefix + "rewriteSketch.blocked.count", "0")) .append(", rewriteSketchFirstBlocker=") .append(properties.getProperty(prefix + "rewriteSketch.firstBlocker", "none")) + .append(", rewriteReviewPackageStatus=") + .append(properties.getProperty(prefix + "rewriteReviewPackage.status", "not-required")) + .append(", rewriteReviewPackageFirstBlocker=") + .append(properties.getProperty(prefix + "rewriteReviewPackage.firstBlocker", "no-review-candidates")) .append(", firstBlocker=") .append(properties.getProperty( prefix + "firstBlocker", diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummaryTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummaryTest.java index a97aba7d..c89650e0 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummaryTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadSummaryTest.java @@ -143,7 +143,7 @@ void summarizesIndexedSourceSwitchingEvidenceForHistoryAndReports() { assertTrue(summary.historyStatus().contains("kernelCount=1")); assertTrue(summary.historyStatus().contains("extensionParticipation=recorded/3executions/failedContinued=1/failedClosed=0")); assertTrue(summary.historyStatus().contains("sourceSwitching=reject-production-irgpu-source/operatorAccepted=true")); - assertTrue(summary.historyStatus().contains("proof=2/acceptedProof=1/blockingProof=1/replacementPlanComplete=3/replacementPlanPartial=1/replacementPlanFirstBlocker=multiply-operands-incomplete/replacementPlanValidationValid=3/replacementPlanValidationTotal=4/replacementPlanValidationInvalid=1/replacementPlanValidationFirstBlocker=replacement-plan-root-missing/rewriteSketchReady=3/rewriteSketchTotal=4/rewriteSketchBlocked=1/rewriteSketchFirstBlocker=replacement-plan-root-missing/rewriteSketchConflicts=1/rewriteSketchConflictFirstBlocker=rewrite-sketch-covered-node-overlap/rewriteSketchConflictResolutionImplemented=false/rewriteSketchSelectionApplied=false/rewriteSelectionStatus=not-required/rewriteSelectionFirstBlocker=no-rewrite-sketches/rewriteSelectionApplied=false/rewriteBuilderImplemented=false/selectedIrReplacement=false/optimizerRules=0/optimizerRuleDetails=none/optimizerFamilies=2/promotionReadyFamilies=1")); + assertTrue(summary.historyStatus().contains("proof=2/acceptedProof=1/blockingProof=1/replacementPlanComplete=3/replacementPlanPartial=1/replacementPlanFirstBlocker=multiply-operands-incomplete/replacementPlanValidationValid=3/replacementPlanValidationTotal=4/replacementPlanValidationInvalid=1/replacementPlanValidationFirstBlocker=replacement-plan-root-missing/rewriteSketchReady=3/rewriteSketchTotal=4/rewriteSketchBlocked=1/rewriteSketchFirstBlocker=replacement-plan-root-missing/rewriteSketchConflicts=1/rewriteSketchConflictFirstBlocker=rewrite-sketch-covered-node-overlap/rewriteSketchConflictResolutionImplemented=false/rewriteSketchSelectionApplied=false/rewriteSelectionStatus=not-required/rewriteSelectionFirstBlocker=no-rewrite-sketches/rewriteSelectionApplied=false/rewriteProofStatus=not-required/rewriteProofFirstBlocker=no-proof-candidates/rewriteProofAccepted=false/rewriteBuilderImplemented=false/selectedIrReplacement=false/rewriteReviewPackageStatus=not-required/rewriteReviewPackageFirstBlocker=no-review-candidates/rewriteReviewPackageComplete=false/optimizerRules=0/optimizerRuleDetails=none/optimizerFamilies=2/promotionReadyFamilies=1")); assertTrue(summary.historyStatus().contains("families=source-parity=1")); } diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java index bd56982d..b27d2fd0 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java @@ -2025,6 +2025,12 @@ void optimizerDriftArtifactCapturesReadOnlyReplacementPlanTelemetry() { Map.entry("rewriteSketch.conflict.firstBlocker", "rewrite-sketch-covered-node-overlap"), Map.entry("rewriteSelection.status", "blocked"), Map.entry("rewriteSelection.firstBlocker", "rewrite-sketch-conflict-resolution-required"), + Map.entry("rewriteProof.status", "blocked"), + Map.entry("rewriteProof.firstBlocker", "rewrite-sketch-conflict-resolution-required"), + Map.entry("rewriteReviewPackage.status", "blocked"), + Map.entry("rewriteReviewPackage.firstBlocker", "rewrite-sketch-conflict-resolution-required"), + Map.entry("rewriteReviewPackage.complete", "false"), + Map.entry("rewriteReviewPackage.selectedIrReplacement", "false"), Map.entry("rule.count", "2"), Map.entry("rule.0.id", "madFma"), Map.entry("rule.0.version", "peephole-rule:mad-fma-v1"), @@ -2049,6 +2055,14 @@ void optimizerDriftArtifactCapturesReadOnlyReplacementPlanTelemetry() { Map.entry("rule.0.rewriteSketch.ready.count", "2"), Map.entry("rule.0.rewriteSketch.blocked.count", "1"), Map.entry("rule.0.rewriteSketch.firstBlocker", "replacement-plan-root-missing"), + Map.entry("rule.0.rewriteSelection.status", "blocked"), + Map.entry("rule.0.rewriteSelection.firstBlocker", "replacement-plan-root-missing"), + Map.entry("rule.0.rewriteProof.status", "blocked"), + Map.entry("rule.0.rewriteProof.firstBlocker", "runtime-equivalence-payload-missing"), + Map.entry("rule.0.rewriteReviewPackage.status", "blocked"), + Map.entry("rule.0.rewriteReviewPackage.firstBlocker", "runtime-equivalence-payload-missing"), + Map.entry("rule.0.rewriteReviewPackage.complete", "false"), + Map.entry("rule.0.rewriteReviewPackage.selectedIrReplacement", "false"), Map.entry("rule.0.firstBlocker", "multiply-operands-incomplete"), Map.entry("rule.1.id", "clamp"), Map.entry("rule.1.version", "peephole-rule:clamp-v1"), @@ -2073,6 +2087,14 @@ void optimizerDriftArtifactCapturesReadOnlyReplacementPlanTelemetry() { Map.entry("rule.1.rewriteSketch.ready.count", "3"), Map.entry("rule.1.rewriteSketch.blocked.count", "0"), Map.entry("rule.1.rewriteSketch.firstBlocker", "none"), + Map.entry("rule.1.rewriteSelection.status", "blocked"), + Map.entry("rule.1.rewriteSelection.firstBlocker", "rewrite-builder-not-implemented"), + Map.entry("rule.1.rewriteProof.status", "blocked"), + Map.entry("rule.1.rewriteProof.firstBlocker", "runtime-equivalence-payload-missing"), + Map.entry("rule.1.rewriteReviewPackage.status", "blocked"), + Map.entry("rule.1.rewriteReviewPackage.firstBlocker", "runtime-equivalence-payload-missing"), + Map.entry("rule.1.rewriteReviewPackage.complete", "false"), + Map.entry("rule.1.rewriteReviewPackage.selectedIrReplacement", "false"), Map.entry("rule.1.firstBlocker", "none") ) )); @@ -2129,6 +2151,20 @@ void optimizerDriftArtifactCapturesReadOnlyReplacementPlanTelemetry() { assertTrue(drift.contains("rewriteSelection.mutationAllowed=false")); assertTrue(drift.contains("rewriteSelection.selectionApplied=false")); assertTrue(drift.contains("rewriteSelection.selectedIrReplacement=false")); + assertTrue(drift.contains("rewriteProof.status=blocked")); + assertTrue(drift.contains("rewriteProof.firstBlocker=rewrite-sketch-conflict-resolution-required")); + assertTrue(drift.contains("rewriteProof.proofAccepted=false")); + assertTrue(drift.contains("rewriteProof.runtimeEquivalencePayload.present=false")); + assertTrue(drift.contains("rewriteProof.runtimeEquivalencePayload.complete=false")); + assertTrue(drift.contains("rewriteProof.rollbackEvidence.present=false")); + assertTrue(drift.contains("rewriteProof.rollbackClean=false")); + assertTrue(drift.contains("rewriteProof.approvalAccepted=false")); + assertTrue(drift.contains("rewriteProof.mutationAllowed=false")); + assertTrue(drift.contains("rewriteProof.selectedIrReplacement=false")); + assertTrue(drift.contains("rewriteReviewPackage.status=blocked")); + assertTrue(drift.contains("rewriteReviewPackage.firstBlocker=rewrite-sketch-conflict-resolution-required")); + assertTrue(drift.contains("rewriteReviewPackage.complete=false")); + assertTrue(drift.contains("rewriteReviewPackage.selectedIrReplacement=false")); assertTrue(drift.contains("optimizerRule.count=2")); assertTrue(drift.contains("optimizerRule.0.id=madFma")); assertTrue(drift.contains("optimizerRule.0.blocked.count=1")); @@ -2139,12 +2175,22 @@ void optimizerDriftArtifactCapturesReadOnlyReplacementPlanTelemetry() { assertTrue(drift.contains("optimizerRule.0.rewriteSketch.ready.count=2")); assertTrue(drift.contains("optimizerRule.0.rewriteSketch.blocked.count=1")); assertTrue(drift.contains("optimizerRule.0.rewriteSketch.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("optimizerRule.0.rewriteSelection.status=blocked")); + assertTrue(drift.contains("optimizerRule.0.rewriteSelection.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("optimizerRule.0.rewriteProof.status=blocked")); + assertTrue(drift.contains("optimizerRule.0.rewriteProof.firstBlocker=runtime-equivalence-payload-missing")); + assertTrue(drift.contains("optimizerRule.0.rewriteProof.proofAccepted=false")); + assertTrue(drift.contains("optimizerRule.0.rewriteProof.selectedIrReplacement=false")); + assertTrue(drift.contains("optimizerRule.0.rewriteReviewPackage.status=blocked")); + assertTrue(drift.contains("optimizerRule.0.rewriteReviewPackage.firstBlocker=runtime-equivalence-payload-missing")); + assertTrue(drift.contains("optimizerRule.0.rewriteReviewPackage.complete=false")); + assertTrue(drift.contains("optimizerRule.0.rewriteReviewPackage.selectedIrReplacement=false")); assertTrue(drift.contains("optimizerRule.0.firstBlocker=multiply-operands-incomplete")); assertTrue(drift.contains("optimizerRule.1.id=clamp")); assertTrue(drift.contains("optimizerRule.1.skipped.count=1")); assertTrue(drift.contains("optimizerRule.summary=madFma[candidates=2, proposals=0, applied=0, skipped=0, blocked=1")); assertTrue(drift.contains("planValidations=3, invalidPlanValidations=1, planValidationFirstBlocker=replacement-plan-root-missing")); - assertTrue(drift.contains("rewriteSketches=3, readySketches=2, blockedSketches=1, rewriteSketchFirstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("rewriteSketches=3, readySketches=2, blockedSketches=1, rewriteSketchFirstBlocker=replacement-plan-root-missing, rewriteSelectionStatus=blocked, rewriteSelectionFirstBlocker=replacement-plan-root-missing, rewriteProofStatus=blocked, rewriteProofFirstBlocker=runtime-equivalence-payload-missing")); assertTrue(drift.contains("optimizerFamily.count=1")); assertTrue(drift.contains("optimizerFamily.summary=peephole[passes=1, acceptedProof=0, blockingProof=1, rolledBack=0, failed=0, promotionReady=false]")); assertTrue(drift.contains("selectedRuntimeIrStage=original")); diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistryTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistryTest.java index f1d02032..505f264c 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistryTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistryTest.java @@ -607,6 +607,33 @@ void diagnosticPeepholePassFindsTypedMadFmaCandidateWithoutMutatingIr() { assertEquals("false", passReport.proofArtifact().fields().get("rewriteSelection.mutationAllowed")); assertEquals("false", passReport.proofArtifact().fields().get("rewriteSelection.selectionApplied")); assertEquals("false", passReport.proofArtifact().fields().get("rewriteSelection.selectedIrReplacement")); + assertEquals("blocked", passReport.proofArtifact().fields().get("rewriteProof.status")); + assertEquals("rewrite-builder-not-implemented", passReport.proofArtifact().fields().get("rewriteProof.firstBlocker")); + assertEquals("true", passReport.proofArtifact().fields().get("rewriteProof.proofRequired")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteProof.proofAccepted")); + assertEquals("true", passReport.proofArtifact().fields().get("rewriteProof.runtimeEquivalenceRequired")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteProof.runtimeEquivalencePayload.present")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteProof.runtimeEquivalencePayload.complete")); + assertEquals("true", passReport.proofArtifact().fields().get("rewriteProof.rollbackRequired")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteProof.rollbackEvidence.present")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteProof.rollbackClean")); + assertEquals("true", passReport.proofArtifact().fields().get("rewriteProof.approvalRequired")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteProof.approvalAccepted")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteProof.mutationAllowed")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteProof.selectedIrReplacement")); + assertEquals("not-built", passReport.proofArtifact().fields().get("rewriteProof.transformedIrIdentity")); + assertEquals("blocked", passReport.proofArtifact().fields().get("rewriteReviewPackage.status")); + assertEquals("runtime-equivalence-payload-missing", passReport.proofArtifact().fields().get("rewriteReviewPackage.firstBlocker")); + assertEquals("true", passReport.proofArtifact().fields().get("rewriteReviewPackage.required")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteReviewPackage.complete")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteReviewPackage.proofAccepted")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteReviewPackage.runtimeEquivalencePayload.complete")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteReviewPackage.rollbackClean")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteReviewPackage.approvalAccepted")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteReviewPackage.mutationAllowed")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteReviewPackage.selectionApplied")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteReviewPackage.selectedIrReplacement")); + assertEquals("true", passReport.proofArtifact().fields().get("rewriteReviewPackage.manualReviewOnly")); assertEquals("sketch-ready", passReport.proofArtifact().fields().get("rule.0.rewriteSketch.0.status")); assertEquals("true", passReport.proofArtifact().fields().get("rule.0.rewriteSketch.0.planComplete")); assertEquals("true", passReport.proofArtifact().fields().get("rule.0.rewriteSketch.0.planValid")); @@ -614,6 +641,19 @@ void diagnosticPeepholePassFindsTypedMadFmaCandidateWithoutMutatingIr() { assertEquals("false", passReport.proofArtifact().fields().get("rule.0.rewriteSketch.0.rewriteBuilderImplemented")); assertEquals("false", passReport.proofArtifact().fields().get("rule.0.rewriteSketch.0.mutationAllowed")); assertEquals("false", passReport.proofArtifact().fields().get("rule.0.rewriteSketch.0.selectedIrReplacement")); + assertEquals("blocked", passReport.proofArtifact().fields().get("rule.0.rewriteSelection.status")); + assertEquals("rewrite-builder-not-implemented", passReport.proofArtifact().fields().get("rule.0.rewriteSelection.firstBlocker")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.rewriteSelection.selectionApplied")); + assertEquals("blocked", passReport.proofArtifact().fields().get("rule.0.rewriteProof.status")); + assertEquals("rewrite-builder-not-implemented", passReport.proofArtifact().fields().get("rule.0.rewriteProof.firstBlocker")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.rewriteProof.proofAccepted")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.rewriteProof.runtimeEquivalencePayload.complete")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.rewriteProof.rollbackClean")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.rewriteProof.selectedIrReplacement")); + assertEquals("blocked", passReport.proofArtifact().fields().get("rule.0.rewriteReviewPackage.status")); + assertEquals("runtime-equivalence-payload-missing", passReport.proofArtifact().fields().get("rule.0.rewriteReviewPackage.firstBlocker")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.rewriteReviewPackage.complete")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.rewriteReviewPackage.selectedIrReplacement")); assertEquals("clamp", passReport.proofArtifact().fields().get("rule.1.id")); assertEquals("dot", passReport.proofArtifact().fields().get("rule.2.id")); assertEquals("mix", passReport.proofArtifact().fields().get("rule.3.id")); @@ -786,6 +826,20 @@ void diagnosticPeepholePassFindsTypedDotCandidateWithoutMutatingIr() { assertEquals("false", fields.get("rewriteSelection.conflictResolutionImplemented")); assertEquals("false", fields.get("rewriteSelection.selectionApplied")); assertEquals("false", fields.get("rewriteSelection.selectedIrReplacement")); + assertEquals("blocked", fields.get("rewriteProof.status")); + assertEquals("rewrite-sketch-conflict-resolution-required", fields.get("rewriteProof.firstBlocker")); + assertEquals("true", fields.get("rewriteProof.proofRequired")); + assertEquals("false", fields.get("rewriteProof.proofAccepted")); + assertEquals("true", fields.get("rewriteProof.runtimeEquivalenceRequired")); + assertEquals("false", fields.get("rewriteProof.runtimeEquivalencePayload.present")); + assertEquals("false", fields.get("rewriteProof.rollbackEvidence.present")); + assertEquals("true", fields.get("rewriteProof.rollbackRequired")); + assertEquals("false", fields.get("rewriteProof.rollbackClean")); + assertEquals("false", fields.get("rewriteProof.selectedIrReplacement")); + assertEquals("blocked", fields.get("rewriteReviewPackage.status")); + assertEquals("rewrite-sketch-conflict-resolution-required", fields.get("rewriteReviewPackage.firstBlocker")); + assertEquals("false", fields.get("rewriteReviewPackage.complete")); + assertEquals("false", fields.get("rewriteReviewPackage.selectedIrReplacement")); assertEquals("rewrite-engine-not-implemented", fields.get("firstBlocker")); assertTrue(passReport.toLine().contains("structural rewrite and proof emission are not implemented")); } @@ -935,6 +989,26 @@ public String extensionId() { assertEquals("false", fields.get("rewriteSelection.runtimeEquivalenceRequired")); assertEquals("false", fields.get("rewriteSelection.approvalRequired")); assertEquals("false", fields.get("rewriteSelection.selectedIrReplacement")); + assertEquals("not-required", fields.get("rewriteProof.status")); + assertEquals("replacement-plan-root-missing", fields.get("rewriteProof.firstBlocker")); + assertEquals("false", fields.get("rewriteProof.proofRequired")); + assertEquals("false", fields.get("rewriteProof.runtimeEquivalenceRequired")); + assertEquals("false", fields.get("rewriteProof.rollbackRequired")); + assertEquals("false", fields.get("rewriteProof.selectedIrReplacement")); + assertEquals("not-required", fields.get("rewriteReviewPackage.status")); + assertEquals("replacement-plan-root-missing", fields.get("rewriteReviewPackage.firstBlocker")); + assertEquals("false", fields.get("rewriteReviewPackage.required")); + assertEquals("false", fields.get("rewriteReviewPackage.complete")); + assertEquals("blocked", fields.get("rule.0.rewriteSelection.status")); + assertEquals("replacement-plan-root-missing", fields.get("rule.0.rewriteSelection.firstBlocker")); + assertEquals("not-required", fields.get("rule.0.rewriteProof.status")); + assertEquals("replacement-plan-root-missing", fields.get("rule.0.rewriteProof.firstBlocker")); + assertEquals("false", fields.get("rule.0.rewriteProof.proofAccepted")); + assertEquals("false", fields.get("rule.0.rewriteProof.selectedIrReplacement")); + assertEquals("not-required", fields.get("rule.0.rewriteReviewPackage.status")); + assertEquals("replacement-plan-root-missing", fields.get("rule.0.rewriteReviewPackage.firstBlocker")); + assertEquals("false", fields.get("rule.0.rewriteReviewPackage.complete")); + assertEquals("false", fields.get("rule.0.rewriteReviewPackage.selectedIrReplacement")); assertEquals("replacement-plan-root-missing", fields.get("firstBlocker")); assertEquals("replacement-plan-root-missing", fields.get("rule.0.firstBlocker")); assertEquals("1", fields.get("rule.0.replacementPlan.validation.count")); diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java index 3904682d..f3182e19 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java @@ -955,7 +955,7 @@ void validationReportAndHistoryExposeRuntimeSnapshotWorkloadGateEvidence() throw assertTrue(reportMarkdown.contains("- Kernel `0` runtime IR handoff: stage=`optimized`, transformed=`true`, rollback=`false`, rejected=`false`, fallback=`none`, first=`optimized IrGpu is selected for backend lowering after runtime optimizer passes`")); assertTrue(reportMarkdown.contains("- Kernel `0` production mutation safety: enabled=`false`, gate=`not-requested`, profileRequested=`false`, first=`runtime IR participates in diagnostics, but production mutation is disabled because no production profile was requested`")); assertTrue(reportMarkdown.contains("- Kernel `0` I3 readiness: status=`review-ready`, sourcePromotion=`review-ready`, sourceReady=`true`, optimizerGate=`not-requested`, productionMutation=`false`, first=`I3 source pipeline is review-ready, but production mutation remains disabled until production gates are accepted`")); - assertTrue(reportMarkdown.contains("- Kernel `0` runtime optimizer drift: passes=`2`, applied=`2`, rolledBack=`0`, failed=`0`, proof=`0`, acceptedProof=`0`, blockingProof=`0`, replacementPlanComplete=`0`, replacementPlanPartial=`0`, replacementPlanFirstBlocker=`none`, replacementPlanValidation=`0/0 valid, invalid=0`, replacementPlanValidationFirstBlocker=`none`, rewriteSketch=`0/0 ready, blocked=0`, rewriteSketchFirstBlocker=`none`, rewriteSketchConflicts=`0`, rewriteSketchConflictFirstBlocker=`none`, rewriteSelection=`not-required`, rewriteSelectionFirstBlocker=`no-rewrite-sketches`, optimizerRules=`0`, optimizerRuleDetails=`none`, optimizerFamilies=`0`, promotionReadyFamilies=`0`, selected=`optimized`, fallback=`none`, gate=`not-requested`")); + assertTrue(reportMarkdown.contains("- Kernel `0` runtime optimizer drift: passes=`2`, applied=`2`, rolledBack=`0`, failed=`0`, proof=`0`, acceptedProof=`0`, blockingProof=`0`, replacementPlanComplete=`0`, replacementPlanPartial=`0`, replacementPlanFirstBlocker=`none`, replacementPlanValidation=`0/0 valid, invalid=0`, replacementPlanValidationFirstBlocker=`none`, rewriteSketch=`0/0 ready, blocked=0`, rewriteSketchFirstBlocker=`none`, rewriteSketchConflicts=`0`, rewriteSketchConflictFirstBlocker=`none`, rewriteSelection=`not-required`, rewriteSelectionFirstBlocker=`no-rewrite-sketches`, rewriteProof=`not-required`, rewriteProofFirstBlocker=`no-proof-candidates`, rewriteReviewPackage=`not-required`, rewriteReviewPackageFirstBlocker=`no-review-candidates`, optimizerRules=`0`, optimizerRuleDetails=`none`, optimizerFamilies=`0`, promotionReadyFamilies=`0`, selected=`optimized`, fallback=`none`, gate=`not-requested`")); assertTrue(reportMarkdown.contains("- Kernel `0` diagnostics: `1`; first=`packaged IrGpu source reconstructed from runtime artifact loader with descriptor parity`")); assertFalse(reportMarkdown.contains("- Kernel `0` reconstruction blockers:")); assertTrue(reportMarkdown.contains("- Kernel `1`: `inline://integration/perlin-kernel.cl`, status=`blocked`, parity=`false`, runtimeEquivalence=`false`, sourceSwitching=`reject-production-irgpu-source`, operatorAccepted=`false`, runtimeIr=`original`, productionMutation=`false`, sourceReady=`false`, i3=`blocked`")); @@ -963,7 +963,7 @@ void validationReportAndHistoryExposeRuntimeSnapshotWorkloadGateEvidence() throw assertTrue(reportMarkdown.contains("- Kernel `1` runtime IR handoff: stage=`original`, transformed=`false`, rollback=`false`, rejected=`true`, fallback=`production-ir-gate-blocked`, first=`optimized IrGpu was rejected by the production IR acceptance gate; original IrGpu remains selected`")); assertTrue(reportMarkdown.contains("- Kernel `1` production mutation safety: enabled=`false`, gate=`blocked`, profileRequested=`true`, first=`runtime IR participates in diagnostics, but production mutation remains fail-closed until production optimizer gates pass`")); assertTrue(reportMarkdown.contains("- Kernel `1` I3 readiness: status=`blocked`, sourcePromotion=`blocked`, sourceReady=`false`, optimizerGate=`blocked`, productionMutation=`false`, first=`I3 pipeline is active for diagnostics, but source promotion or production mutation is still blocked`")); - assertTrue(reportMarkdown.contains("- Kernel `1` runtime optimizer drift: passes=`3`, applied=`1`, rolledBack=`0`, failed=`1`, proof=`0`, acceptedProof=`0`, blockingProof=`0`, replacementPlanComplete=`0`, replacementPlanPartial=`0`, replacementPlanFirstBlocker=`none`, replacementPlanValidation=`0/0 valid, invalid=0`, replacementPlanValidationFirstBlocker=`none`, rewriteSketch=`0/0 ready, blocked=0`, rewriteSketchFirstBlocker=`none`, rewriteSketchConflicts=`0`, rewriteSketchConflictFirstBlocker=`none`, rewriteSelection=`not-required`, rewriteSelectionFirstBlocker=`no-rewrite-sketches`, optimizerRules=`0`, optimizerRuleDetails=`none`, optimizerFamilies=`0`, promotionReadyFamilies=`0`, selected=`original`, fallback=`production-ir-gate-blocked`, gate=`blocked`")); + assertTrue(reportMarkdown.contains("- Kernel `1` runtime optimizer drift: passes=`3`, applied=`1`, rolledBack=`0`, failed=`1`, proof=`0`, acceptedProof=`0`, blockingProof=`0`, replacementPlanComplete=`0`, replacementPlanPartial=`0`, replacementPlanFirstBlocker=`none`, replacementPlanValidation=`0/0 valid, invalid=0`, replacementPlanValidationFirstBlocker=`none`, rewriteSketch=`0/0 ready, blocked=0`, rewriteSketchFirstBlocker=`none`, rewriteSketchConflicts=`0`, rewriteSketchConflictFirstBlocker=`none`, rewriteSelection=`not-required`, rewriteSelectionFirstBlocker=`no-rewrite-sketches`, rewriteProof=`not-required`, rewriteProofFirstBlocker=`no-proof-candidates`, rewriteReviewPackage=`not-required`, rewriteReviewPackageFirstBlocker=`no-review-candidates`, optimizerRules=`0`, optimizerRuleDetails=`none`, optimizerFamilies=`0`, promotionReadyFamilies=`0`, selected=`original`, fallback=`production-ir-gate-blocked`, gate=`blocked`")); assertTrue(reportMarkdown.contains("- Kernel `1` diagnostics: `1`; first=`reconstructed source must match descriptor source before promotion review`")); assertTrue(reportMarkdown.contains("- Kernel blocker families: `source-parity=1`")); assertFalse(reportMarkdown.contains("runtimeIr=`unknown`")); @@ -980,8 +980,8 @@ void validationReportAndHistoryExposeRuntimeSnapshotWorkloadGateEvidence() throw assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("sourcePromotionFirstBlockers=runtime equivalence must execute and pass before backend source promotion=1")); assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("sourcePromotionFirstBlockerFamilies=runtime-equivalence=1")); assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("kernelCount=2")); - assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("kernel.0=inline://integration/image-kernel.cl[diagnostics=1, sourceSwitching=compile-irgpu-source-review/operatorAccepted=false/sourcePromotionFirstBlocker=none, runtimeIr=optimized, optimizerDrift=recorded/2passes/rollback=0/proof=0/acceptedProof=0/blockingProof=0/replacementPlanComplete=0/replacementPlanPartial=0/replacementPlanFirstBlocker=none/replacementPlanValidationValid=0/replacementPlanValidationTotal=0/replacementPlanValidationInvalid=0/replacementPlanValidationFirstBlocker=none/rewriteSketchReady=0/rewriteSketchTotal=0/rewriteSketchBlocked=0/rewriteSketchFirstBlocker=none/rewriteSketchConflicts=0/rewriteSketchConflictFirstBlocker=none/rewriteSketchConflictResolutionImplemented=false/rewriteSketchSelectionApplied=false/rewriteSelectionStatus=not-required/rewriteSelectionFirstBlocker=no-rewrite-sketches/rewriteSelectionApplied=false/rewriteBuilderImplemented=false/selectedIrReplacement=false/optimizerRules=0/optimizerRuleDetails=none/optimizerFamilies=0/promotionReadyFamilies=0/payloadCompleteFamilies=0/payloadCompleteAll=false, extensionParticipation=not-recorded/0executions/failedContinued=0/failedClosed=0/fallback=none, productionMutation=false, sourceReady=true, i3=review-ready, families=none]")); - assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("kernel.1=inline://integration/perlin-kernel.cl[diagnostics=1, sourceSwitching=reject-production-irgpu-source/operatorAccepted=false/sourcePromotionFirstBlocker=runtime equivalence must execute and pass before backend source promotion, runtimeIr=original, optimizerDrift=recorded/3passes/rollback=0/proof=0/acceptedProof=0/blockingProof=0/replacementPlanComplete=0/replacementPlanPartial=0/replacementPlanFirstBlocker=none/replacementPlanValidationValid=0/replacementPlanValidationTotal=0/replacementPlanValidationInvalid=0/replacementPlanValidationFirstBlocker=none/rewriteSketchReady=0/rewriteSketchTotal=0/rewriteSketchBlocked=0/rewriteSketchFirstBlocker=none/rewriteSketchConflicts=0/rewriteSketchConflictFirstBlocker=none/rewriteSketchConflictResolutionImplemented=false/rewriteSketchSelectionApplied=false/rewriteSelectionStatus=not-required/rewriteSelectionFirstBlocker=no-rewrite-sketches/rewriteSelectionApplied=false/rewriteBuilderImplemented=false/selectedIrReplacement=false/optimizerRules=0/optimizerRuleDetails=none/optimizerFamilies=0/promotionReadyFamilies=0/payloadCompleteFamilies=0/payloadCompleteAll=false, extensionParticipation=not-recorded/0executions/failedContinued=0/failedClosed=0/fallback=production-ir-gate-blocked, productionMutation=false, sourceReady=false, i3=blocked, families=source-parity=1]")); + assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("kernel.0=inline://integration/image-kernel.cl[diagnostics=1, sourceSwitching=compile-irgpu-source-review/operatorAccepted=false/sourcePromotionFirstBlocker=none, runtimeIr=optimized, optimizerDrift=recorded/2passes/rollback=0/proof=0/acceptedProof=0/blockingProof=0/replacementPlanComplete=0/replacementPlanPartial=0/replacementPlanFirstBlocker=none/replacementPlanValidationValid=0/replacementPlanValidationTotal=0/replacementPlanValidationInvalid=0/replacementPlanValidationFirstBlocker=none/rewriteSketchReady=0/rewriteSketchTotal=0/rewriteSketchBlocked=0/rewriteSketchFirstBlocker=none/rewriteSketchConflicts=0/rewriteSketchConflictFirstBlocker=none/rewriteSketchConflictResolutionImplemented=false/rewriteSketchSelectionApplied=false/rewriteSelectionStatus=not-required/rewriteSelectionFirstBlocker=no-rewrite-sketches/rewriteSelectionApplied=false/rewriteProofStatus=not-required/rewriteProofFirstBlocker=no-proof-candidates/rewriteProofAccepted=false/rewriteBuilderImplemented=false/selectedIrReplacement=false/rewriteReviewPackageStatus=not-required/rewriteReviewPackageFirstBlocker=no-review-candidates/rewriteReviewPackageComplete=false/optimizerRules=0/optimizerRuleDetails=none/optimizerFamilies=0/promotionReadyFamilies=0/payloadCompleteFamilies=0/payloadCompleteAll=false, extensionParticipation=not-recorded/0executions/failedContinued=0/failedClosed=0/fallback=none, productionMutation=false, sourceReady=true, i3=review-ready, families=none]")); + assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("kernel.1=inline://integration/perlin-kernel.cl[diagnostics=1, sourceSwitching=reject-production-irgpu-source/operatorAccepted=false/sourcePromotionFirstBlocker=runtime equivalence must execute and pass before backend source promotion, runtimeIr=original, optimizerDrift=recorded/3passes/rollback=0/proof=0/acceptedProof=0/blockingProof=0/replacementPlanComplete=0/replacementPlanPartial=0/replacementPlanFirstBlocker=none/replacementPlanValidationValid=0/replacementPlanValidationTotal=0/replacementPlanValidationInvalid=0/replacementPlanValidationFirstBlocker=none/rewriteSketchReady=0/rewriteSketchTotal=0/rewriteSketchBlocked=0/rewriteSketchFirstBlocker=none/rewriteSketchConflicts=0/rewriteSketchConflictFirstBlocker=none/rewriteSketchConflictResolutionImplemented=false/rewriteSketchSelectionApplied=false/rewriteSelectionStatus=not-required/rewriteSelectionFirstBlocker=no-rewrite-sketches/rewriteSelectionApplied=false/rewriteProofStatus=not-required/rewriteProofFirstBlocker=no-proof-candidates/rewriteProofAccepted=false/rewriteBuilderImplemented=false/selectedIrReplacement=false/rewriteReviewPackageStatus=not-required/rewriteReviewPackageFirstBlocker=no-review-candidates/rewriteReviewPackageComplete=false/optimizerRules=0/optimizerRuleDetails=none/optimizerFamilies=0/promotionReadyFamilies=0/payloadCompleteFamilies=0/payloadCompleteAll=false, extensionParticipation=not-recorded/0executions/failedContinued=0/failedClosed=0/fallback=production-ir-gate-blocked, productionMutation=false, sourceReady=false, i3=blocked, families=source-parity=1]")); assertTrue(entries.get(0).backendSourcePromotionWorkloadStatus().contains("productionSourceSwitching=disabled")); assertTrue(entries.get(0).productionPromotionExplainabilityStatus().contains("contract=valid")); assertTrue(entries.get(0).productionPromotionExplainabilityStatus().contains("decisionMode=diagnostic-only")); From 234c0480d2cb0fc24c54ce3e73c16643022915d2 Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Mon, 13 Jul 2026 11:16:32 +0300 Subject: [PATCH 23/32] wip: safe progress --- ROADMAP.md | 6 +- docs/IR-Optimizer.md | 4 +- docs/Runtime-Guide.md | 4 +- ...dSourcePromotionWorkloadGateFormatter.java | 252 +++++ ...uRuntimeIrPeepholeGraphPatchPreflight.java | 244 +++++ ...IrPeepholeIrArtifactEnvelopePreflight.java | 313 ++++++ ...meIrPeepholeNodeIdAllocationPreflight.java | 194 ++++ .../runtime/GpuRuntimeIrPeepholePass.java | 414 ++++++++ ...RuntimeIrPeepholeReplacementBlueprint.java | 188 ++++ ...imeIrPeepholeReplacementNodePreflight.java | 240 +++++ ...IrPeepholeRewriteTransactionPreflight.java | 194 ++++ ...untimeIrPeepholeRewriteVisitPreflight.java | 168 ++++ .../GpuRuntimeIrPeepholeRuleRegistry.java | 44 +- .../GpuRuntimeIrPeepholeRuleReport.java | 17 +- ...meIrPeepholeTransformedGraphPreflight.java | 264 +++++ ...uRuntimeIrPeepholeTypedRewriteVisitor.java | 128 +++ .../runtime/GpuRuntimeIrTypedNodeGraph.java | 7 + .../GpuRuntimeOptimizerDriftArtifact.java | 952 ++++++++++++++++++ .../GpuRuntimeCompileArtifactDumperTest.java | 220 ++++ .../GpuRuntimeIrOptimizerRegistryTest.java | 220 ++++ 20 files changed, 4065 insertions(+), 8 deletions(-) create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeGraphPatchPreflight.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeNodeIdAllocationPreflight.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeReplacementBlueprint.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeReplacementNodePreflight.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteTransactionPreflight.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteVisitPreflight.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeTransformedGraphPreflight.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeTypedRewriteVisitor.java diff --git a/ROADMAP.md b/ROADMAP.md index 10770cc0..a36ec40c 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -445,7 +445,8 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i Build proof-backed local expression-tree rewrites such as `a + t * (b - a) -> mix(a, b, t)`, `a * b + c -> mad/fma(a, b, c)`, `min(max(x, lo), hi) -> clamp(x, lo, hi)`, ternary step forms -> `step(edge, x)`, and small fixed-width dot-product patterns. Mutation must stay fail-closed until structural replacement, typed-graph validation, runtime-equivalence, rollback, and A1/A2 evidence are stable. - [ ] Add a safe IR pattern-matching and rewrite visitor for peephole passes. The current typed preflight safely detects the initial `mad/fma` candidate shape without editing nodes. Next, add reusable typed-node traversal/replacement APIs that can prove complete-pattern replacement before editing anything. Partial matches must produce diagnostics only. Rule implementations should remain isolated (`mix`, `mad/fma`, `clamp`, `step`, `dot`) and report applied/skipped/blocked counts per rule so optimizer drift artifacts can detect regressions. - `GpuRuntimeIrPeepholeReplacementPlan` now adds the first read-only typed replacement-planning contract. The `mad/fma` rule records root, covered, and input node ids plus complete/partial blocker state for detected `a*b+c` / `c+a*b` shapes, `clamp` records read-only complete plans for `min(max(x, lo), hi)` typed call shapes, `step` records read-only complete plans for simple `x < edge ? 0 : 1` / `x >= edge ? 1 : 0` ternary shapes, `dot` records read-only plans for fixed-width additive multiply trees such as `(a0*b0)+(a1*b1)`, and `mix` records read-only complete plans for `a + t * (b - a)` / `t * (b - a) + a`. Rewrite-sketch overlap/conflict detection, `rewriteSelection.*` selection-readiness preflight, `rewriteProof.*` proof/runtime-equivalence/rollback preflight, and `rewriteReviewPackage.*` manual-review package preflight now make conflict-resolution, rewrite-builder, runtime-equivalence payload, rollback evidence, approval, mutation, selected-IR, and package-completeness blockers explicit before any rewrite engine exists. The pass still reports `rewrite-engine-not-implemented` and never mutates selected IR. All current peephole families now have diagnostic matchers; the remaining work is shared traversal/rewrite mechanics and proof gates, not family visibility. + `GpuRuntimeIrPeepholeReplacementPlan` now adds the first read-only typed replacement-planning contract. The `mad/fma` rule records root, covered, and input node ids plus complete/partial blocker state for detected `a*b+c` / `c+a*b` shapes, `clamp` records read-only complete plans for `min(max(x, lo), hi)` typed call shapes, `step` records read-only complete plans for simple `x < edge ? 0 : 1` / `x >= edge ? 1 : 0` ternary shapes, `dot` records read-only plans for fixed-width additive multiply trees such as `(a0*b0)+(a1*b1)`, and `mix` records read-only complete plans for `a + t * (b - a)` / `t * (b - a) + a`. `GpuRuntimeIrPeepholeTypedRewriteVisitor` now adds shared read-only traversal preflight, `GpuRuntimeIrPeepholeReplacementBlueprint` adds a target intrinsic-call blueprint, `GpuRuntimeIrPeepholeRewriteTransactionPreflight` adds a transaction-shape preflight, `GpuRuntimeIrPeepholeNodeIdAllocationPreflight` adds deterministic candidate id diagnostics above transaction-ready plans, `GpuRuntimeIrPeepholeReplacementNodePreflight` adds a replacement-node construction preview that pairs the candidate id with the intrinsic-call blueprint without building an IR node, `GpuRuntimeIrPeepholeGraphPatchPreflight` adds a read-only replace/remove/retain/insert graph-patch preview without applying it, and `GpuRuntimeIrPeepholeTransformedGraphPreflight` adds a materialization-key/original-IR-identity preview while keeping `transformedGraphIdentity=not-built`. Together they record deterministic visit order, blueprint target node kind/operation, argument ids/roles, replaced/removed/retained/inserted node ids, planned added-node count, candidate ids from current graph max id, candidate replacement-node id, materialization key, ready/blocked counts, first blockers, and disabled id reservation / allocator application / replacement-node build / graph-patch application / transformed-graph build / graph-rewrite / replacement-builder / transformed-IR / mutation / selected-IR flags. Rewrite-sketch overlap/conflict detection, `rewriteSelection.*` selection-readiness preflight, `rewriteProof.*` proof/runtime-equivalence/rollback preflight, and `rewriteReviewPackage.*` manual-review package preflight now make conflict-resolution, rewrite-builder, runtime-equivalence payload, rollback evidence, approval, mutation, selected-IR, and package-completeness blockers explicit before any rewrite engine exists. The pass still reports `rewrite-engine-not-implemented` and never mutates selected IR. All current peephole families now have diagnostic matchers, shared traversal evidence, replacement blueprints, transaction-shape evidence, node-id allocation previews, replacement-node construction previews, graph-patch previews, and transformed-graph materialization previews; the remaining work is real id reservation, actual replacement-node construction, graph rewrite mechanics, transformed IR artifact construction, and proof gates. + `GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight` now adds the next read-only artifact-envelope boundary above transformed-graph materialization. It records original IR identity, absent transformed-graph / optimized-artifact identities, deterministic envelope key, proof anchor, rollback anchor, ready/blocked counts, first blocker, and disabled artifact build / optimized-artifact build / transformed-IR / graph-patch / graph-rewrite / mutation / selected-IR flags before any optimized artifact can exist. - [x] Add Java-source fast-math policy metadata for runtime optimizer decisions. `@GPUOptimize(fastMath = false)` is now the production-safe default policy surface. Java-source frontend artifacts persist the explicit annotation source or `default-strict`, runtime optimization requests expose the resolved policy, and optimizer hooks can declare `requiresFastMath()` so the registry skips them without invocation unless the method explicitly opts in. This does not enable production mutation by itself. - [x] Preserve fast-math policy through the ASM frontend. @@ -455,7 +456,8 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i - [ ] Add proof, runtime-equivalence, and rollback gates for peephole rewrites. `GpuRuntimeIrPeepholeRewriteProofReadiness` now exposes the read-only proof preflight above selection readiness: proof required/accepted state, runtime-equivalence payload presence/completeness, rollback evidence, approval acceptance, original IR identity, absent transformed IR identity, and disabled mutation / selected-IR replacement. `GpuRuntimeIrPeepholeRewriteReviewPackage` now sits above that as the package boundary: it records required/complete status, first blocker, conflict count, proof acceptance, runtime-equivalence payload, rollback, approval, manual-review-only state, and disabled mutation / selection / selected-IR replacement. The pass now emits these gates both at aggregate level and per rule through `rule.N.rewriteSelection.*`, `rule.N.rewriteProof.*`, and `rule.N.rewriteReviewPackage.*`, so drift artifacts can distinguish a rule blocked by structural validation from one blocked by missing runtime-equivalence payload, rollback evidence, approval, or incomplete review packaging. Real rewrites still must emit accepted proof artifacts with optimizer family (`peephole`, `instcombine`, or per-rule families), rule counters, fast-math policy, original/transformed IR identities, and compact/durable runtime-equivalence payload evidence before this item can close. Failed proofs, incomplete pattern rewrites, runtime-equivalence failures, rollback diagnostics, or incomplete review packages must keep the original IR selected. - [ ] Surface peephole telemetry in optimizer drift and promotion artifacts. - `runtime-optimizer-drift.properties` now surfaces read-only replacement-plan evidence with aggregate complete/partial counts, structural validation counts, rewrite-sketch ready/blocked counts, overlap/conflict counters, aggregate and per-rule `rewriteSelection.*` status/first-blocker guardrails, aggregate and per-rule `rewriteProof.*` proof/runtime-equivalence/rollback blockers, aggregate and per-rule `rewriteReviewPackage.*` package-completeness blockers, and compact `optimizerRule.*` summaries from the existing `rule.N.*` proof fields, so CI can detect `mad/fma`, `clamp`, `step`, `dot`, and `mix` planning drift before any rewrite engine exists. Workload gates, compact workload summaries, OpenCL validation Markdown/history, I3 summaries, and production-promotion explainability now carry those replacement-plan counters and per-rule summaries without affecting readiness or production decisions. Production promotion must remain disabled until these rule families have stable A1/A2 runtime evidence and backend/vendor confidence. + `runtime-optimizer-drift.properties` now surfaces read-only replacement-plan evidence with aggregate complete/partial counts, structural validation counts, rewrite-visitor ready/blocked counts, replacement-blueprint ready/blocked counts, rewrite-transaction ready/blocked counts, node-id allocation ready/blocked counts, replacement-node preview ready/blocked counts, graph-patch preview ready/blocked counts, transformed-graph materialization ready/blocked counts, rewrite-sketch ready/blocked counts, overlap/conflict counters, aggregate and per-rule `rewriteSelection.*` status/first-blocker guardrails, aggregate and per-rule `rewriteProof.*` proof/runtime-equivalence/rollback blockers, aggregate and per-rule `rewriteReviewPackage.*` package-completeness blockers, and compact `optimizerRule.*` summaries from the existing `rule.N.*` proof fields, so CI can detect `mad/fma`, `clamp`, `step`, `dot`, and `mix` planning drift before any rewrite engine exists. Workload gates carry those visitor/blueprint/transaction/allocation/replacement-node/graph-patch/transformed-graph fields forward with the rest of the drift payload, and compact workload summaries, OpenCL validation Markdown/history, I3 summaries, and production-promotion explainability continue to treat them as evidence-only telemetry. Production promotion must remain disabled until these rule families have stable A1/A2 runtime evidence and backend/vendor confidence. + Drift artifacts and workload gates now also carry `irArtifactEnvelope.*` aggregate/per-rule telemetry so CI can see the future optimized-artifact envelope boundary separately from transformed-graph materialization, while `artifactEnvelopeBuilt=false`, `optimizedArtifactBuilt=false`, `mutationAllowed=false`, and `selectedIrReplacement=false` stay pinned. #### I3.5 Vendor/device-specific optimization strategy diff --git a/docs/IR-Optimizer.md b/docs/IR-Optimizer.md index ea2cc77a..042626f4 100644 --- a/docs/IR-Optimizer.md +++ b/docs/IR-Optimizer.md @@ -68,7 +68,9 @@ The default bridge is intentionally proposal-only. It can surface canonicalizati Runtime artifact dumping records bridge/provider evidence in `runtime-ir-optimizer-evidence.properties`. The artifact is filtered by the stable `javatogpu.ir-optimizer` provider prefix and includes pass counts, outcomes, proposal-only/selected counts, original/transformed IR identities, proof artifact fields, and diagnostics. This is an evidence surface only; it does not authorize optimized IR selection or production mutation by itself. -Runtime peephole/InstCombine proof is also kept read-only. `GpuRuntimeIrPeepholeReplacementPlan` records future structural replacement intent, including root node, covered node ids, input node ids, replacement kind, completeness, and first blocker. `GpuRuntimeIrTypedNodeGraph` is the shared read-only traversal view for typed peephole rules, and `GpuRuntimeIrPeepholeReplacementPlanValidation` checks each emitted plan against that graph before it is surfaced. `GpuRuntimeIrPeepholeRewriteSketch` is the next mutation-free skeleton: a complete and valid plan can become `sketch-ready`, but the sketch still records `rewriteBuilderImplemented=false`, `mutationAllowed=false`, and `selectedIrReplacement=false`; runtime equivalence and approval remain required. `GpuRuntimeIrPeepholeRewriteSelectionReadiness` now sits above ready/blocked sketches as a read-only selection preflight: it reports sketch counts, conflict counts, status, first blocker, missing rewrite-builder/conflict-resolution/proof/approval gates, and disabled mutation/selection flags without choosing any optimized IR. `GpuRuntimeIrPeepholeRewriteProofReadiness` then records the next fail-closed proof boundary: proof required/accepted state, runtime-equivalence payload presence/completeness, rollback evidence, approval acceptance, original IR identity, absent transformed IR identity, and disabled mutation / selected-IR replacement. `GpuRuntimeIrPeepholeRewriteReviewPackage` sits above proof readiness as the manual-review package boundary: it records required/complete state, conflict count, proof acceptance, runtime-equivalence payload, rollback, approval, manual-review-only state, and disabled mutation / selection / selected-IR replacement. The pass emits aggregate `rewriteSelection.*`, `rewriteProof.*`, and `rewriteReviewPackage.*` plus per-rule `rule.N.rewriteSelection.*`, `rule.N.rewriteProof.*`, and `rule.N.rewriteReviewPackage.*`, so CI can identify which matcher family is blocked by graph validation, conflict resolution, missing runtime-equivalence payloads, missing rollback evidence, missing approval, or incomplete review packaging. Validation fields such as `replacementPlan.validation.invalid.count` and `rule.N.replacementPlan.validation.firstBlocker` fail closed on missing roots, uncovered roots, or missing covered/input nodes; they still do not build replacement nodes or authorize mutation. The current matcher set covers `mad/fma` for `a*b+c` / `c+a*b`, `clamp` for `min(max(x, lo), hi)`, `step` for simple `x < edge ? 0 : 1` / `x >= edge ? 1 : 0` ternaries, `dot` for additive multiply trees such as `(a0*b0)+(a1*b1)`, and `mix` for `a + t * (b - a)` / `t * (b - a) + a`. `runtime-optimizer-drift.properties` aggregates complete/partial replacement-plan counts, structural plan-validation total/valid/invalid counts, rewrite-sketch ready/blocked counts, overlap/conflict counters, aggregate and per-rule `rewriteSelection.*` status/first-blocker guardrails, aggregate and per-rule `rewriteProof.*` proof/runtime-equivalence/rollback blockers, aggregate and per-rule `rewriteReviewPackage.*` package-completeness blockers, and compact `optimizerRule.*` summaries for CI drift detection. Workload gates, validation reports/history, I3 summaries, and production-promotion explainability carry those counters forward as evidence-only telemetry. The pass still reports `rewrite-engine-not-implemented` and never mutates the selected IR. +Runtime peephole/InstCombine proof is also kept read-only. `GpuRuntimeIrPeepholeReplacementPlan` records future structural replacement intent, including root node, covered node ids, input node ids, replacement kind, completeness, and first blocker. `GpuRuntimeIrTypedNodeGraph` is the shared read-only traversal view for typed peephole rules, and `GpuRuntimeIrPeepholeReplacementPlanValidation` checks each emitted plan against that graph before it is surfaced. `GpuRuntimeIrPeepholeTypedRewriteVisitor` is the shared traversal preflight above validated plans: it records deterministic visit order, visitor-ready/blocked counts, first visitor blocker, `visitorImplemented=true`, and disabled replacement-builder / transformed-IR / mutation / selected-IR flags. `GpuRuntimeIrPeepholeReplacementBlueprint` sits above visitor-ready plans as a read-only target-node contract: it records intended intrinsic-call node kind, target operation, argument node ids/roles, blueprint-ready/blocked counts, first blueprint blocker, and disabled replacement-builder / transformed-IR / mutation / selected-IR flags. `GpuRuntimeIrPeepholeRewriteTransactionPreflight` then describes the future graph transaction shape: replaced root ids, removed covered ids, retained input ids, planned added-node count, `plannedAddedNodeIds=not-allocated`, and disabled node-id allocator / graph-rewrite / transformed-IR / mutation / selected-IR flags. `GpuRuntimeIrPeepholeNodeIdAllocationPreflight` sits above transaction-ready plans as a diagnostic-only allocation preview: it records current graph max node id, deterministic candidate node ids, allocation-ready/blocked counts, first blocker, and disabled id reservation / allocator application / graph-rewrite / transformed-IR / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeReplacementNodePreflight` then pairs that candidate id with the intrinsic-call blueprint as a diagnostic-only replacement-node construction preview, keeping `replacementNodeBuilt=false`, `replacementBuilderImplemented=false`, and graph-rewrite / transformed-IR / mutation / selected-IR disabled. `GpuRuntimeIrPeepholeGraphPatchPreflight` records the next read-only patch preview: replacement node id, replaced/removed/retained/inserted node sets, graph-patch ready/blocked counts, first blocker, and disabled graph-patch application / graph-rewrite / transformed-IR / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeTransformedGraphPreflight` adds the materialization boundary above that patch: original IR identity, deterministic materialization key, `transformedGraphIdentity=not-built`, materialization-ready/blocked counts, first blocker, and disabled transformed-graph build / transformed-IR / graph-patch application / graph-rewrite / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeRewriteSketch` remains the mutation-free rewrite skeleton: a complete, valid replacement plan can become `sketch-ready`, but the sketch still records `rewriteBuilderImplemented=false`, `mutationAllowed=false`, and `selectedIrReplacement=false`; runtime equivalence and approval remain required. `GpuRuntimeIrPeepholeRewriteSelectionReadiness` now sits above ready/blocked sketches as a read-only selection preflight: it reports sketch counts, conflict counts, status, first blocker, missing rewrite-builder/conflict-resolution/proof/approval gates, and disabled mutation/selection flags without choosing any optimized IR. `GpuRuntimeIrPeepholeRewriteProofReadiness` then records the next fail-closed proof boundary: proof required/accepted state, runtime-equivalence payload presence/completeness, rollback evidence, approval acceptance, original IR identity, absent transformed IR identity, and disabled mutation / selected-IR replacement. `GpuRuntimeIrPeepholeRewriteReviewPackage` sits above proof readiness as the manual-review package boundary: it records required/complete state, conflict count, proof acceptance, runtime-equivalence payload, rollback, approval, manual-review-only state, and disabled mutation / selection / selected-IR replacement. The pass emits aggregate `rewriteVisitor.*`, `replacementBlueprint.*`, `rewriteTransaction.*`, `nodeIdAllocation.*`, `replacementNode.*`, `graphPatch.*`, `transformedGraph.*`, `rewriteSelection.*`, `rewriteProof.*`, and `rewriteReviewPackage.*` plus matching per-rule fields, so CI can identify which matcher family is blocked by graph validation, traversal, blueprint construction, transaction shaping, id-allocation preview, replacement-node preview, graph-patch preview, transformed-graph materialization, conflict resolution, missing runtime-equivalence payloads, missing rollback evidence, missing approval, or incomplete review packaging. Validation fields fail closed on missing roots, uncovered roots, or missing covered/input nodes; visitor fields prove only original-node traversal; blueprint fields describe only a future intrinsic-call shape; transaction fields describe only planned replace/remove/retain/add effects; allocation fields preview candidate ids only and keep `nodeIdsReserved=false` and `nodeIdAllocatorApplied=false`; replacement-node fields preview the synthetic node shape only and keep `replacementNodeBuilt=false`; graph-patch fields preview the future diff only and keep `graphPatchApplied=false`; transformed-graph fields preview materialization only and keep `transformedGraphBuilt=false`. They still do not reserve node ids, build replacement nodes, apply graph patches, build transformed graphs, rewrite graphs, or authorize mutation. The current matcher set covers `mad/fma` for `a*b+c` / `c+a*b`, `clamp` for `min(max(x, lo), hi)`, `step` for simple `x < edge ? 0 : 1` / `x >= edge ? 1 : 0` ternaries, `dot` for additive multiply trees such as `(a0*b0)+(a1*b1)`, and `mix` for `a + t * (b - a)` / `t * (b - a) + a`. `runtime-optimizer-drift.properties` aggregates complete/partial replacement-plan counts, structural plan-validation total/valid/invalid counts, rewrite-visitor ready/blocked counts, replacement-blueprint ready/blocked counts, rewrite-transaction ready/blocked counts, node-id allocation ready/blocked counts, replacement-node preview ready/blocked counts, graph-patch preview ready/blocked counts, transformed-graph materialization ready/blocked counts, rewrite-sketch ready/blocked counts, overlap/conflict counters, aggregate and per-rule `rewriteSelection.*` status/first-blocker guardrails, aggregate and per-rule `rewriteProof.*` proof/runtime-equivalence/rollback blockers, aggregate and per-rule `rewriteReviewPackage.*` package-completeness blockers, and compact `optimizerRule.*` summaries for CI drift detection. Workload gates carry those visitor/blueprint/transaction/allocation/replacement-node/graph-patch/transformed-graph fields forward with the rest of the drift payload, while validation reports/history, I3 summaries, and production-promotion explainability treat the optimizer state as evidence-only telemetry. The pass still reports `rewrite-engine-not-implemented` and never mutates the selected IR. + +`GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight` now sits above transformed-graph materialization as the future optimized-artifact boundary. It records the original IR identity, absent transformed graph and optimized artifact identities, deterministic envelope key, proof anchor, rollback anchor, ready/blocked counts, first blocker, and disabled artifact build / optimized-artifact build / transformed-IR / graph-patch / graph-rewrite / mutation / selected-IR flags. The pass emits aggregate and `rule.N.irArtifactEnvelope.*` fields, `runtime-optimizer-drift.properties` aggregates them, and workload gates carry them forward as evidence-only telemetry; no optimized artifact is constructed or selected. The same evidence artifact now records `approvalTemplate.*` fields for each optimizer pass. Real proposal rewrites with distinct original/optimized identities and accepted proof metadata are marked `pending`; preview and no-change proposals are marked `not-applicable` with a first blocker such as `proposal-decision-not-proposed`. diff --git a/docs/Runtime-Guide.md b/docs/Runtime-Guide.md index eea998f7..35abe4e1 100644 --- a/docs/Runtime-Guide.md +++ b/docs/Runtime-Guide.md @@ -235,7 +235,9 @@ Non-analysis optimizer passes with runtime-equivalence payload evidence also pro When the optional IR optimizer bridge participates, each kernel directory may also receive `runtime-ir-optimizer-evidence.properties`. The artifact records only proposal/provider evidence from stable `javatogpu.ir-optimizer` sources: pass counts, proposal-only versus selected optimized counts, rollback counts, IR identities, proof fields, approval-template applicability, and diagnostics. Constant-folding preview proof is also aggregated into `constantFoldingPreview.*` fields for pass count, candidate count, skipped blockers, required runtime-equivalence/approval gates, and unresolved integer-overflow / floating-point-rounding safety flags. Safe-local-CSE preview proof is aggregated into `safeLocalCsePreview.*` fields for pass count, expression/candidate/duplicate/equivalence-class counts, unsupported-operator / impure-operand / control-flow-boundary blockers, required runtime-equivalence/approval gates, and unresolved dominance / side-effect-freedom proof flags. Typed dead-code preview proof is aggregated into `typedDeadCodePreview.*` fields for pass count, typed/reachable/unreachable node counts, missing-root / missing-child-reference / side-effecting-unreachable blockers, required runtime-equivalence/approval gates, and unresolved side-effect-freedom proof flags. The artifact also records `previewReadiness.*` fields with aggregate status, recorded/candidate/blocked family counts, and a per-family status summary so CI can distinguish no-candidate previews from proof-blocked previews and review-ready runtime-equivalence candidates. `runtimeEquivalenceReview.*` records the next fail-closed review boundary: status, eligibility, requirement, first blocker, family summary, disabled production mutation, disabled selected-IR replacement, and manual-review-only state. `reviewPackage.*` records the manual-review package boundary above that review gate, including package status, requirement/completeness, first blocker, proposal-pass count, pending approval count, original/optimized IR and proof-summary requirements, manual-review-only state, and disabled production mutation / selected-IR replacement. `openClValidationReport` aggregates this into `Runtime IR Optimizer Evidence` for CI visibility, including constant-folding, safe-local-CSE, typed dead-code preview totals, preview-readiness status, runtime-equivalence review eligibility, and review-package status. `validateOpenClRuntimeIrOptimizerEvidence` then validates the raw runtime evidence files and fails CI if those fail-closed guardrails are missing, completed early, or production-enabling. The section and validator are evidence-only and do not participate in history, source switching, selected-IR replacement, or production mutation gates. -Runtime peephole/InstCombine remains diagnostic-only. `GpuRuntimeIrPeepholeReplacementPlan` records read-only typed replacement plans for current rule families: `mad/fma`, `clamp`, `step`, `dot`, and `mix`. Built-in rules now use `GpuRuntimeIrTypedNodeGraph` as the shared read-only typed-node traversal surface, and each emitted plan is checked by `GpuRuntimeIrPeepholeReplacementPlanValidation` before proof fields are exported. `GpuRuntimeIrPeepholeRewriteSketch` records the mutation-free structural rewrite skeleton above those validated plans: ready sketches prove only that the future rewrite shape is describable, while `rewriteBuilderImplemented`, `mutationAllowed`, and `selectedIrReplacement` remain `false`. `rewriteSelection.*` fields add a read-only selection preflight over those sketches, including sketch/conflict counts, selection status, first blocker, proof/approval requirements, and disabled mutation/selection guardrails. `rewriteProof.*` fields add the matching proof preflight: proof status, first blocker, runtime-equivalence payload presence/completeness, rollback evidence, approval acceptance, original IR identity, absent transformed IR identity, and disabled selected-IR replacement. `rewriteReviewPackage.*` fields add the manual-review package boundary above proof readiness: package required/complete state, conflict count, proof acceptance, runtime-equivalence payload, rollback, approval, manual-review-only state, and disabled mutation / selection / selected-IR replacement. The same selection/proof/package preflight fields are also emitted under `rule.N.rewriteSelection.*`, `rule.N.rewriteProof.*`, and `rule.N.rewriteReviewPackage.*`, making rule-family blockers visible without enabling any rewrite. Proof fields expose the candidate root node, covered nodes, input nodes, completeness, first blocker, structural validation counters such as `replacementPlan.validation.invalid.count` / `rule.N.replacementPlan.validation.firstBlocker`, sketch counters such as `rewriteSketch.ready.count` / `rule.N.rewriteSketch.blocked.count`, selection preflight fields such as `rewriteSelection.status` / `rule.N.rewriteSelection.firstBlocker`, proof preflight fields such as `rewriteProof.status` / `rule.N.rewriteProof.firstBlocker`, and review-package fields such as `rewriteReviewPackage.status` / `rule.N.rewriteReviewPackage.firstBlocker`. `runtime-optimizer-drift.properties` aggregates the complete/partial replacement-plan counts, plan-validation total/valid/invalid counts, rewrite-sketch ready/blocked counts, conflict counts, selection-readiness guardrails, proof/runtime-equivalence/rollback blockers, package-completeness blockers, first incomplete/invalid/sketch/selection/proof/package blockers, and compact `optimizerRule.*` summaries from existing `rule.N.*` proof fields so CI can detect planning drift while the rewrite engine remains absent. Workload gates, compact workload summaries, OpenCL validation Markdown/history, I3 summaries, and production-promotion explainability surface the same counters as evidence-only telemetry. These plans and sketches describe what a future structural rewrite would need to replace; they do not edit typed bodies, select optimized IR, or enable production mutation. +Runtime peephole/InstCombine remains diagnostic-only. `GpuRuntimeIrPeepholeReplacementPlan` records read-only typed replacement plans for current rule families: `mad/fma`, `clamp`, `step`, `dot`, and `mix`. Built-in rules now use `GpuRuntimeIrTypedNodeGraph` as the shared read-only typed-node traversal surface, and each emitted plan is checked by `GpuRuntimeIrPeepholeReplacementPlanValidation` before proof fields are exported. `GpuRuntimeIrPeepholeTypedRewriteVisitor` adds a shared traversal preflight above validated plans: it records deterministic visit order, visitor-ready/blocked counts, first visitor blocker, and disabled replacement-builder / transformed-IR / mutation / selected-IR flags. `GpuRuntimeIrPeepholeReplacementBlueprint` adds a read-only target intrinsic-call blueprint above visitor-ready plans: it records target node kind/operation, argument node ids/roles, blueprint-ready/blocked counts, first blueprint blocker, and the same disabled builder / transformed-IR / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeRewriteTransactionPreflight` adds the graph-transaction shape above blueprint-ready plans: replaced root ids, removed covered ids, retained input ids, planned added-node count, `plannedAddedNodeIds=not-allocated`, and disabled node-id allocation / graph rewrite / transformed-IR / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeNodeIdAllocationPreflight` adds a diagnostic allocation preview above transaction-ready plans: it records current graph max node id, deterministic candidate node ids, allocation-ready/blocked counts, first blocker, and disabled id reservation / allocator application / graph rewrite / transformed-IR / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeReplacementNodePreflight` adds the next diagnostic preview: candidate replacement-node id, target intrinsic-call kind/operation, argument ids/roles, ready/blocked counts, first blocker, and disabled replacement-node build / replacement-builder / graph rewrite / transformed-IR / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeGraphPatchPreflight` adds a diagnostic graph-patch preview: replacement node id, replaced/removed/retained/inserted node sets, ready/blocked counts, first blocker, and disabled patch application / graph rewrite / transformed-IR / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeTransformedGraphPreflight` adds the transformed-graph materialization preview above that patch: original IR identity, deterministic materialization key, `transformedGraphIdentity=not-built`, ready/blocked counts, first blocker, and disabled transformed-graph build / transformed-IR / patch application / graph rewrite / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeRewriteSketch` records the mutation-free structural rewrite skeleton around the validated replacement plan: ready sketches prove only that the future rewrite shape is describable, while `rewriteBuilderImplemented`, `mutationAllowed`, and `selectedIrReplacement` remain `false`. `rewriteSelection.*` fields add a read-only selection preflight over those sketches, including sketch/conflict counts, selection status, first blocker, proof/approval requirements, and disabled mutation/selection guardrails. `rewriteProof.*` fields add the matching proof preflight: proof status, first blocker, runtime-equivalence payload presence/completeness, rollback evidence, approval acceptance, original IR identity, absent transformed IR identity, and disabled selected-IR replacement. `rewriteReviewPackage.*` fields add the manual-review package boundary above proof readiness: package required/complete state, conflict count, proof acceptance, runtime-equivalence payload, rollback, approval, manual-review-only state, and disabled mutation / selection / selected-IR replacement. The same visitor/blueprint/transaction/allocation/replacement-node/graph-patch/transformed-graph/selection/proof/package preflight fields are also emitted under `rule.N.*`, making rule-family blockers visible without enabling any rewrite. Proof fields expose the candidate root node, covered nodes, input nodes, completeness, first blocker, structural validation counters, visitor counters, blueprint counters, transaction counters, allocation counters, replacement-node counters, graph-patch counters, transformed-graph counters, sketch counters, selection preflight fields, proof preflight fields, and review-package fields. `runtime-optimizer-drift.properties` aggregates complete/partial replacement-plan counts, plan-validation total/valid/invalid counts, rewrite-visitor ready/blocked counts, replacement-blueprint ready/blocked counts, rewrite-transaction ready/blocked counts, node-id allocation ready/blocked counts, replacement-node preview ready/blocked counts, graph-patch preview ready/blocked counts, transformed-graph materialization ready/blocked counts, rewrite-sketch ready/blocked counts, conflict counts, selection-readiness guardrails, proof/runtime-equivalence/rollback blockers, package-completeness blockers, first incomplete/invalid/visitor/blueprint/transaction/allocation/replacement-node/graph-patch/transformed-graph/sketch/selection/proof/package blockers, and compact `optimizerRule.*` summaries from existing `rule.N.*` proof fields so CI can detect planning drift while the rewrite engine remains absent. Workload gates carry the same visitor/blueprint/transaction/allocation/replacement-node/graph-patch/transformed-graph fields forward as evidence-only telemetry. These plans, visitors, blueprints, transactions, allocation previews, replacement-node previews, graph-patch previews, transformed-graph previews, and sketches describe what a future structural rewrite would need to replace; they do not edit typed bodies, reserve node ids, build replacement nodes, apply graph patches, build transformed graphs, apply allocators, rewrite graphs, select optimized IR, or enable production mutation. + +`GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight` adds the next evidence-only boundary above transformed-graph materialization. It records the metadata, proof, and rollback anchors a future optimized IR artifact would require, including deterministic envelope keys and aggregate / `rule.N.irArtifactEnvelope.*` ready or blocked counts. Drift artifacts and workload gates now carry these fields, but `artifactEnvelopeBuilt`, `optimizedArtifactBuilt`, `transformedIrBuilt`, `mutationAllowed`, and `selectedIrReplacement` remain `false`. The same index records `familyBinding.*`. Binding remains `not-bound` unless exactly one optimizer family has passed runtime evidence whose comparison mode is explicitly family-specific (`optimizer-family::...`). Descriptor-versus-reconstructed-source evidence is pipeline validation and is deliberately rejected as optimizer-family proof. diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatter.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatter.java index 7878c333..1797e39b 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatter.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatter.java @@ -461,6 +461,90 @@ private static void copyRuntimeOptimizerDriftProperties(Properties source, Prope target.setProperty("runtimeOptimizerDrift.replacementPlan.validation.valid.count", "0"); target.setProperty("runtimeOptimizerDrift.replacementPlan.validation.invalid.count", "0"); target.setProperty("runtimeOptimizerDrift.replacementPlan.validation.firstBlocker", "none"); + target.setProperty("runtimeOptimizerDrift.rewriteVisitor.count", "0"); + target.setProperty("runtimeOptimizerDrift.rewriteVisitor.ready.count", "0"); + target.setProperty("runtimeOptimizerDrift.rewriteVisitor.blocked.count", "0"); + target.setProperty("runtimeOptimizerDrift.rewriteVisitor.firstBlocker", "none"); + target.setProperty("runtimeOptimizerDrift.rewriteVisitor.visitorImplemented", "true"); + target.setProperty("runtimeOptimizerDrift.rewriteVisitor.replacementBuilderImplemented", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteVisitor.transformedIrBuilt", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteVisitor.mutationAllowed", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteVisitor.selectedIrReplacement", "false"); + target.setProperty("runtimeOptimizerDrift.replacementBlueprint.count", "0"); + target.setProperty("runtimeOptimizerDrift.replacementBlueprint.ready.count", "0"); + target.setProperty("runtimeOptimizerDrift.replacementBlueprint.blocked.count", "0"); + target.setProperty("runtimeOptimizerDrift.replacementBlueprint.firstBlocker", "none"); + target.setProperty("runtimeOptimizerDrift.replacementBlueprint.blueprintImplemented", "true"); + target.setProperty("runtimeOptimizerDrift.replacementBlueprint.replacementBuilderImplemented", "false"); + target.setProperty("runtimeOptimizerDrift.replacementBlueprint.transformedIrBuilt", "false"); + target.setProperty("runtimeOptimizerDrift.replacementBlueprint.mutationAllowed", "false"); + target.setProperty("runtimeOptimizerDrift.replacementBlueprint.selectedIrReplacement", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteTransaction.count", "0"); + target.setProperty("runtimeOptimizerDrift.rewriteTransaction.ready.count", "0"); + target.setProperty("runtimeOptimizerDrift.rewriteTransaction.blocked.count", "0"); + target.setProperty("runtimeOptimizerDrift.rewriteTransaction.firstBlocker", "none"); + target.setProperty("runtimeOptimizerDrift.rewriteTransaction.transactionPreflightImplemented", "true"); + target.setProperty("runtimeOptimizerDrift.rewriteTransaction.nodeIdAllocatorImplemented", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteTransaction.graphRewriteImplemented", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteTransaction.transformedIrBuilt", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteTransaction.mutationAllowed", "false"); + target.setProperty("runtimeOptimizerDrift.rewriteTransaction.selectedIrReplacement", "false"); + target.setProperty("runtimeOptimizerDrift.nodeIdAllocation.count", "0"); + target.setProperty("runtimeOptimizerDrift.nodeIdAllocation.ready.count", "0"); + target.setProperty("runtimeOptimizerDrift.nodeIdAllocation.blocked.count", "0"); + target.setProperty("runtimeOptimizerDrift.nodeIdAllocation.firstBlocker", "none"); + target.setProperty("runtimeOptimizerDrift.nodeIdAllocation.allocationPreflightImplemented", "true"); + target.setProperty("runtimeOptimizerDrift.nodeIdAllocation.nodeIdsReserved", "false"); + target.setProperty("runtimeOptimizerDrift.nodeIdAllocation.nodeIdAllocatorApplied", "false"); + target.setProperty("runtimeOptimizerDrift.nodeIdAllocation.graphRewriteImplemented", "false"); + target.setProperty("runtimeOptimizerDrift.nodeIdAllocation.transformedIrBuilt", "false"); + target.setProperty("runtimeOptimizerDrift.nodeIdAllocation.mutationAllowed", "false"); + target.setProperty("runtimeOptimizerDrift.nodeIdAllocation.selectedIrReplacement", "false"); + target.setProperty("runtimeOptimizerDrift.replacementNode.count", "0"); + target.setProperty("runtimeOptimizerDrift.replacementNode.ready.count", "0"); + target.setProperty("runtimeOptimizerDrift.replacementNode.blocked.count", "0"); + target.setProperty("runtimeOptimizerDrift.replacementNode.firstBlocker", "none"); + target.setProperty("runtimeOptimizerDrift.replacementNode.replacementNodePreflightImplemented", "true"); + target.setProperty("runtimeOptimizerDrift.replacementNode.replacementNodeBuilt", "false"); + target.setProperty("runtimeOptimizerDrift.replacementNode.replacementBuilderImplemented", "false"); + target.setProperty("runtimeOptimizerDrift.replacementNode.graphRewriteImplemented", "false"); + target.setProperty("runtimeOptimizerDrift.replacementNode.transformedIrBuilt", "false"); + target.setProperty("runtimeOptimizerDrift.replacementNode.mutationAllowed", "false"); + target.setProperty("runtimeOptimizerDrift.replacementNode.selectedIrReplacement", "false"); + target.setProperty("runtimeOptimizerDrift.graphPatch.count", "0"); + target.setProperty("runtimeOptimizerDrift.graphPatch.ready.count", "0"); + target.setProperty("runtimeOptimizerDrift.graphPatch.blocked.count", "0"); + target.setProperty("runtimeOptimizerDrift.graphPatch.firstBlocker", "none"); + target.setProperty("runtimeOptimizerDrift.graphPatch.graphPatchPreflightImplemented", "true"); + target.setProperty("runtimeOptimizerDrift.graphPatch.graphPatchApplied", "false"); + target.setProperty("runtimeOptimizerDrift.graphPatch.graphRewriteImplemented", "false"); + target.setProperty("runtimeOptimizerDrift.graphPatch.transformedIrBuilt", "false"); + target.setProperty("runtimeOptimizerDrift.graphPatch.mutationAllowed", "false"); + target.setProperty("runtimeOptimizerDrift.graphPatch.selectedIrReplacement", "false"); + target.setProperty("runtimeOptimizerDrift.transformedGraph.count", "0"); + target.setProperty("runtimeOptimizerDrift.transformedGraph.ready.count", "0"); + target.setProperty("runtimeOptimizerDrift.transformedGraph.blocked.count", "0"); + target.setProperty("runtimeOptimizerDrift.transformedGraph.firstBlocker", "none"); + target.setProperty("runtimeOptimizerDrift.transformedGraph.materializationPreflightImplemented", "true"); + target.setProperty("runtimeOptimizerDrift.transformedGraph.transformedGraphBuilt", "false"); + target.setProperty("runtimeOptimizerDrift.transformedGraph.transformedIrBuilt", "false"); + target.setProperty("runtimeOptimizerDrift.transformedGraph.graphPatchApplied", "false"); + target.setProperty("runtimeOptimizerDrift.transformedGraph.graphRewriteImplemented", "false"); + target.setProperty("runtimeOptimizerDrift.transformedGraph.mutationAllowed", "false"); + target.setProperty("runtimeOptimizerDrift.transformedGraph.selectedIrReplacement", "false"); + target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.count", "0"); + target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.ready.count", "0"); + target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.blocked.count", "0"); + target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.firstBlocker", "none"); + target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.artifactEnvelopePreflightImplemented", "true"); + target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.artifactEnvelopeBuilt", "false"); + target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.optimizedArtifactBuilt", "false"); + target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.transformedGraphBuilt", "false"); + target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.transformedIrBuilt", "false"); + target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.graphPatchApplied", "false"); + target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.graphRewriteImplemented", "false"); + target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.mutationAllowed", "false"); + target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.selectedIrReplacement", "false"); target.setProperty("runtimeOptimizerDrift.rewriteSketch.count", "0"); target.setProperty("runtimeOptimizerDrift.rewriteSketch.ready.count", "0"); target.setProperty("runtimeOptimizerDrift.rewriteSketch.blocked.count", "0"); @@ -543,6 +627,90 @@ private static void copyRuntimeOptimizerDriftProperties(Properties source, Prope copyRuntimeOptimizerDriftProperty(source, target, "replacementPlan.validation.valid.count", "0"); copyRuntimeOptimizerDriftProperty(source, target, "replacementPlan.validation.invalid.count", "0"); copyRuntimeOptimizerDriftProperty(source, target, "replacementPlan.validation.firstBlocker", "none"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteVisitor.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteVisitor.ready.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteVisitor.blocked.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteVisitor.firstBlocker", "none"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteVisitor.visitorImplemented", "true"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteVisitor.replacementBuilderImplemented", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteVisitor.transformedIrBuilt", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteVisitor.mutationAllowed", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteVisitor.selectedIrReplacement", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementBlueprint.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementBlueprint.ready.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementBlueprint.blocked.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementBlueprint.firstBlocker", "none"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementBlueprint.blueprintImplemented", "true"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementBlueprint.replacementBuilderImplemented", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementBlueprint.transformedIrBuilt", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementBlueprint.mutationAllowed", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementBlueprint.selectedIrReplacement", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteTransaction.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteTransaction.ready.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteTransaction.blocked.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteTransaction.firstBlocker", "none"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteTransaction.transactionPreflightImplemented", "true"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteTransaction.nodeIdAllocatorImplemented", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteTransaction.graphRewriteImplemented", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteTransaction.transformedIrBuilt", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteTransaction.mutationAllowed", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "rewriteTransaction.selectedIrReplacement", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "nodeIdAllocation.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "nodeIdAllocation.ready.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "nodeIdAllocation.blocked.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "nodeIdAllocation.firstBlocker", "none"); + copyRuntimeOptimizerDriftProperty(source, target, "nodeIdAllocation.allocationPreflightImplemented", "true"); + copyRuntimeOptimizerDriftProperty(source, target, "nodeIdAllocation.nodeIdsReserved", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "nodeIdAllocation.nodeIdAllocatorApplied", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "nodeIdAllocation.graphRewriteImplemented", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "nodeIdAllocation.transformedIrBuilt", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "nodeIdAllocation.mutationAllowed", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "nodeIdAllocation.selectedIrReplacement", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementNode.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementNode.ready.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementNode.blocked.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementNode.firstBlocker", "none"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementNode.replacementNodePreflightImplemented", "true"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementNode.replacementNodeBuilt", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementNode.replacementBuilderImplemented", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementNode.graphRewriteImplemented", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementNode.transformedIrBuilt", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementNode.mutationAllowed", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "replacementNode.selectedIrReplacement", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "graphPatch.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "graphPatch.ready.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "graphPatch.blocked.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "graphPatch.firstBlocker", "none"); + copyRuntimeOptimizerDriftProperty(source, target, "graphPatch.graphPatchPreflightImplemented", "true"); + copyRuntimeOptimizerDriftProperty(source, target, "graphPatch.graphPatchApplied", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "graphPatch.graphRewriteImplemented", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "graphPatch.transformedIrBuilt", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "graphPatch.mutationAllowed", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "graphPatch.selectedIrReplacement", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "transformedGraph.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "transformedGraph.ready.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "transformedGraph.blocked.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "transformedGraph.firstBlocker", "none"); + copyRuntimeOptimizerDriftProperty(source, target, "transformedGraph.materializationPreflightImplemented", "true"); + copyRuntimeOptimizerDriftProperty(source, target, "transformedGraph.transformedGraphBuilt", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "transformedGraph.transformedIrBuilt", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "transformedGraph.graphPatchApplied", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "transformedGraph.graphRewriteImplemented", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "transformedGraph.mutationAllowed", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "transformedGraph.selectedIrReplacement", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.ready.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.blocked.count", "0"); + copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.firstBlocker", "none"); + copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.artifactEnvelopePreflightImplemented", "true"); + copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.artifactEnvelopeBuilt", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.optimizedArtifactBuilt", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.transformedGraphBuilt", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.transformedIrBuilt", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.graphPatchApplied", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.graphRewriteImplemented", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.mutationAllowed", "false"); + copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.selectedIrReplacement", "false"); copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.count", "0"); copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.ready.count", "0"); copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.blocked.count", "0"); @@ -933,6 +1101,90 @@ private static void appendRuntimeOptimizerDrift(StringBuilder builder, String pr builder.append(prefix).append("runtimeOptimizerDrift.replacementPlan.validation.valid.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementPlan.validation.valid.count", "0")).append('\n'); builder.append(prefix).append("runtimeOptimizerDrift.replacementPlan.validation.invalid.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementPlan.validation.invalid.count", "0")).append('\n'); builder.append(prefix).append("runtimeOptimizerDrift.replacementPlan.validation.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.replacementPlan.validation.firstBlocker", "none")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteVisitor.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteVisitor.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteVisitor.ready.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteVisitor.ready.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteVisitor.blocked.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteVisitor.blocked.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteVisitor.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.rewriteVisitor.firstBlocker", "none")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteVisitor.visitorImplemented=").append(entry.getProperty("runtimeOptimizerDrift.rewriteVisitor.visitorImplemented", "true")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteVisitor.replacementBuilderImplemented=").append(entry.getProperty("runtimeOptimizerDrift.rewriteVisitor.replacementBuilderImplemented", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteVisitor.transformedIrBuilt=").append(entry.getProperty("runtimeOptimizerDrift.rewriteVisitor.transformedIrBuilt", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteVisitor.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.rewriteVisitor.mutationAllowed", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteVisitor.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.rewriteVisitor.selectedIrReplacement", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementBlueprint.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementBlueprint.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementBlueprint.ready.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementBlueprint.ready.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementBlueprint.blocked.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementBlueprint.blocked.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementBlueprint.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.replacementBlueprint.firstBlocker", "none")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementBlueprint.blueprintImplemented=").append(entry.getProperty("runtimeOptimizerDrift.replacementBlueprint.blueprintImplemented", "true")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementBlueprint.replacementBuilderImplemented=").append(entry.getProperty("runtimeOptimizerDrift.replacementBlueprint.replacementBuilderImplemented", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementBlueprint.transformedIrBuilt=").append(entry.getProperty("runtimeOptimizerDrift.replacementBlueprint.transformedIrBuilt", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementBlueprint.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.replacementBlueprint.mutationAllowed", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementBlueprint.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.replacementBlueprint.selectedIrReplacement", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteTransaction.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteTransaction.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteTransaction.ready.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteTransaction.ready.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteTransaction.blocked.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteTransaction.blocked.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteTransaction.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.rewriteTransaction.firstBlocker", "none")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteTransaction.transactionPreflightImplemented=").append(entry.getProperty("runtimeOptimizerDrift.rewriteTransaction.transactionPreflightImplemented", "true")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteTransaction.nodeIdAllocatorImplemented=").append(entry.getProperty("runtimeOptimizerDrift.rewriteTransaction.nodeIdAllocatorImplemented", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteTransaction.graphRewriteImplemented=").append(entry.getProperty("runtimeOptimizerDrift.rewriteTransaction.graphRewriteImplemented", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteTransaction.transformedIrBuilt=").append(entry.getProperty("runtimeOptimizerDrift.rewriteTransaction.transformedIrBuilt", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteTransaction.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.rewriteTransaction.mutationAllowed", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.rewriteTransaction.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.rewriteTransaction.selectedIrReplacement", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.nodeIdAllocation.count=").append(entry.getProperty("runtimeOptimizerDrift.nodeIdAllocation.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.nodeIdAllocation.ready.count=").append(entry.getProperty("runtimeOptimizerDrift.nodeIdAllocation.ready.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.nodeIdAllocation.blocked.count=").append(entry.getProperty("runtimeOptimizerDrift.nodeIdAllocation.blocked.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.nodeIdAllocation.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.nodeIdAllocation.firstBlocker", "none")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.nodeIdAllocation.allocationPreflightImplemented=").append(entry.getProperty("runtimeOptimizerDrift.nodeIdAllocation.allocationPreflightImplemented", "true")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.nodeIdAllocation.nodeIdsReserved=").append(entry.getProperty("runtimeOptimizerDrift.nodeIdAllocation.nodeIdsReserved", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.nodeIdAllocation.nodeIdAllocatorApplied=").append(entry.getProperty("runtimeOptimizerDrift.nodeIdAllocation.nodeIdAllocatorApplied", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.nodeIdAllocation.graphRewriteImplemented=").append(entry.getProperty("runtimeOptimizerDrift.nodeIdAllocation.graphRewriteImplemented", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.nodeIdAllocation.transformedIrBuilt=").append(entry.getProperty("runtimeOptimizerDrift.nodeIdAllocation.transformedIrBuilt", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.nodeIdAllocation.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.nodeIdAllocation.mutationAllowed", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.nodeIdAllocation.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.nodeIdAllocation.selectedIrReplacement", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementNode.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementNode.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementNode.ready.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementNode.ready.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementNode.blocked.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementNode.blocked.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementNode.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.replacementNode.firstBlocker", "none")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementNode.replacementNodePreflightImplemented=").append(entry.getProperty("runtimeOptimizerDrift.replacementNode.replacementNodePreflightImplemented", "true")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementNode.replacementNodeBuilt=").append(entry.getProperty("runtimeOptimizerDrift.replacementNode.replacementNodeBuilt", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementNode.replacementBuilderImplemented=").append(entry.getProperty("runtimeOptimizerDrift.replacementNode.replacementBuilderImplemented", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementNode.graphRewriteImplemented=").append(entry.getProperty("runtimeOptimizerDrift.replacementNode.graphRewriteImplemented", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementNode.transformedIrBuilt=").append(entry.getProperty("runtimeOptimizerDrift.replacementNode.transformedIrBuilt", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementNode.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.replacementNode.mutationAllowed", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.replacementNode.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.replacementNode.selectedIrReplacement", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.graphPatch.count=").append(entry.getProperty("runtimeOptimizerDrift.graphPatch.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.graphPatch.ready.count=").append(entry.getProperty("runtimeOptimizerDrift.graphPatch.ready.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.graphPatch.blocked.count=").append(entry.getProperty("runtimeOptimizerDrift.graphPatch.blocked.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.graphPatch.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.graphPatch.firstBlocker", "none")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.graphPatch.graphPatchPreflightImplemented=").append(entry.getProperty("runtimeOptimizerDrift.graphPatch.graphPatchPreflightImplemented", "true")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.graphPatch.graphPatchApplied=").append(entry.getProperty("runtimeOptimizerDrift.graphPatch.graphPatchApplied", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.graphPatch.graphRewriteImplemented=").append(entry.getProperty("runtimeOptimizerDrift.graphPatch.graphRewriteImplemented", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.graphPatch.transformedIrBuilt=").append(entry.getProperty("runtimeOptimizerDrift.graphPatch.transformedIrBuilt", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.graphPatch.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.graphPatch.mutationAllowed", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.graphPatch.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.graphPatch.selectedIrReplacement", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.transformedGraph.count=").append(entry.getProperty("runtimeOptimizerDrift.transformedGraph.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.transformedGraph.ready.count=").append(entry.getProperty("runtimeOptimizerDrift.transformedGraph.ready.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.transformedGraph.blocked.count=").append(entry.getProperty("runtimeOptimizerDrift.transformedGraph.blocked.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.transformedGraph.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.transformedGraph.firstBlocker", "none")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.transformedGraph.materializationPreflightImplemented=").append(entry.getProperty("runtimeOptimizerDrift.transformedGraph.materializationPreflightImplemented", "true")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.transformedGraph.transformedGraphBuilt=").append(entry.getProperty("runtimeOptimizerDrift.transformedGraph.transformedGraphBuilt", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.transformedGraph.transformedIrBuilt=").append(entry.getProperty("runtimeOptimizerDrift.transformedGraph.transformedIrBuilt", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.transformedGraph.graphPatchApplied=").append(entry.getProperty("runtimeOptimizerDrift.transformedGraph.graphPatchApplied", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.transformedGraph.graphRewriteImplemented=").append(entry.getProperty("runtimeOptimizerDrift.transformedGraph.graphRewriteImplemented", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.transformedGraph.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.transformedGraph.mutationAllowed", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.transformedGraph.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.transformedGraph.selectedIrReplacement", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.count=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.ready.count=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.ready.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.blocked.count=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.blocked.count", "0")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.firstBlocker", "none")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.artifactEnvelopePreflightImplemented=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.artifactEnvelopePreflightImplemented", "true")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.artifactEnvelopeBuilt=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.artifactEnvelopeBuilt", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.optimizedArtifactBuilt=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.optimizedArtifactBuilt", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.transformedGraphBuilt=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.transformedGraphBuilt", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.transformedIrBuilt=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.transformedIrBuilt", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.graphPatchApplied=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.graphPatchApplied", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.graphRewriteImplemented=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.graphRewriteImplemented", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.mutationAllowed", "false")).append('\n'); + builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.selectedIrReplacement", "false")).append('\n'); builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.count", "0")).append('\n'); builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.ready.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.ready.count", "0")).append('\n'); builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.blocked.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.blocked.count", "0")).append('\n'); diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeGraphPatchPreflight.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeGraphPatchPreflight.java new file mode 100644 index 00000000..9dbee59f --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeGraphPatchPreflight.java @@ -0,0 +1,244 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Read-only graph patch preview for a future typed peephole rewrite. + * + *

The patch records the replace/remove/retain/insert sets that a graph rewrite would need. It never applies the + * patch, rewrites the graph, builds transformed IR, or selects optimized IR.

+ */ +public record GpuRuntimeIrPeepholeGraphPatchPreflight( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + String replacementNodeId, + List replacedNodeIds, + List removedNodeIds, + List retainedInputNodeIds, + List insertedNodeIds, + boolean transactionReady, + boolean replacementNodeReady, + boolean graphPatchReady, + String status, + String firstBlocker, + boolean graphPatchPreflightImplemented, + boolean graphPatchApplied, + boolean graphRewriteImplemented, + boolean transformedIrBuilt, + boolean mutationAllowed, + boolean selectedIrReplacement +) { + + public GpuRuntimeIrPeepholeGraphPatchPreflight { + ruleId = normalize(ruleId, "rule:unknown"); + methodName = normalize(methodName, "unknown"); + rootNodeId = Math.max(0, rootNodeId); + replacementKind = normalize(replacementKind, "unknown"); + replacementNodeId = normalize(replacementNodeId, "none"); + replacedNodeIds = normalizeNodeIds(replacedNodeIds); + removedNodeIds = normalizeNodeIds(removedNodeIds); + retainedInputNodeIds = normalizeNodeIds(retainedInputNodeIds); + insertedNodeIds = normalizeNodeIds(insertedNodeIds); + status = normalize(status, graphPatchReady ? "graph-patch-ready" : "blocked"); + firstBlocker = normalize(firstBlocker, graphPatchReady ? "none" : "graph-patch-blocked"); + } + + public static GpuRuntimeIrPeepholeGraphPatchPreflight from( + GpuRuntimeIrPeepholeRewriteTransactionPreflight transaction, + GpuRuntimeIrPeepholeReplacementNodePreflight replacementNode + ) { + if (transaction == null) { + return blocked( + "rule:unknown", + "unknown", + 0, + "unknown", + "none", + List.of(), + List.of(), + List.of(), + false, + false, + "rewrite-transaction-missing" + ); + } + if (replacementNode == null) { + return blockedFrom(transaction, "none", List.of(), false, "replacement-node-missing"); + } + if (!transaction.transactionReady()) { + return blockedFrom(transaction, replacementNode.candidateReplacementNodeId(), List.of(), false, transaction.firstBlocker()); + } + if (!replacementNode.replacementNodeReady()) { + return blockedFrom(transaction, replacementNode.candidateReplacementNodeId(), List.of(), false, replacementNode.firstBlocker()); + } + int replacementNodeId = parseNodeId(replacementNode.candidateReplacementNodeId()); + if (replacementNodeId < 0) { + return blockedFrom(transaction, replacementNode.candidateReplacementNodeId(), List.of(), true, "candidate-replacement-node-id-missing"); + } + return new GpuRuntimeIrPeepholeGraphPatchPreflight( + transaction.ruleId(), + transaction.methodName(), + transaction.rootNodeId(), + transaction.replacementKind(), + Integer.toString(replacementNodeId), + transaction.replacedNodeIds(), + transaction.removedNodeIds(), + transaction.retainedInputNodeIds(), + List.of(replacementNodeId), + true, + true, + true, + "graph-patch-ready", + "none", + true, + false, + false, + false, + false, + false + ); + } + + public Map fields(String prefix) { + String normalizedPrefix = normalize(prefix, "graphPatch"); + return Map.ofEntries( + Map.entry(normalizedPrefix + ".ruleId", ruleId), + Map.entry(normalizedPrefix + ".methodName", methodName), + Map.entry(normalizedPrefix + ".rootNodeId", Integer.toString(rootNodeId)), + Map.entry(normalizedPrefix + ".replacementKind", replacementKind), + Map.entry(normalizedPrefix + ".replacementNodeId", replacementNodeId), + Map.entry(normalizedPrefix + ".replacedNodeIds", join(replacedNodeIds)), + Map.entry(normalizedPrefix + ".removedNodeIds", join(removedNodeIds)), + Map.entry(normalizedPrefix + ".retainedInputNodeIds", join(retainedInputNodeIds)), + Map.entry(normalizedPrefix + ".insertedNodeIds", join(insertedNodeIds)), + Map.entry(normalizedPrefix + ".transactionReady", Boolean.toString(transactionReady)), + Map.entry(normalizedPrefix + ".replacementNodeReady", Boolean.toString(replacementNodeReady)), + Map.entry(normalizedPrefix + ".graphPatchReady", Boolean.toString(graphPatchReady)), + Map.entry(normalizedPrefix + ".status", status), + Map.entry(normalizedPrefix + ".firstBlocker", firstBlocker), + Map.entry(normalizedPrefix + ".graphPatchPreflightImplemented", Boolean.toString(graphPatchPreflightImplemented)), + Map.entry(normalizedPrefix + ".graphPatchApplied", Boolean.toString(graphPatchApplied)), + Map.entry(normalizedPrefix + ".graphRewriteImplemented", Boolean.toString(graphRewriteImplemented)), + Map.entry(normalizedPrefix + ".transformedIrBuilt", Boolean.toString(transformedIrBuilt)), + Map.entry(normalizedPrefix + ".mutationAllowed", Boolean.toString(mutationAllowed)), + Map.entry(normalizedPrefix + ".selectedIrReplacement", Boolean.toString(selectedIrReplacement)) + ); + } + + private static GpuRuntimeIrPeepholeGraphPatchPreflight blockedFrom( + GpuRuntimeIrPeepholeRewriteTransactionPreflight transaction, + String replacementNodeId, + List insertedNodeIds, + boolean replacementNodeReady, + String firstBlocker + ) { + return blocked( + transaction.ruleId(), + transaction.methodName(), + transaction.rootNodeId(), + transaction.replacementKind(), + replacementNodeId, + transaction.replacedNodeIds(), + transaction.removedNodeIds(), + transaction.retainedInputNodeIds(), + transaction.transactionReady(), + replacementNodeReady, + firstBlocker + ).withInsertedNodeIds(insertedNodeIds); + } + + private GpuRuntimeIrPeepholeGraphPatchPreflight withInsertedNodeIds(List insertedNodeIds) { + return new GpuRuntimeIrPeepholeGraphPatchPreflight( + ruleId, + methodName, + rootNodeId, + replacementKind, + replacementNodeId, + replacedNodeIds, + removedNodeIds, + retainedInputNodeIds, + insertedNodeIds, + transactionReady, + replacementNodeReady, + false, + "blocked", + firstBlocker, + true, + false, + false, + false, + false, + false + ); + } + + private static GpuRuntimeIrPeepholeGraphPatchPreflight blocked( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + String replacementNodeId, + List replacedNodeIds, + List removedNodeIds, + List retainedInputNodeIds, + boolean transactionReady, + boolean replacementNodeReady, + String firstBlocker + ) { + return new GpuRuntimeIrPeepholeGraphPatchPreflight( + ruleId, + methodName, + rootNodeId, + replacementKind, + replacementNodeId, + replacedNodeIds, + removedNodeIds, + retainedInputNodeIds, + List.of(), + transactionReady, + replacementNodeReady, + false, + "blocked", + firstBlocker, + true, + false, + false, + false, + false, + false + ); + } + + private static int parseNodeId(String value) { + try { + return Math.max(-1, Integer.parseInt(value == null ? "-1" : value)); + } catch (NumberFormatException ignored) { + return -1; + } + } + + private static List normalizeNodeIds(List ids) { + if (ids == null || ids.isEmpty()) { + return List.of(); + } + return ids.stream() + .filter(id -> id != null && id >= 0) + .distinct() + .toList(); + } + + private static String join(List ids) { + if (ids == null || ids.isEmpty()) { + return "none"; + } + return ids.stream().map(String::valueOf).collect(Collectors.joining(",")); + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight.java new file mode 100644 index 00000000..0cb23674 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight.java @@ -0,0 +1,313 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Read-only optimized IR artifact envelope preview for a future peephole rewrite. + * + *

The preview records the metadata, proof, and rollback anchors a future optimized artifact must carry. It never + * builds an optimized artifact, builds transformed IR, applies graph patches, or selects optimized IR.

+ */ +public record GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + String originalIrIdentity, + String transformedGraphIdentity, + String optimizedArtifactIdentity, + String materializationKey, + String envelopeKey, + String proofAnchor, + String rollbackAnchor, + String replacementNodeId, + List replacedNodeIds, + List removedNodeIds, + List retainedInputNodeIds, + List insertedNodeIds, + boolean materializationReady, + boolean metadataReady, + boolean proofSlotReady, + boolean rollbackPlanReady, + boolean artifactEnvelopeReady, + String status, + String firstBlocker, + boolean artifactEnvelopePreflightImplemented, + boolean artifactEnvelopeBuilt, + boolean optimizedArtifactBuilt, + boolean transformedGraphBuilt, + boolean transformedIrBuilt, + boolean graphPatchApplied, + boolean graphRewriteImplemented, + boolean mutationAllowed, + boolean selectedIrReplacement +) { + + public GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight { + ruleId = normalize(ruleId, "rule:unknown"); + methodName = normalize(methodName, "unknown"); + rootNodeId = Math.max(0, rootNodeId); + replacementKind = normalize(replacementKind, "unknown"); + originalIrIdentity = normalize(originalIrIdentity, "irgpu:unknown"); + transformedGraphIdentity = normalize(transformedGraphIdentity, "not-built"); + optimizedArtifactIdentity = normalize(optimizedArtifactIdentity, "not-built"); + materializationKey = normalize(materializationKey, "not-built"); + proofAnchor = normalize(proofAnchor, "runtime-equivalence-required"); + rollbackAnchor = normalize(rollbackAnchor, "original-ir"); + replacementNodeId = normalize(replacementNodeId, "none"); + replacedNodeIds = normalizeNodeIds(replacedNodeIds); + removedNodeIds = normalizeNodeIds(removedNodeIds); + retainedInputNodeIds = normalizeNodeIds(retainedInputNodeIds); + insertedNodeIds = normalizeNodeIds(insertedNodeIds); + envelopeKey = normalize(envelopeKey, envelopeKey( + ruleId, + methodName, + rootNodeId, + replacementKind, + originalIrIdentity, + transformedGraphIdentity, + materializationKey, + proofAnchor, + rollbackAnchor + )); + status = normalize(status, artifactEnvelopeReady ? "artifact-envelope-ready" : "blocked"); + firstBlocker = normalize(firstBlocker, artifactEnvelopeReady ? "none" : "ir-artifact-envelope-blocked"); + } + + public static GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight from( + GpuRuntimeIrPeepholeTransformedGraphPreflight transformedGraph + ) { + if (transformedGraph == null) { + return blocked( + "rule:unknown", + "unknown", + 0, + "unknown", + "irgpu:unknown", + "not-built", + "not-built", + "none", + List.of(), + List.of(), + List.of(), + List.of(), + false, + "transformed-graph-missing" + ); + } + if (!transformedGraph.materializationReady()) { + return blocked( + transformedGraph.ruleId(), + transformedGraph.methodName(), + transformedGraph.rootNodeId(), + transformedGraph.replacementKind(), + transformedGraph.originalIrIdentity(), + transformedGraph.transformedGraphIdentity(), + transformedGraph.materializationKey(), + transformedGraph.replacementNodeId(), + transformedGraph.replacedNodeIds(), + transformedGraph.removedNodeIds(), + transformedGraph.retainedInputNodeIds(), + transformedGraph.insertedNodeIds(), + false, + transformedGraph.firstBlocker() + ); + } + String proofAnchor = "runtime-equivalence-required"; + String rollbackAnchor = "original-ir"; + return new GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight( + transformedGraph.ruleId(), + transformedGraph.methodName(), + transformedGraph.rootNodeId(), + transformedGraph.replacementKind(), + transformedGraph.originalIrIdentity(), + transformedGraph.transformedGraphIdentity(), + "not-built", + transformedGraph.materializationKey(), + envelopeKey( + transformedGraph.ruleId(), + transformedGraph.methodName(), + transformedGraph.rootNodeId(), + transformedGraph.replacementKind(), + transformedGraph.originalIrIdentity(), + transformedGraph.transformedGraphIdentity(), + transformedGraph.materializationKey(), + proofAnchor, + rollbackAnchor + ), + proofAnchor, + rollbackAnchor, + transformedGraph.replacementNodeId(), + transformedGraph.replacedNodeIds(), + transformedGraph.removedNodeIds(), + transformedGraph.retainedInputNodeIds(), + transformedGraph.insertedNodeIds(), + true, + true, + true, + true, + true, + "artifact-envelope-ready", + "none", + true, + false, + false, + false, + false, + false, + false, + false, + false + ); + } + + public Map fields(String prefix) { + String normalizedPrefix = normalize(prefix, "irArtifactEnvelope"); + return Map.ofEntries( + Map.entry(normalizedPrefix + ".ruleId", ruleId), + Map.entry(normalizedPrefix + ".methodName", methodName), + Map.entry(normalizedPrefix + ".rootNodeId", Integer.toString(rootNodeId)), + Map.entry(normalizedPrefix + ".replacementKind", replacementKind), + Map.entry(normalizedPrefix + ".originalIrIdentity", originalIrIdentity), + Map.entry(normalizedPrefix + ".transformedGraphIdentity", transformedGraphIdentity), + Map.entry(normalizedPrefix + ".optimizedArtifactIdentity", optimizedArtifactIdentity), + Map.entry(normalizedPrefix + ".materializationKey", materializationKey), + Map.entry(normalizedPrefix + ".envelopeKey", envelopeKey), + Map.entry(normalizedPrefix + ".proofAnchor", proofAnchor), + Map.entry(normalizedPrefix + ".rollbackAnchor", rollbackAnchor), + Map.entry(normalizedPrefix + ".replacementNodeId", replacementNodeId), + Map.entry(normalizedPrefix + ".replacedNodeIds", join(replacedNodeIds)), + Map.entry(normalizedPrefix + ".removedNodeIds", join(removedNodeIds)), + Map.entry(normalizedPrefix + ".retainedInputNodeIds", join(retainedInputNodeIds)), + Map.entry(normalizedPrefix + ".insertedNodeIds", join(insertedNodeIds)), + Map.entry(normalizedPrefix + ".materializationReady", Boolean.toString(materializationReady)), + Map.entry(normalizedPrefix + ".metadataReady", Boolean.toString(metadataReady)), + Map.entry(normalizedPrefix + ".proofSlotReady", Boolean.toString(proofSlotReady)), + Map.entry(normalizedPrefix + ".rollbackPlanReady", Boolean.toString(rollbackPlanReady)), + Map.entry(normalizedPrefix + ".artifactEnvelopeReady", Boolean.toString(artifactEnvelopeReady)), + Map.entry(normalizedPrefix + ".status", status), + Map.entry(normalizedPrefix + ".firstBlocker", firstBlocker), + Map.entry(normalizedPrefix + ".artifactEnvelopePreflightImplemented", Boolean.toString(artifactEnvelopePreflightImplemented)), + Map.entry(normalizedPrefix + ".artifactEnvelopeBuilt", Boolean.toString(artifactEnvelopeBuilt)), + Map.entry(normalizedPrefix + ".optimizedArtifactBuilt", Boolean.toString(optimizedArtifactBuilt)), + Map.entry(normalizedPrefix + ".transformedGraphBuilt", Boolean.toString(transformedGraphBuilt)), + Map.entry(normalizedPrefix + ".transformedIrBuilt", Boolean.toString(transformedIrBuilt)), + Map.entry(normalizedPrefix + ".graphPatchApplied", Boolean.toString(graphPatchApplied)), + Map.entry(normalizedPrefix + ".graphRewriteImplemented", Boolean.toString(graphRewriteImplemented)), + Map.entry(normalizedPrefix + ".mutationAllowed", Boolean.toString(mutationAllowed)), + Map.entry(normalizedPrefix + ".selectedIrReplacement", Boolean.toString(selectedIrReplacement)) + ); + } + + private static GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight blocked( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + String originalIrIdentity, + String transformedGraphIdentity, + String materializationKey, + String replacementNodeId, + List replacedNodeIds, + List removedNodeIds, + List retainedInputNodeIds, + List insertedNodeIds, + boolean materializationReady, + String firstBlocker + ) { + String proofAnchor = "runtime-equivalence-required"; + String rollbackAnchor = "original-ir"; + return new GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight( + ruleId, + methodName, + rootNodeId, + replacementKind, + originalIrIdentity, + transformedGraphIdentity, + "not-built", + materializationKey, + envelopeKey( + ruleId, + methodName, + rootNodeId, + replacementKind, + originalIrIdentity, + transformedGraphIdentity, + materializationKey, + proofAnchor, + rollbackAnchor + ), + proofAnchor, + rollbackAnchor, + replacementNodeId, + replacedNodeIds, + removedNodeIds, + retainedInputNodeIds, + insertedNodeIds, + materializationReady, + false, + false, + false, + false, + "blocked", + firstBlocker, + true, + false, + false, + false, + false, + false, + false, + false, + false + ); + } + + private static String envelopeKey( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + String originalIrIdentity, + String transformedGraphIdentity, + String materializationKey, + String proofAnchor, + String rollbackAnchor + ) { + return String.join( + "|", + normalize(ruleId, "rule:unknown"), + normalize(methodName, "unknown"), + Integer.toString(Math.max(0, rootNodeId)), + normalize(replacementKind, "unknown"), + "original=" + normalize(originalIrIdentity, "irgpu:unknown"), + "graph=" + normalize(transformedGraphIdentity, "not-built"), + "materialization=" + normalize(materializationKey, "not-built"), + "proof=" + normalize(proofAnchor, "runtime-equivalence-required"), + "rollback=" + normalize(rollbackAnchor, "original-ir") + ); + } + + private static List normalizeNodeIds(List ids) { + if (ids == null || ids.isEmpty()) { + return List.of(); + } + return ids.stream() + .filter(id -> id != null && id >= 0) + .distinct() + .toList(); + } + + private static String join(List ids) { + if (ids == null || ids.isEmpty()) { + return "none"; + } + return ids.stream().map(String::valueOf).collect(Collectors.joining(",")); + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeNodeIdAllocationPreflight.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeNodeIdAllocationPreflight.java new file mode 100644 index 00000000..7c054c1f --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeNodeIdAllocationPreflight.java @@ -0,0 +1,194 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * Read-only node-id allocation preview for a future peephole rewrite transaction. + * + *

The candidate ids are deterministic diagnostics only. They are not reserved, inserted, or selected.

+ */ +public record GpuRuntimeIrPeepholeNodeIdAllocationPreflight( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + int graphNodeMaxId, + int plannedAddedNodeCount, + List candidateNodeIds, + boolean transactionReady, + boolean allocationReady, + String status, + String firstBlocker, + boolean allocationPreflightImplemented, + boolean nodeIdsReserved, + boolean nodeIdAllocatorApplied, + boolean graphRewriteImplemented, + boolean transformedIrBuilt, + boolean mutationAllowed, + boolean selectedIrReplacement +) { + + public GpuRuntimeIrPeepholeNodeIdAllocationPreflight { + ruleId = normalize(ruleId, "rule:unknown"); + methodName = normalize(methodName, "unknown"); + rootNodeId = Math.max(0, rootNodeId); + replacementKind = normalize(replacementKind, "unknown"); + graphNodeMaxId = Math.max(0, graphNodeMaxId); + plannedAddedNodeCount = Math.max(0, plannedAddedNodeCount); + candidateNodeIds = normalizeNodeIds(candidateNodeIds); + status = normalize(status, allocationReady ? "allocation-ready" : "blocked"); + firstBlocker = normalize(firstBlocker, allocationReady ? "none" : "node-id-allocation-blocked"); + } + + public static GpuRuntimeIrPeepholeNodeIdAllocationPreflight from( + GpuRuntimeIrPeepholeRewriteTransactionPreflight transaction, + GpuRuntimeIrPeepholeRewriteVisitPreflight visitPreflight + ) { + if (transaction == null) { + return blocked( + "rule:unknown", + "unknown", + 0, + "unknown", + 0, + 0, + false, + "rewrite-transaction-missing" + ); + } + int graphMax = visitPreflight == null ? 0 : visitPreflight.graphNodeMaxId(); + if (!transaction.transactionReady()) { + return blocked( + transaction.ruleId(), + transaction.methodName(), + transaction.rootNodeId(), + transaction.replacementKind(), + graphMax, + transaction.plannedAddedNodeCount(), + false, + transaction.firstBlocker() + ); + } + if (transaction.plannedAddedNodeCount() <= 0) { + return blocked( + transaction.ruleId(), + transaction.methodName(), + transaction.rootNodeId(), + transaction.replacementKind(), + graphMax, + 0, + true, + "no-added-nodes-planned" + ); + } + return new GpuRuntimeIrPeepholeNodeIdAllocationPreflight( + transaction.ruleId(), + transaction.methodName(), + transaction.rootNodeId(), + transaction.replacementKind(), + graphMax, + transaction.plannedAddedNodeCount(), + candidateIds(graphMax, transaction.plannedAddedNodeCount()), + true, + true, + "allocation-ready", + "none", + true, + false, + false, + false, + false, + false, + false + ); + } + + public Map fields(String prefix) { + String normalizedPrefix = normalize(prefix, "nodeIdAllocation"); + return Map.ofEntries( + Map.entry(normalizedPrefix + ".ruleId", ruleId), + Map.entry(normalizedPrefix + ".methodName", methodName), + Map.entry(normalizedPrefix + ".rootNodeId", Integer.toString(rootNodeId)), + Map.entry(normalizedPrefix + ".replacementKind", replacementKind), + Map.entry(normalizedPrefix + ".graphNodeMaxId", Integer.toString(graphNodeMaxId)), + Map.entry(normalizedPrefix + ".plannedAddedNode.count", Integer.toString(plannedAddedNodeCount)), + Map.entry(normalizedPrefix + ".candidateNodeIds", join(candidateNodeIds)), + Map.entry(normalizedPrefix + ".transactionReady", Boolean.toString(transactionReady)), + Map.entry(normalizedPrefix + ".allocationReady", Boolean.toString(allocationReady)), + Map.entry(normalizedPrefix + ".status", status), + Map.entry(normalizedPrefix + ".firstBlocker", firstBlocker), + Map.entry(normalizedPrefix + ".allocationPreflightImplemented", Boolean.toString(allocationPreflightImplemented)), + Map.entry(normalizedPrefix + ".nodeIdsReserved", Boolean.toString(nodeIdsReserved)), + Map.entry(normalizedPrefix + ".nodeIdAllocatorApplied", Boolean.toString(nodeIdAllocatorApplied)), + Map.entry(normalizedPrefix + ".graphRewriteImplemented", Boolean.toString(graphRewriteImplemented)), + Map.entry(normalizedPrefix + ".transformedIrBuilt", Boolean.toString(transformedIrBuilt)), + Map.entry(normalizedPrefix + ".mutationAllowed", Boolean.toString(mutationAllowed)), + Map.entry(normalizedPrefix + ".selectedIrReplacement", Boolean.toString(selectedIrReplacement)) + ); + } + + private static GpuRuntimeIrPeepholeNodeIdAllocationPreflight blocked( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + int graphNodeMaxId, + int plannedAddedNodeCount, + boolean transactionReady, + String firstBlocker + ) { + return new GpuRuntimeIrPeepholeNodeIdAllocationPreflight( + ruleId, + methodName, + rootNodeId, + replacementKind, + graphNodeMaxId, + plannedAddedNodeCount, + List.of(), + transactionReady, + false, + "blocked", + firstBlocker, + true, + false, + false, + false, + false, + false, + false + ); + } + + private static List candidateIds(int graphNodeMaxId, int count) { + if (count <= 0) { + return List.of(); + } + return IntStream.rangeClosed(graphNodeMaxId + 1, graphNodeMaxId + count) + .boxed() + .toList(); + } + + private static List normalizeNodeIds(List ids) { + if (ids == null || ids.isEmpty()) { + return List.of(); + } + return ids.stream() + .filter(id -> id != null && id >= 0) + .distinct() + .toList(); + } + + private static String join(List ids) { + if (ids == null || ids.isEmpty()) { + return "none"; + } + return ids.stream().map(String::valueOf).collect(Collectors.joining(",")); + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholePass.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholePass.java index cea893cd..e4b95ce4 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholePass.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholePass.java @@ -98,6 +98,21 @@ private Map diagnosticFields( Integer.toString(analysis.invalidReplacementPlanValidationCount()) ); fields.put("replacementPlan.validation.firstBlocker", analysis.firstReplacementPlanValidationBlocker()); + List visitPreflights = rewriteVisitPreflights(analysis.ruleReports()); + List blueprints = replacementBlueprints(visitPreflights); + appendRewriteVisitorFields(fields, "rewriteVisitor", visitPreflights); + appendReplacementBlueprintFields(fields, "replacementBlueprint", blueprints); + List transactions = rewriteTransactions(blueprints, visitPreflights); + appendRewriteTransactionFields(fields, "rewriteTransaction", transactions); + List nodeIdAllocations = nodeIdAllocations(transactions, visitPreflights); + appendNodeIdAllocationFields(fields, "nodeIdAllocation", nodeIdAllocations); + List replacementNodes = replacementNodes(nodeIdAllocations, blueprints); + appendReplacementNodeFields(fields, "replacementNode", replacementNodes); + List graphPatches = graphPatches(transactions, replacementNodes); + appendGraphPatchFields(fields, "graphPatch", graphPatches); + List transformedGraphs = transformedGraphs(graphPatches, originalIrIdentity); + appendTransformedGraphFields(fields, "transformedGraph", transformedGraphs); + appendIrArtifactEnvelopeFields(fields, "irArtifactEnvelope", irArtifactEnvelopes(transformedGraphs)); List rewriteSketches = rewriteSketches(analysis.ruleReports()); List rewriteSketchConflicts = rewriteSketchConflicts(rewriteSketches); appendRewriteSketchFields(fields, "rewriteSketch", rewriteSketches); @@ -182,6 +197,64 @@ private void appendRuleFields( ? executionProofStatus(rule, analysis) : reports.get(0).proofStatus()); appendReplacementPlanFields(fields, prefix, reports); + List ruleVisitPreflights = rewriteVisitPreflights(reports); + List ruleBlueprints = replacementBlueprints(ruleVisitPreflights); + appendRewriteVisitorFields(fields, prefix + ".rewriteVisitor", ruleVisitPreflights); + appendReplacementBlueprintFields( + fields, + prefix + ".replacementBlueprint", + ruleBlueprints + ); + List ruleTransactions = rewriteTransactions( + ruleBlueprints, + ruleVisitPreflights + ); + appendRewriteTransactionFields( + fields, + prefix + ".rewriteTransaction", + ruleTransactions + ); + List ruleNodeIdAllocations = nodeIdAllocations( + ruleTransactions, + ruleVisitPreflights + ); + appendNodeIdAllocationFields( + fields, + prefix + ".nodeIdAllocation", + ruleNodeIdAllocations + ); + List ruleReplacementNodes = replacementNodes( + ruleNodeIdAllocations, + ruleBlueprints + ); + appendReplacementNodeFields( + fields, + prefix + ".replacementNode", + ruleReplacementNodes + ); + List ruleGraphPatches = graphPatches( + ruleTransactions, + ruleReplacementNodes + ); + appendGraphPatchFields( + fields, + prefix + ".graphPatch", + ruleGraphPatches + ); + List ruleTransformedGraphs = transformedGraphs( + ruleGraphPatches, + originalIrIdentity + ); + appendTransformedGraphFields( + fields, + prefix + ".transformedGraph", + ruleTransformedGraphs + ); + appendIrArtifactEnvelopeFields( + fields, + prefix + ".irArtifactEnvelope", + irArtifactEnvelopes(ruleTransformedGraphs) + ); List ruleSketches = rewriteSketches(reports); List ruleConflicts = rewriteSketchConflicts(ruleSketches); appendRewriteSketchFields(fields, prefix + ".rewriteSketch", ruleSketches); @@ -281,6 +354,347 @@ private static List rewriteSketches( return List.copyOf(sketches); } + private static List rewriteVisitPreflights( + List reports + ) { + if (reports == null || reports.isEmpty()) { + return List.of(); + } + return reports.stream() + .flatMap(report -> report.rewriteVisitPreflights().stream()) + .toList(); + } + + private static void appendRewriteVisitorFields( + LinkedHashMap fields, + String prefix, + List preflights + ) { + fields.put(prefix + ".count", Integer.toString(preflights.size())); + fields.put(prefix + ".ready.count", Long.toString(preflights.stream() + .filter(GpuRuntimeIrPeepholeRewriteVisitPreflight::visitorReady) + .count())); + fields.put(prefix + ".blocked.count", Long.toString(preflights.stream() + .filter(preflight -> !preflight.visitorReady()) + .count())); + fields.put(prefix + ".firstBlocker", preflights.stream() + .filter(preflight -> !preflight.visitorReady()) + .map(GpuRuntimeIrPeepholeRewriteVisitPreflight::firstBlocker) + .findFirst() + .orElse("none")); + fields.put(prefix + ".visitorImplemented", "true"); + fields.put(prefix + ".replacementBuilderImplemented", "false"); + fields.put(prefix + ".transformedIrBuilt", "false"); + fields.put(prefix + ".mutationAllowed", "false"); + fields.put(prefix + ".selectedIrReplacement", "false"); + if (!preflights.isEmpty()) { + fields.putAll(preflights.get(0).fields(prefix + ".0")); + } + } + + private static List replacementBlueprints( + List preflights + ) { + if (preflights == null || preflights.isEmpty()) { + return List.of(); + } + return preflights.stream() + .map(GpuRuntimeIrPeepholeReplacementBlueprint::from) + .toList(); + } + + private static void appendReplacementBlueprintFields( + LinkedHashMap fields, + String prefix, + List blueprints + ) { + fields.put(prefix + ".count", Integer.toString(blueprints.size())); + fields.put(prefix + ".ready.count", Long.toString(blueprints.stream() + .filter(GpuRuntimeIrPeepholeReplacementBlueprint::blueprintReady) + .count())); + fields.put(prefix + ".blocked.count", Long.toString(blueprints.stream() + .filter(blueprint -> !blueprint.blueprintReady()) + .count())); + fields.put(prefix + ".firstBlocker", blueprints.stream() + .filter(blueprint -> !blueprint.blueprintReady()) + .map(GpuRuntimeIrPeepholeReplacementBlueprint::firstBlocker) + .findFirst() + .orElse("none")); + fields.put(prefix + ".blueprintImplemented", "true"); + fields.put(prefix + ".replacementBuilderImplemented", "false"); + fields.put(prefix + ".transformedIrBuilt", "false"); + fields.put(prefix + ".mutationAllowed", "false"); + fields.put(prefix + ".selectedIrReplacement", "false"); + if (!blueprints.isEmpty()) { + fields.putAll(blueprints.get(0).fields(prefix + ".0")); + } + } + + private static List rewriteTransactions( + List blueprints, + List preflights + ) { + if (blueprints == null || blueprints.isEmpty()) { + return List.of(); + } + java.util.ArrayList transactions = new java.util.ArrayList<>(); + for (int index = 0; index < blueprints.size(); index++) { + GpuRuntimeIrPeepholeRewriteVisitPreflight preflight = preflights != null && index < preflights.size() + ? preflights.get(index) + : null; + transactions.add(GpuRuntimeIrPeepholeRewriteTransactionPreflight.from(blueprints.get(index), preflight)); + } + return List.copyOf(transactions); + } + + private static void appendRewriteTransactionFields( + LinkedHashMap fields, + String prefix, + List transactions + ) { + fields.put(prefix + ".count", Integer.toString(transactions.size())); + fields.put(prefix + ".ready.count", Long.toString(transactions.stream() + .filter(GpuRuntimeIrPeepholeRewriteTransactionPreflight::transactionReady) + .count())); + fields.put(prefix + ".blocked.count", Long.toString(transactions.stream() + .filter(transaction -> !transaction.transactionReady()) + .count())); + fields.put(prefix + ".firstBlocker", transactions.stream() + .filter(transaction -> !transaction.transactionReady()) + .map(GpuRuntimeIrPeepholeRewriteTransactionPreflight::firstBlocker) + .findFirst() + .orElse("none")); + fields.put(prefix + ".transactionPreflightImplemented", "true"); + fields.put(prefix + ".nodeIdAllocatorImplemented", "false"); + fields.put(prefix + ".graphRewriteImplemented", "false"); + fields.put(prefix + ".transformedIrBuilt", "false"); + fields.put(prefix + ".mutationAllowed", "false"); + fields.put(prefix + ".selectedIrReplacement", "false"); + if (!transactions.isEmpty()) { + fields.putAll(transactions.get(0).fields(prefix + ".0")); + } + } + + private static List nodeIdAllocations( + List transactions, + List preflights + ) { + if (transactions == null || transactions.isEmpty()) { + return List.of(); + } + java.util.ArrayList allocations = new java.util.ArrayList<>(); + for (int index = 0; index < transactions.size(); index++) { + GpuRuntimeIrPeepholeRewriteVisitPreflight preflight = preflights != null && index < preflights.size() + ? preflights.get(index) + : null; + allocations.add(GpuRuntimeIrPeepholeNodeIdAllocationPreflight.from(transactions.get(index), preflight)); + } + return List.copyOf(allocations); + } + + private static void appendNodeIdAllocationFields( + LinkedHashMap fields, + String prefix, + List allocations + ) { + fields.put(prefix + ".count", Integer.toString(allocations.size())); + fields.put(prefix + ".ready.count", Long.toString(allocations.stream() + .filter(GpuRuntimeIrPeepholeNodeIdAllocationPreflight::allocationReady) + .count())); + fields.put(prefix + ".blocked.count", Long.toString(allocations.stream() + .filter(allocation -> !allocation.allocationReady()) + .count())); + fields.put(prefix + ".firstBlocker", allocations.stream() + .filter(allocation -> !allocation.allocationReady()) + .map(GpuRuntimeIrPeepholeNodeIdAllocationPreflight::firstBlocker) + .findFirst() + .orElse("none")); + fields.put(prefix + ".allocationPreflightImplemented", "true"); + fields.put(prefix + ".nodeIdsReserved", "false"); + fields.put(prefix + ".nodeIdAllocatorApplied", "false"); + fields.put(prefix + ".graphRewriteImplemented", "false"); + fields.put(prefix + ".transformedIrBuilt", "false"); + fields.put(prefix + ".mutationAllowed", "false"); + fields.put(prefix + ".selectedIrReplacement", "false"); + if (!allocations.isEmpty()) { + fields.putAll(allocations.get(0).fields(prefix + ".0")); + } + } + + private static List replacementNodes( + List allocations, + List blueprints + ) { + if (allocations == null || allocations.isEmpty()) { + return List.of(); + } + java.util.ArrayList nodes = new java.util.ArrayList<>(); + for (int index = 0; index < allocations.size(); index++) { + GpuRuntimeIrPeepholeReplacementBlueprint blueprint = blueprints != null && index < blueprints.size() + ? blueprints.get(index) + : null; + nodes.add(GpuRuntimeIrPeepholeReplacementNodePreflight.from(allocations.get(index), blueprint)); + } + return List.copyOf(nodes); + } + + private static void appendReplacementNodeFields( + LinkedHashMap fields, + String prefix, + List nodes + ) { + fields.put(prefix + ".count", Integer.toString(nodes.size())); + fields.put(prefix + ".ready.count", Long.toString(nodes.stream() + .filter(GpuRuntimeIrPeepholeReplacementNodePreflight::replacementNodeReady) + .count())); + fields.put(prefix + ".blocked.count", Long.toString(nodes.stream() + .filter(node -> !node.replacementNodeReady()) + .count())); + fields.put(prefix + ".firstBlocker", nodes.stream() + .filter(node -> !node.replacementNodeReady()) + .map(GpuRuntimeIrPeepholeReplacementNodePreflight::firstBlocker) + .findFirst() + .orElse("none")); + fields.put(prefix + ".replacementNodePreflightImplemented", "true"); + fields.put(prefix + ".replacementNodeBuilt", "false"); + fields.put(prefix + ".replacementBuilderImplemented", "false"); + fields.put(prefix + ".graphRewriteImplemented", "false"); + fields.put(prefix + ".transformedIrBuilt", "false"); + fields.put(prefix + ".mutationAllowed", "false"); + fields.put(prefix + ".selectedIrReplacement", "false"); + if (!nodes.isEmpty()) { + fields.putAll(nodes.get(0).fields(prefix + ".0")); + } + } + + private static List graphPatches( + List transactions, + List replacementNodes + ) { + if (transactions == null || transactions.isEmpty()) { + return List.of(); + } + java.util.ArrayList patches = new java.util.ArrayList<>(); + for (int index = 0; index < transactions.size(); index++) { + GpuRuntimeIrPeepholeReplacementNodePreflight replacementNode = replacementNodes != null && index < replacementNodes.size() + ? replacementNodes.get(index) + : null; + patches.add(GpuRuntimeIrPeepholeGraphPatchPreflight.from(transactions.get(index), replacementNode)); + } + return List.copyOf(patches); + } + + private static void appendGraphPatchFields( + LinkedHashMap fields, + String prefix, + List patches + ) { + fields.put(prefix + ".count", Integer.toString(patches.size())); + fields.put(prefix + ".ready.count", Long.toString(patches.stream() + .filter(GpuRuntimeIrPeepholeGraphPatchPreflight::graphPatchReady) + .count())); + fields.put(prefix + ".blocked.count", Long.toString(patches.stream() + .filter(patch -> !patch.graphPatchReady()) + .count())); + fields.put(prefix + ".firstBlocker", patches.stream() + .filter(patch -> !patch.graphPatchReady()) + .map(GpuRuntimeIrPeepholeGraphPatchPreflight::firstBlocker) + .findFirst() + .orElse("none")); + fields.put(prefix + ".graphPatchPreflightImplemented", "true"); + fields.put(prefix + ".graphPatchApplied", "false"); + fields.put(prefix + ".graphRewriteImplemented", "false"); + fields.put(prefix + ".transformedIrBuilt", "false"); + fields.put(prefix + ".mutationAllowed", "false"); + fields.put(prefix + ".selectedIrReplacement", "false"); + if (!patches.isEmpty()) { + fields.putAll(patches.get(0).fields(prefix + ".0")); + } + } + + private static List transformedGraphs( + List graphPatches, + String originalIrIdentity + ) { + if (graphPatches == null || graphPatches.isEmpty()) { + return List.of(); + } + return graphPatches.stream() + .map(patch -> GpuRuntimeIrPeepholeTransformedGraphPreflight.from(patch, originalIrIdentity)) + .toList(); + } + + private static void appendTransformedGraphFields( + LinkedHashMap fields, + String prefix, + List transformedGraphs + ) { + fields.put(prefix + ".count", Integer.toString(transformedGraphs.size())); + fields.put(prefix + ".ready.count", Long.toString(transformedGraphs.stream() + .filter(GpuRuntimeIrPeepholeTransformedGraphPreflight::materializationReady) + .count())); + fields.put(prefix + ".blocked.count", Long.toString(transformedGraphs.stream() + .filter(graph -> !graph.materializationReady()) + .count())); + fields.put(prefix + ".firstBlocker", transformedGraphs.stream() + .filter(graph -> !graph.materializationReady()) + .map(GpuRuntimeIrPeepholeTransformedGraphPreflight::firstBlocker) + .findFirst() + .orElse("none")); + fields.put(prefix + ".materializationPreflightImplemented", "true"); + fields.put(prefix + ".transformedGraphBuilt", "false"); + fields.put(prefix + ".transformedIrBuilt", "false"); + fields.put(prefix + ".graphPatchApplied", "false"); + fields.put(prefix + ".graphRewriteImplemented", "false"); + fields.put(prefix + ".mutationAllowed", "false"); + fields.put(prefix + ".selectedIrReplacement", "false"); + if (!transformedGraphs.isEmpty()) { + fields.putAll(transformedGraphs.get(0).fields(prefix + ".0")); + } + } + + private static List irArtifactEnvelopes( + List transformedGraphs + ) { + if (transformedGraphs == null || transformedGraphs.isEmpty()) { + return List.of(); + } + return transformedGraphs.stream() + .map(GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight::from) + .toList(); + } + + private static void appendIrArtifactEnvelopeFields( + LinkedHashMap fields, + String prefix, + List envelopes + ) { + fields.put(prefix + ".count", Integer.toString(envelopes.size())); + fields.put(prefix + ".ready.count", Long.toString(envelopes.stream() + .filter(GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight::artifactEnvelopeReady) + .count())); + fields.put(prefix + ".blocked.count", Long.toString(envelopes.stream() + .filter(envelope -> !envelope.artifactEnvelopeReady()) + .count())); + fields.put(prefix + ".firstBlocker", envelopes.stream() + .filter(envelope -> !envelope.artifactEnvelopeReady()) + .map(GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight::firstBlocker) + .findFirst() + .orElse("none")); + fields.put(prefix + ".artifactEnvelopePreflightImplemented", "true"); + fields.put(prefix + ".artifactEnvelopeBuilt", "false"); + fields.put(prefix + ".optimizedArtifactBuilt", "false"); + fields.put(prefix + ".transformedGraphBuilt", "false"); + fields.put(prefix + ".transformedIrBuilt", "false"); + fields.put(prefix + ".graphPatchApplied", "false"); + fields.put(prefix + ".graphRewriteImplemented", "false"); + fields.put(prefix + ".mutationAllowed", "false"); + fields.put(prefix + ".selectedIrReplacement", "false"); + if (!envelopes.isEmpty()) { + fields.putAll(envelopes.get(0).fields(prefix + ".0")); + } + } + private static void appendRewriteSketchFields( LinkedHashMap fields, String prefix, diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeReplacementBlueprint.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeReplacementBlueprint.java new file mode 100644 index 00000000..2d459285 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeReplacementBlueprint.java @@ -0,0 +1,188 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * Read-only description of the future replacement node for one peephole rewrite. + * + *

The blueprint records the intended intrinsic call shape and argument source nodes. It is not a builder and never + * creates transformed IR.

+ */ +public record GpuRuntimeIrPeepholeReplacementBlueprint( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + String targetNodeKind, + String targetOperation, + List argumentNodeIds, + List argumentRoles, + boolean visitorReady, + boolean blueprintReady, + String status, + String firstBlocker, + boolean blueprintImplemented, + boolean replacementBuilderImplemented, + boolean transformedIrBuilt, + boolean mutationAllowed, + boolean selectedIrReplacement +) { + + public GpuRuntimeIrPeepholeReplacementBlueprint { + ruleId = normalize(ruleId, "rule:unknown"); + methodName = normalize(methodName, "unknown"); + rootNodeId = Math.max(0, rootNodeId); + replacementKind = normalize(replacementKind, "unknown"); + targetNodeKind = normalize(targetNodeKind, "GpuIrIntrinsicCall"); + targetOperation = normalize(targetOperation, replacementKind); + argumentNodeIds = normalizeNodeIds(argumentNodeIds); + argumentRoles = argumentRoles == null || argumentRoles.isEmpty() + ? defaultRoles(argumentNodeIds.size()) + : List.copyOf(argumentRoles); + status = normalize(status, blueprintReady ? "blueprint-ready" : "blocked"); + firstBlocker = normalize(firstBlocker, blueprintReady ? "none" : "replacement-blueprint-blocked"); + } + + public static GpuRuntimeIrPeepholeReplacementBlueprint from( + GpuRuntimeIrPeepholeRewriteVisitPreflight preflight + ) { + if (preflight == null) { + return blocked( + "rule:unknown", + "unknown", + 0, + "unknown", + List.of(), + false, + "rewrite-visitor-preflight-missing" + ); + } + if (!preflight.visitorReady()) { + return blocked( + preflight.ruleId(), + preflight.methodName(), + preflight.rootNodeId(), + preflight.replacementKind(), + preflight.inputNodeIds(), + false, + preflight.firstBlocker() + ); + } + if (preflight.inputNodeIds().isEmpty()) { + return blocked( + preflight.ruleId(), + preflight.methodName(), + preflight.rootNodeId(), + preflight.replacementKind(), + preflight.inputNodeIds(), + true, + "replacement-blueprint-inputs-missing" + ); + } + return new GpuRuntimeIrPeepholeReplacementBlueprint( + preflight.ruleId(), + preflight.methodName(), + preflight.rootNodeId(), + preflight.replacementKind(), + "GpuIrIntrinsicCall", + preflight.replacementKind(), + preflight.inputNodeIds(), + defaultRoles(preflight.inputNodeIds().size()), + true, + true, + "blueprint-ready", + "none", + true, + false, + false, + false, + false + ); + } + + public Map fields(String prefix) { + String normalizedPrefix = normalize(prefix, "replacementBlueprint"); + return Map.ofEntries( + Map.entry(normalizedPrefix + ".ruleId", ruleId), + Map.entry(normalizedPrefix + ".methodName", methodName), + Map.entry(normalizedPrefix + ".rootNodeId", Integer.toString(rootNodeId)), + Map.entry(normalizedPrefix + ".replacementKind", replacementKind), + Map.entry(normalizedPrefix + ".targetNodeKind", targetNodeKind), + Map.entry(normalizedPrefix + ".targetOperation", targetOperation), + Map.entry(normalizedPrefix + ".argumentNodeIds", join(argumentNodeIds)), + Map.entry(normalizedPrefix + ".argumentRoles", String.join(",", argumentRoles)), + Map.entry(normalizedPrefix + ".visitorReady", Boolean.toString(visitorReady)), + Map.entry(normalizedPrefix + ".blueprintReady", Boolean.toString(blueprintReady)), + Map.entry(normalizedPrefix + ".status", status), + Map.entry(normalizedPrefix + ".firstBlocker", firstBlocker), + Map.entry(normalizedPrefix + ".blueprintImplemented", Boolean.toString(blueprintImplemented)), + Map.entry(normalizedPrefix + ".replacementBuilderImplemented", Boolean.toString(replacementBuilderImplemented)), + Map.entry(normalizedPrefix + ".transformedIrBuilt", Boolean.toString(transformedIrBuilt)), + Map.entry(normalizedPrefix + ".mutationAllowed", Boolean.toString(mutationAllowed)), + Map.entry(normalizedPrefix + ".selectedIrReplacement", Boolean.toString(selectedIrReplacement)) + ); + } + + private static GpuRuntimeIrPeepholeReplacementBlueprint blocked( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + List argumentNodeIds, + boolean visitorReady, + String firstBlocker + ) { + return new GpuRuntimeIrPeepholeReplacementBlueprint( + ruleId, + methodName, + rootNodeId, + replacementKind, + "GpuIrIntrinsicCall", + replacementKind, + argumentNodeIds, + defaultRoles(argumentNodeIds == null ? 0 : argumentNodeIds.size()), + visitorReady, + false, + "blocked", + firstBlocker, + true, + false, + false, + false, + false + ); + } + + private static List normalizeNodeIds(List ids) { + if (ids == null || ids.isEmpty()) { + return List.of(); + } + return ids.stream() + .filter(id -> id != null && id >= 0) + .distinct() + .toList(); + } + + private static List defaultRoles(int count) { + if (count <= 0) { + return List.of(); + } + return IntStream.range(0, count) + .mapToObj(index -> "arg" + index) + .toList(); + } + + private static String join(List ids) { + if (ids == null || ids.isEmpty()) { + return "none"; + } + return ids.stream().map(String::valueOf).collect(Collectors.joining(",")); + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeReplacementNodePreflight.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeReplacementNodePreflight.java new file mode 100644 index 00000000..35166895 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeReplacementNodePreflight.java @@ -0,0 +1,240 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Read-only replacement-node construction preview for a future peephole rewrite. + * + *

The preview pairs a deterministic candidate node id with the target intrinsic-call blueprint. It never builds a + * replacement node, mutates the graph, or selects transformed IR.

+ */ +public record GpuRuntimeIrPeepholeReplacementNodePreflight( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + String targetNodeKind, + String targetOperation, + String candidateReplacementNodeId, + List argumentNodeIds, + List argumentRoles, + boolean allocationReady, + boolean blueprintReady, + boolean replacementNodeReady, + String status, + String firstBlocker, + boolean replacementNodePreflightImplemented, + boolean replacementNodeBuilt, + boolean replacementBuilderImplemented, + boolean graphRewriteImplemented, + boolean transformedIrBuilt, + boolean mutationAllowed, + boolean selectedIrReplacement +) { + + public GpuRuntimeIrPeepholeReplacementNodePreflight { + ruleId = normalize(ruleId, "rule:unknown"); + methodName = normalize(methodName, "unknown"); + rootNodeId = Math.max(0, rootNodeId); + replacementKind = normalize(replacementKind, "unknown"); + targetNodeKind = normalize(targetNodeKind, "GpuIrIntrinsicCall"); + targetOperation = normalize(targetOperation, replacementKind); + candidateReplacementNodeId = normalize(candidateReplacementNodeId, "none"); + argumentNodeIds = normalizeNodeIds(argumentNodeIds); + argumentRoles = argumentRoles == null || argumentRoles.isEmpty() + ? defaultRoles(argumentNodeIds.size()) + : List.copyOf(argumentRoles); + status = normalize(status, replacementNodeReady ? "replacement-node-ready" : "blocked"); + firstBlocker = normalize(firstBlocker, replacementNodeReady ? "none" : "replacement-node-blocked"); + } + + public static GpuRuntimeIrPeepholeReplacementNodePreflight from( + GpuRuntimeIrPeepholeNodeIdAllocationPreflight allocation, + GpuRuntimeIrPeepholeReplacementBlueprint blueprint + ) { + if (allocation == null) { + return blocked( + "rule:unknown", + "unknown", + 0, + "unknown", + "GpuIrIntrinsicCall", + "unknown", + List.of(), + List.of(), + false, + false, + "node-id-allocation-missing" + ); + } + if (blueprint == null) { + return blocked( + allocation.ruleId(), + allocation.methodName(), + allocation.rootNodeId(), + allocation.replacementKind(), + "GpuIrIntrinsicCall", + allocation.replacementKind(), + List.of(), + List.of(), + allocation.allocationReady(), + false, + "replacement-blueprint-missing" + ); + } + if (!allocation.allocationReady()) { + return blockedFrom(allocation, blueprint, allocation.firstBlocker()); + } + if (!blueprint.blueprintReady()) { + return blockedFrom(allocation, blueprint, blueprint.firstBlocker()); + } + if (allocation.candidateNodeIds().isEmpty()) { + return blockedFrom(allocation, blueprint, "candidate-replacement-node-id-missing"); + } + if (blueprint.argumentNodeIds().isEmpty()) { + return blockedFrom(allocation, blueprint, "replacement-node-arguments-missing"); + } + return new GpuRuntimeIrPeepholeReplacementNodePreflight( + allocation.ruleId(), + allocation.methodName(), + allocation.rootNodeId(), + allocation.replacementKind(), + blueprint.targetNodeKind(), + blueprint.targetOperation(), + Integer.toString(allocation.candidateNodeIds().get(0)), + blueprint.argumentNodeIds(), + blueprint.argumentRoles(), + true, + true, + true, + "replacement-node-ready", + "none", + true, + false, + false, + false, + false, + false, + false + ); + } + + public Map fields(String prefix) { + String normalizedPrefix = normalize(prefix, "replacementNode"); + return Map.ofEntries( + Map.entry(normalizedPrefix + ".ruleId", ruleId), + Map.entry(normalizedPrefix + ".methodName", methodName), + Map.entry(normalizedPrefix + ".rootNodeId", Integer.toString(rootNodeId)), + Map.entry(normalizedPrefix + ".replacementKind", replacementKind), + Map.entry(normalizedPrefix + ".targetNodeKind", targetNodeKind), + Map.entry(normalizedPrefix + ".targetOperation", targetOperation), + Map.entry(normalizedPrefix + ".candidateReplacementNodeId", candidateReplacementNodeId), + Map.entry(normalizedPrefix + ".argumentNodeIds", join(argumentNodeIds)), + Map.entry(normalizedPrefix + ".argumentRoles", String.join(",", argumentRoles)), + Map.entry(normalizedPrefix + ".allocationReady", Boolean.toString(allocationReady)), + Map.entry(normalizedPrefix + ".blueprintReady", Boolean.toString(blueprintReady)), + Map.entry(normalizedPrefix + ".replacementNodeReady", Boolean.toString(replacementNodeReady)), + Map.entry(normalizedPrefix + ".status", status), + Map.entry(normalizedPrefix + ".firstBlocker", firstBlocker), + Map.entry(normalizedPrefix + ".replacementNodePreflightImplemented", Boolean.toString(replacementNodePreflightImplemented)), + Map.entry(normalizedPrefix + ".replacementNodeBuilt", Boolean.toString(replacementNodeBuilt)), + Map.entry(normalizedPrefix + ".replacementBuilderImplemented", Boolean.toString(replacementBuilderImplemented)), + Map.entry(normalizedPrefix + ".graphRewriteImplemented", Boolean.toString(graphRewriteImplemented)), + Map.entry(normalizedPrefix + ".transformedIrBuilt", Boolean.toString(transformedIrBuilt)), + Map.entry(normalizedPrefix + ".mutationAllowed", Boolean.toString(mutationAllowed)), + Map.entry(normalizedPrefix + ".selectedIrReplacement", Boolean.toString(selectedIrReplacement)) + ); + } + + private static GpuRuntimeIrPeepholeReplacementNodePreflight blockedFrom( + GpuRuntimeIrPeepholeNodeIdAllocationPreflight allocation, + GpuRuntimeIrPeepholeReplacementBlueprint blueprint, + String firstBlocker + ) { + return blocked( + allocation.ruleId(), + allocation.methodName(), + allocation.rootNodeId(), + allocation.replacementKind(), + blueprint.targetNodeKind(), + blueprint.targetOperation(), + blueprint.argumentNodeIds(), + blueprint.argumentRoles(), + allocation.allocationReady(), + blueprint.blueprintReady(), + firstBlocker + ); + } + + private static GpuRuntimeIrPeepholeReplacementNodePreflight blocked( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + String targetNodeKind, + String targetOperation, + List argumentNodeIds, + List argumentRoles, + boolean allocationReady, + boolean blueprintReady, + String firstBlocker + ) { + return new GpuRuntimeIrPeepholeReplacementNodePreflight( + ruleId, + methodName, + rootNodeId, + replacementKind, + targetNodeKind, + targetOperation, + "none", + argumentNodeIds, + argumentRoles, + allocationReady, + blueprintReady, + false, + "blocked", + firstBlocker, + true, + false, + false, + false, + false, + false, + false + ); + } + + private static List normalizeNodeIds(List ids) { + if (ids == null || ids.isEmpty()) { + return List.of(); + } + return ids.stream() + .filter(id -> id != null && id >= 0) + .distinct() + .toList(); + } + + private static List defaultRoles(int count) { + if (count <= 0) { + return List.of(); + } + java.util.ArrayList roles = new java.util.ArrayList<>(); + for (int index = 0; index < count; index++) { + roles.add("arg" + index); + } + return List.copyOf(roles); + } + + private static String join(List ids) { + if (ids == null || ids.isEmpty()) { + return "none"; + } + return ids.stream().map(String::valueOf).collect(Collectors.joining(",")); + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteTransactionPreflight.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteTransactionPreflight.java new file mode 100644 index 00000000..9d70c4a6 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteTransactionPreflight.java @@ -0,0 +1,194 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Read-only transaction shape for a future typed peephole graph rewrite. + * + *

The transaction records which existing nodes would be replaced/removed/retained and whether a synthetic + * replacement node would be needed. It does not allocate ids, rewrite the graph, or build transformed IR.

+ */ +public record GpuRuntimeIrPeepholeRewriteTransactionPreflight( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + List replacedNodeIds, + List removedNodeIds, + List retainedInputNodeIds, + int plannedAddedNodeCount, + String plannedAddedNodeIds, + boolean visitorReady, + boolean blueprintReady, + boolean transactionReady, + String status, + String firstBlocker, + boolean transactionPreflightImplemented, + boolean nodeIdAllocatorImplemented, + boolean graphRewriteImplemented, + boolean transformedIrBuilt, + boolean mutationAllowed, + boolean selectedIrReplacement +) { + + public GpuRuntimeIrPeepholeRewriteTransactionPreflight { + ruleId = normalize(ruleId, "rule:unknown"); + methodName = normalize(methodName, "unknown"); + rootNodeId = Math.max(0, rootNodeId); + replacementKind = normalize(replacementKind, "unknown"); + replacedNodeIds = normalizeNodeIds(replacedNodeIds); + removedNodeIds = normalizeNodeIds(removedNodeIds); + retainedInputNodeIds = normalizeNodeIds(retainedInputNodeIds); + plannedAddedNodeCount = Math.max(0, plannedAddedNodeCount); + plannedAddedNodeIds = normalize(plannedAddedNodeIds, plannedAddedNodeCount == 0 ? "none" : "not-allocated"); + status = normalize(status, transactionReady ? "transaction-ready" : "blocked"); + firstBlocker = normalize(firstBlocker, transactionReady ? "none" : "rewrite-transaction-blocked"); + } + + public static GpuRuntimeIrPeepholeRewriteTransactionPreflight from( + GpuRuntimeIrPeepholeReplacementBlueprint blueprint, + GpuRuntimeIrPeepholeRewriteVisitPreflight visitPreflight + ) { + if (blueprint == null) { + return blocked( + "rule:unknown", + "unknown", + 0, + "unknown", + List.of(), + List.of(), + List.of(), + false, + false, + "replacement-blueprint-missing" + ); + } + List coveredNodeIds = visitPreflight == null ? List.of() : visitPreflight.coveredNodeIds(); + List replacedNodeIds = rootOnly(blueprint.rootNodeId()); + if (!blueprint.blueprintReady()) { + return blocked( + blueprint.ruleId(), + blueprint.methodName(), + blueprint.rootNodeId(), + blueprint.replacementKind(), + replacedNodeIds, + coveredNodeIds, + blueprint.argumentNodeIds(), + visitPreflight != null && visitPreflight.visitorReady(), + false, + blueprint.firstBlocker() + ); + } + return new GpuRuntimeIrPeepholeRewriteTransactionPreflight( + blueprint.ruleId(), + blueprint.methodName(), + blueprint.rootNodeId(), + blueprint.replacementKind(), + replacedNodeIds, + coveredNodeIds, + blueprint.argumentNodeIds(), + 1, + "not-allocated", + true, + true, + true, + "transaction-ready", + "none", + true, + false, + false, + false, + false, + false + ); + } + + public Map fields(String prefix) { + String normalizedPrefix = normalize(prefix, "rewriteTransaction"); + return Map.ofEntries( + Map.entry(normalizedPrefix + ".ruleId", ruleId), + Map.entry(normalizedPrefix + ".methodName", methodName), + Map.entry(normalizedPrefix + ".rootNodeId", Integer.toString(rootNodeId)), + Map.entry(normalizedPrefix + ".replacementKind", replacementKind), + Map.entry(normalizedPrefix + ".replacedNodeIds", join(replacedNodeIds)), + Map.entry(normalizedPrefix + ".removedNodeIds", join(removedNodeIds)), + Map.entry(normalizedPrefix + ".retainedInputNodeIds", join(retainedInputNodeIds)), + Map.entry(normalizedPrefix + ".plannedAddedNode.count", Integer.toString(plannedAddedNodeCount)), + Map.entry(normalizedPrefix + ".plannedAddedNodeIds", plannedAddedNodeIds), + Map.entry(normalizedPrefix + ".visitorReady", Boolean.toString(visitorReady)), + Map.entry(normalizedPrefix + ".blueprintReady", Boolean.toString(blueprintReady)), + Map.entry(normalizedPrefix + ".transactionReady", Boolean.toString(transactionReady)), + Map.entry(normalizedPrefix + ".status", status), + Map.entry(normalizedPrefix + ".firstBlocker", firstBlocker), + Map.entry(normalizedPrefix + ".transactionPreflightImplemented", Boolean.toString(transactionPreflightImplemented)), + Map.entry(normalizedPrefix + ".nodeIdAllocatorImplemented", Boolean.toString(nodeIdAllocatorImplemented)), + Map.entry(normalizedPrefix + ".graphRewriteImplemented", Boolean.toString(graphRewriteImplemented)), + Map.entry(normalizedPrefix + ".transformedIrBuilt", Boolean.toString(transformedIrBuilt)), + Map.entry(normalizedPrefix + ".mutationAllowed", Boolean.toString(mutationAllowed)), + Map.entry(normalizedPrefix + ".selectedIrReplacement", Boolean.toString(selectedIrReplacement)) + ); + } + + private static GpuRuntimeIrPeepholeRewriteTransactionPreflight blocked( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + List replacedNodeIds, + List removedNodeIds, + List retainedInputNodeIds, + boolean visitorReady, + boolean blueprintReady, + String firstBlocker + ) { + return new GpuRuntimeIrPeepholeRewriteTransactionPreflight( + ruleId, + methodName, + rootNodeId, + replacementKind, + replacedNodeIds, + removedNodeIds, + retainedInputNodeIds, + 0, + "none", + visitorReady, + blueprintReady, + false, + "blocked", + firstBlocker, + true, + false, + false, + false, + false, + false + ); + } + + private static List rootOnly(int rootNodeId) { + return rootNodeId < 0 ? List.of() : List.of(rootNodeId); + } + + private static List normalizeNodeIds(List ids) { + if (ids == null || ids.isEmpty()) { + return List.of(); + } + return ids.stream() + .filter(id -> id != null && id >= 0) + .distinct() + .toList(); + } + + private static String join(List ids) { + if (ids == null || ids.isEmpty()) { + return "none"; + } + return ids.stream().map(String::valueOf).collect(Collectors.joining(",")); + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteVisitPreflight.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteVisitPreflight.java new file mode 100644 index 00000000..e74d6e17 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRewriteVisitPreflight.java @@ -0,0 +1,168 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Read-only traversal preflight for a future typed peephole rewrite. + * + *

This proves only that the original graph nodes needed by a replacement plan can be visited deterministically. It + * does not create replacement nodes, mutate typed bodies, or select optimized IR.

+ */ +public record GpuRuntimeIrPeepholeRewriteVisitPreflight( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + List coveredNodeIds, + List inputNodeIds, + List visitOrderNodeIds, + int graphNodeMaxId, + boolean planComplete, + boolean planValid, + boolean rootVisitable, + boolean coveredNodesVisitable, + boolean inputNodesVisitable, + boolean visitorReady, + String status, + String firstBlocker, + boolean visitorImplemented, + boolean replacementBuilderImplemented, + boolean transformedIrBuilt, + boolean mutationAllowed, + boolean selectedIrReplacement +) { + + public GpuRuntimeIrPeepholeRewriteVisitPreflight { + ruleId = normalize(ruleId, "rule:unknown"); + methodName = normalize(methodName, "unknown"); + rootNodeId = Math.max(0, rootNodeId); + replacementKind = normalize(replacementKind, "unknown"); + coveredNodeIds = normalizeNodeIds(coveredNodeIds); + inputNodeIds = normalizeNodeIds(inputNodeIds); + visitOrderNodeIds = normalizeNodeIds(visitOrderNodeIds); + graphNodeMaxId = Math.max(0, graphNodeMaxId); + status = normalize(status, visitorReady ? "visitor-ready" : "blocked"); + firstBlocker = normalize(firstBlocker, visitorReady ? "none" : "rewrite-visitor-blocked"); + } + + public static GpuRuntimeIrPeepholeRewriteVisitPreflight blocked( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + List coveredNodeIds, + List inputNodeIds, + List visitOrderNodeIds, + int graphNodeMaxId, + boolean planComplete, + boolean planValid, + boolean rootVisitable, + boolean coveredNodesVisitable, + boolean inputNodesVisitable, + String firstBlocker + ) { + return new GpuRuntimeIrPeepholeRewriteVisitPreflight( + ruleId, + methodName, + rootNodeId, + replacementKind, + coveredNodeIds, + inputNodeIds, + visitOrderNodeIds, + graphNodeMaxId, + planComplete, + planValid, + rootVisitable, + coveredNodesVisitable, + inputNodesVisitable, + false, + "blocked", + firstBlocker, + true, + false, + false, + false, + false + ); + } + + public static GpuRuntimeIrPeepholeRewriteVisitPreflight ready( + GpuRuntimeIrPeepholeReplacementPlan plan, + List visitOrderNodeIds, + int graphNodeMaxId + ) { + return new GpuRuntimeIrPeepholeRewriteVisitPreflight( + plan.ruleId(), + plan.methodName(), + plan.rootNodeId(), + plan.replacementKind(), + plan.coveredNodeIds(), + plan.inputNodeIds(), + visitOrderNodeIds, + graphNodeMaxId, + true, + true, + true, + true, + true, + true, + "visitor-ready", + "none", + true, + false, + false, + false, + false + ); + } + + public Map fields(String prefix) { + String normalizedPrefix = normalize(prefix, "rewriteVisitor"); + return Map.ofEntries( + Map.entry(normalizedPrefix + ".ruleId", ruleId), + Map.entry(normalizedPrefix + ".methodName", methodName), + Map.entry(normalizedPrefix + ".rootNodeId", Integer.toString(rootNodeId)), + Map.entry(normalizedPrefix + ".replacementKind", replacementKind), + Map.entry(normalizedPrefix + ".coveredNodeIds", join(coveredNodeIds)), + Map.entry(normalizedPrefix + ".inputNodeIds", join(inputNodeIds)), + Map.entry(normalizedPrefix + ".visitOrderNodeIds", join(visitOrderNodeIds)), + Map.entry(normalizedPrefix + ".graphNodeMaxId", Integer.toString(graphNodeMaxId)), + Map.entry(normalizedPrefix + ".planComplete", Boolean.toString(planComplete)), + Map.entry(normalizedPrefix + ".planValid", Boolean.toString(planValid)), + Map.entry(normalizedPrefix + ".rootVisitable", Boolean.toString(rootVisitable)), + Map.entry(normalizedPrefix + ".coveredNodesVisitable", Boolean.toString(coveredNodesVisitable)), + Map.entry(normalizedPrefix + ".inputNodesVisitable", Boolean.toString(inputNodesVisitable)), + Map.entry(normalizedPrefix + ".visitorReady", Boolean.toString(visitorReady)), + Map.entry(normalizedPrefix + ".status", status), + Map.entry(normalizedPrefix + ".firstBlocker", firstBlocker), + Map.entry(normalizedPrefix + ".visitorImplemented", Boolean.toString(visitorImplemented)), + Map.entry(normalizedPrefix + ".replacementBuilderImplemented", Boolean.toString(replacementBuilderImplemented)), + Map.entry(normalizedPrefix + ".transformedIrBuilt", Boolean.toString(transformedIrBuilt)), + Map.entry(normalizedPrefix + ".mutationAllowed", Boolean.toString(mutationAllowed)), + Map.entry(normalizedPrefix + ".selectedIrReplacement", Boolean.toString(selectedIrReplacement)) + ); + } + + private static List normalizeNodeIds(List ids) { + if (ids == null || ids.isEmpty()) { + return List.of(); + } + return ids.stream() + .filter(id -> id != null && id >= 0) + .distinct() + .toList(); + } + + private static String join(List ids) { + if (ids == null || ids.isEmpty()) { + return "none"; + } + return ids.stream().map(String::valueOf).collect(Collectors.joining(",")); + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRuleRegistry.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRuleRegistry.java index 33896afe..118efaef 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRuleRegistry.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRuleRegistry.java @@ -144,9 +144,20 @@ private static GpuRuntimeIrPeepholeRuleReport withReplacementPlanValidations( GpuRuntimeIrPeepholeRuleContext context, GpuRuntimeIrPeepholeRuleReport report ) { - return report.withReplacementPlanValidations(report.replacementPlans().stream() + List validations = report.replacementPlans().stream() .map(plan -> GpuRuntimeIrPeepholeReplacementPlanValidation.validate(plan, context.graph())) - .toList()); + .toList(); + GpuRuntimeIrPeepholeTypedRewriteVisitor visitor = GpuRuntimeIrPeepholeTypedRewriteVisitor.forGraph( + context.graph() + ); + ArrayList visitPreflights = new ArrayList<>(); + for (int index = 0; index < report.replacementPlans().size(); index++) { + GpuRuntimeIrPeepholeReplacementPlanValidation validation = index < validations.size() + ? validations.get(index) + : null; + visitPreflights.add(visitor.preflight(report.replacementPlans().get(index), validation)); + } + return report.withReplacementPlanAnalysis(validations, visitPreflights); } public record Analysis( @@ -198,5 +209,34 @@ public String firstReplacementPlanValidationBlocker() { .findFirst() .orElse("none"); } + + public int rewriteVisitPreflightCount() { + return (int) ruleReports.stream() + .flatMap(report -> report.rewriteVisitPreflights().stream()) + .count(); + } + + public int rewriteVisitPreflightReadyCount() { + return (int) ruleReports.stream() + .flatMap(report -> report.rewriteVisitPreflights().stream()) + .filter(GpuRuntimeIrPeepholeRewriteVisitPreflight::visitorReady) + .count(); + } + + public int rewriteVisitPreflightBlockedCount() { + return (int) ruleReports.stream() + .flatMap(report -> report.rewriteVisitPreflights().stream()) + .filter(preflight -> !preflight.visitorReady()) + .count(); + } + + public String firstRewriteVisitPreflightBlocker() { + return ruleReports.stream() + .flatMap(report -> report.rewriteVisitPreflights().stream()) + .filter(preflight -> !preflight.visitorReady()) + .map(GpuRuntimeIrPeepholeRewriteVisitPreflight::firstBlocker) + .findFirst() + .orElse("none"); + } } } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRuleReport.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRuleReport.java index 1d6286d3..0fbcd9c2 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRuleReport.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeRuleReport.java @@ -17,6 +17,7 @@ public record GpuRuntimeIrPeepholeRuleReport( Map fields, List replacementPlans, List replacementPlanValidations, + List rewriteVisitPreflights, List diagnostics ) { @@ -32,6 +33,7 @@ public record GpuRuntimeIrPeepholeRuleReport( replacementPlanValidations = replacementPlanValidations == null ? List.of() : List.copyOf(replacementPlanValidations); + rewriteVisitPreflights = rewriteVisitPreflights == null ? List.of() : List.copyOf(rewriteVisitPreflights); diagnostics = diagnostics == null ? List.of() : List.copyOf(diagnostics); } @@ -58,6 +60,7 @@ public GpuRuntimeIrPeepholeRuleReport( fields, replacementPlans, List.of(), + List.of(), diagnostics ); } @@ -79,6 +82,7 @@ public static GpuRuntimeIrPeepholeRuleReport diagnosticCandidates( fields, List.of(), List.of(), + List.of(), List.of("rule is diagnostic-only; no mutation proposal was emitted") ); } @@ -101,12 +105,14 @@ public static GpuRuntimeIrPeepholeRuleReport diagnosticCandidates( fields, replacementPlans, List.of(), + List.of(), List.of("rule is diagnostic-only; no mutation proposal was emitted") ); } - public GpuRuntimeIrPeepholeRuleReport withReplacementPlanValidations( - List validations + public GpuRuntimeIrPeepholeRuleReport withReplacementPlanAnalysis( + List validations, + List visitPreflights ) { return new GpuRuntimeIrPeepholeRuleReport( ruleId, @@ -119,10 +125,17 @@ public GpuRuntimeIrPeepholeRuleReport withReplacementPlanValidations( fields, replacementPlans, validations, + visitPreflights, diagnostics ); } + public GpuRuntimeIrPeepholeRuleReport withReplacementPlanValidations( + List validations + ) { + return withReplacementPlanAnalysis(validations, rewriteVisitPreflights); + } + private static String normalize(String value, String fallback) { return value == null || value.isBlank() ? fallback : value; } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeTransformedGraphPreflight.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeTransformedGraphPreflight.java new file mode 100644 index 00000000..9007a425 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeTransformedGraphPreflight.java @@ -0,0 +1,264 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Read-only transformed-graph materialization preview for a future peephole rewrite. + * + *

The preview records the deterministic materialization inputs and original IR identity. It never materializes a + * transformed graph, builds transformed IR, applies graph patches, or selects optimized IR.

+ */ +public record GpuRuntimeIrPeepholeTransformedGraphPreflight( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + String originalIrIdentity, + String transformedGraphIdentity, + String materializationKey, + String replacementNodeId, + List replacedNodeIds, + List removedNodeIds, + List retainedInputNodeIds, + List insertedNodeIds, + boolean graphPatchReady, + boolean materializationReady, + String status, + String firstBlocker, + boolean materializationPreflightImplemented, + boolean transformedGraphBuilt, + boolean transformedIrBuilt, + boolean graphPatchApplied, + boolean graphRewriteImplemented, + boolean mutationAllowed, + boolean selectedIrReplacement +) { + + public GpuRuntimeIrPeepholeTransformedGraphPreflight { + ruleId = normalize(ruleId, "rule:unknown"); + methodName = normalize(methodName, "unknown"); + rootNodeId = Math.max(0, rootNodeId); + replacementKind = normalize(replacementKind, "unknown"); + originalIrIdentity = normalize(originalIrIdentity, "irgpu:unknown"); + transformedGraphIdentity = normalize(transformedGraphIdentity, "not-built"); + replacementNodeId = normalize(replacementNodeId, "none"); + replacedNodeIds = normalizeNodeIds(replacedNodeIds); + removedNodeIds = normalizeNodeIds(removedNodeIds); + retainedInputNodeIds = normalizeNodeIds(retainedInputNodeIds); + insertedNodeIds = normalizeNodeIds(insertedNodeIds); + materializationKey = normalize(materializationKey, materializationKey( + ruleId, + methodName, + rootNodeId, + replacementKind, + replacementNodeId, + replacedNodeIds, + removedNodeIds, + retainedInputNodeIds, + insertedNodeIds + )); + status = normalize(status, materializationReady ? "materialization-ready" : "blocked"); + firstBlocker = normalize(firstBlocker, materializationReady ? "none" : "transformed-graph-materialization-blocked"); + } + + public static GpuRuntimeIrPeepholeTransformedGraphPreflight from( + GpuRuntimeIrPeepholeGraphPatchPreflight graphPatch, + String originalIrIdentity + ) { + if (graphPatch == null) { + return blocked( + "rule:unknown", + "unknown", + 0, + "unknown", + originalIrIdentity, + "none", + List.of(), + List.of(), + List.of(), + List.of(), + false, + "graph-patch-missing" + ); + } + if (!graphPatch.graphPatchReady()) { + return blocked( + graphPatch.ruleId(), + graphPatch.methodName(), + graphPatch.rootNodeId(), + graphPatch.replacementKind(), + originalIrIdentity, + graphPatch.replacementNodeId(), + graphPatch.replacedNodeIds(), + graphPatch.removedNodeIds(), + graphPatch.retainedInputNodeIds(), + graphPatch.insertedNodeIds(), + false, + graphPatch.firstBlocker() + ); + } + return new GpuRuntimeIrPeepholeTransformedGraphPreflight( + graphPatch.ruleId(), + graphPatch.methodName(), + graphPatch.rootNodeId(), + graphPatch.replacementKind(), + originalIrIdentity, + "not-built", + materializationKey( + graphPatch.ruleId(), + graphPatch.methodName(), + graphPatch.rootNodeId(), + graphPatch.replacementKind(), + graphPatch.replacementNodeId(), + graphPatch.replacedNodeIds(), + graphPatch.removedNodeIds(), + graphPatch.retainedInputNodeIds(), + graphPatch.insertedNodeIds() + ), + graphPatch.replacementNodeId(), + graphPatch.replacedNodeIds(), + graphPatch.removedNodeIds(), + graphPatch.retainedInputNodeIds(), + graphPatch.insertedNodeIds(), + true, + true, + "materialization-ready", + "none", + true, + false, + false, + false, + false, + false, + false + ); + } + + public Map fields(String prefix) { + String normalizedPrefix = normalize(prefix, "transformedGraph"); + return Map.ofEntries( + Map.entry(normalizedPrefix + ".ruleId", ruleId), + Map.entry(normalizedPrefix + ".methodName", methodName), + Map.entry(normalizedPrefix + ".rootNodeId", Integer.toString(rootNodeId)), + Map.entry(normalizedPrefix + ".replacementKind", replacementKind), + Map.entry(normalizedPrefix + ".originalIrIdentity", originalIrIdentity), + Map.entry(normalizedPrefix + ".transformedGraphIdentity", transformedGraphIdentity), + Map.entry(normalizedPrefix + ".materializationKey", materializationKey), + Map.entry(normalizedPrefix + ".replacementNodeId", replacementNodeId), + Map.entry(normalizedPrefix + ".replacedNodeIds", join(replacedNodeIds)), + Map.entry(normalizedPrefix + ".removedNodeIds", join(removedNodeIds)), + Map.entry(normalizedPrefix + ".retainedInputNodeIds", join(retainedInputNodeIds)), + Map.entry(normalizedPrefix + ".insertedNodeIds", join(insertedNodeIds)), + Map.entry(normalizedPrefix + ".graphPatchReady", Boolean.toString(graphPatchReady)), + Map.entry(normalizedPrefix + ".materializationReady", Boolean.toString(materializationReady)), + Map.entry(normalizedPrefix + ".status", status), + Map.entry(normalizedPrefix + ".firstBlocker", firstBlocker), + Map.entry(normalizedPrefix + ".materializationPreflightImplemented", Boolean.toString(materializationPreflightImplemented)), + Map.entry(normalizedPrefix + ".transformedGraphBuilt", Boolean.toString(transformedGraphBuilt)), + Map.entry(normalizedPrefix + ".transformedIrBuilt", Boolean.toString(transformedIrBuilt)), + Map.entry(normalizedPrefix + ".graphPatchApplied", Boolean.toString(graphPatchApplied)), + Map.entry(normalizedPrefix + ".graphRewriteImplemented", Boolean.toString(graphRewriteImplemented)), + Map.entry(normalizedPrefix + ".mutationAllowed", Boolean.toString(mutationAllowed)), + Map.entry(normalizedPrefix + ".selectedIrReplacement", Boolean.toString(selectedIrReplacement)) + ); + } + + private static GpuRuntimeIrPeepholeTransformedGraphPreflight blocked( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + String originalIrIdentity, + String replacementNodeId, + List replacedNodeIds, + List removedNodeIds, + List retainedInputNodeIds, + List insertedNodeIds, + boolean graphPatchReady, + String firstBlocker + ) { + return new GpuRuntimeIrPeepholeTransformedGraphPreflight( + ruleId, + methodName, + rootNodeId, + replacementKind, + originalIrIdentity, + "not-built", + materializationKey( + ruleId, + methodName, + rootNodeId, + replacementKind, + replacementNodeId, + replacedNodeIds, + removedNodeIds, + retainedInputNodeIds, + insertedNodeIds + ), + replacementNodeId, + replacedNodeIds, + removedNodeIds, + retainedInputNodeIds, + insertedNodeIds, + graphPatchReady, + false, + "blocked", + firstBlocker, + true, + false, + false, + false, + false, + false, + false + ); + } + + private static String materializationKey( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + String replacementNodeId, + List replacedNodeIds, + List removedNodeIds, + List retainedInputNodeIds, + List insertedNodeIds + ) { + return String.join( + "|", + normalize(ruleId, "rule:unknown"), + normalize(methodName, "unknown"), + Integer.toString(Math.max(0, rootNodeId)), + normalize(replacementKind, "unknown"), + "replacement=" + normalize(replacementNodeId, "none"), + "replace=" + join(normalizeNodeIds(replacedNodeIds)), + "remove=" + join(normalizeNodeIds(removedNodeIds)), + "retain=" + join(normalizeNodeIds(retainedInputNodeIds)), + "insert=" + join(normalizeNodeIds(insertedNodeIds)) + ); + } + + private static List normalizeNodeIds(List ids) { + if (ids == null || ids.isEmpty()) { + return List.of(); + } + return ids.stream() + .filter(id -> id != null && id >= 0) + .distinct() + .toList(); + } + + private static String join(List ids) { + if (ids == null || ids.isEmpty()) { + return "none"; + } + return ids.stream().map(String::valueOf).collect(Collectors.joining(",")); + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeTypedRewriteVisitor.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeTypedRewriteVisitor.java new file mode 100644 index 00000000..ffd682e3 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeTypedRewriteVisitor.java @@ -0,0 +1,128 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Shared read-only visitor preflight for future typed peephole rewrites. + * + *

The visitor only verifies deterministic access to original nodes referenced by a replacement plan. Replacement + * node construction and artifact mutation intentionally stay outside this class.

+ */ +public final class GpuRuntimeIrPeepholeTypedRewriteVisitor { + + private final GpuRuntimeIrTypedNodeGraph graph; + + private GpuRuntimeIrPeepholeTypedRewriteVisitor(GpuRuntimeIrTypedNodeGraph graph) { + this.graph = Objects.requireNonNull(graph, "graph"); + } + + public static GpuRuntimeIrPeepholeTypedRewriteVisitor forGraph(GpuRuntimeIrTypedNodeGraph graph) { + return new GpuRuntimeIrPeepholeTypedRewriteVisitor(graph); + } + + public GpuRuntimeIrPeepholeRewriteVisitPreflight preflight( + GpuRuntimeIrPeepholeReplacementPlan plan, + GpuRuntimeIrPeepholeReplacementPlanValidation validation + ) { + if (plan == null) { + return GpuRuntimeIrPeepholeRewriteVisitPreflight.blocked( + "rule:unknown", + "unknown", + 0, + "unknown", + List.of(), + List.of(), + List.of(), + graph.maxNodeId(), + false, + false, + false, + false, + false, + "replacement-plan-missing" + ); + } + + boolean rootVisitable = graph.containsNode(plan.rootNodeId()); + boolean coveredNodesVisitable = graph.missingNodeIds(plan.coveredNodeIds()).isEmpty(); + boolean inputNodesVisitable = graph.missingNodeIds(plan.inputNodeIds()).isEmpty(); + boolean planValid = validation != null && validation.valid(); + List visitOrder = visitOrder(plan); + String firstBlocker = firstBlocker( + plan, + validation, + rootVisitable, + coveredNodesVisitable, + inputNodesVisitable, + planValid + ); + if (!"none".equals(firstBlocker)) { + return GpuRuntimeIrPeepholeRewriteVisitPreflight.blocked( + plan.ruleId(), + plan.methodName(), + plan.rootNodeId(), + plan.replacementKind(), + plan.coveredNodeIds(), + plan.inputNodeIds(), + visitOrder, + graph.maxNodeId(), + plan.complete(), + planValid, + rootVisitable, + coveredNodesVisitable, + inputNodesVisitable, + firstBlocker + ); + } + return GpuRuntimeIrPeepholeRewriteVisitPreflight.ready(plan, visitOrder, graph.maxNodeId()); + } + + private List visitOrder(GpuRuntimeIrPeepholeReplacementPlan plan) { + ArrayList ids = new ArrayList<>(); + addIfPresent(ids, plan.rootNodeId()); + for (Integer id : plan.coveredNodeIds()) { + addIfPresent(ids, id); + } + for (Integer id : plan.inputNodeIds()) { + addIfPresent(ids, id); + } + return List.copyOf(ids); + } + + private void addIfPresent(List ids, Integer id) { + if (id != null && graph.containsNode(id) && !ids.contains(id)) { + ids.add(id); + } + } + + private static String firstBlocker( + GpuRuntimeIrPeepholeReplacementPlan plan, + GpuRuntimeIrPeepholeReplacementPlanValidation validation, + boolean rootVisitable, + boolean coveredNodesVisitable, + boolean inputNodesVisitable, + boolean planValid + ) { + if (!plan.complete()) { + return plan.firstBlocker(); + } + if (validation == null) { + return "replacement-plan-validation-missing"; + } + if (!planValid) { + return validation.firstBlocker(); + } + if (!rootVisitable) { + return "rewrite-visitor-root-not-visitable"; + } + if (!coveredNodesVisitable) { + return "rewrite-visitor-covered-node-not-visitable"; + } + if (!inputNodesVisitable) { + return "rewrite-visitor-input-node-not-visitable"; + } + return "none"; + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrTypedNodeGraph.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrTypedNodeGraph.java index 50d01d28..d0cdc890 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrTypedNodeGraph.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrTypedNodeGraph.java @@ -50,6 +50,13 @@ public Map nodesById() { return nodesById; } + public int maxNodeId() { + return nodesById.keySet().stream() + .mapToInt(Integer::intValue) + .max() + .orElse(0); + } + public IrGpuTypedNode node(int id) { return nodesById.get(id); } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeOptimizerDriftArtifact.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeOptimizerDriftArtifact.java index dd6453cb..0552d8cf 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeOptimizerDriftArtifact.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeOptimizerDriftArtifact.java @@ -30,6 +30,38 @@ public record GpuRuntimeOptimizerDriftArtifact( int replacementPlanValidationValidCount, int replacementPlanValidationInvalidCount, String replacementPlanValidationFirstBlocker, + int rewriteVisitorCount, + int rewriteVisitorReadyCount, + int rewriteVisitorBlockedCount, + String rewriteVisitorFirstBlocker, + int replacementBlueprintCount, + int replacementBlueprintReadyCount, + int replacementBlueprintBlockedCount, + String replacementBlueprintFirstBlocker, + int rewriteTransactionCount, + int rewriteTransactionReadyCount, + int rewriteTransactionBlockedCount, + String rewriteTransactionFirstBlocker, + int nodeIdAllocationCount, + int nodeIdAllocationReadyCount, + int nodeIdAllocationBlockedCount, + String nodeIdAllocationFirstBlocker, + int replacementNodeCount, + int replacementNodeReadyCount, + int replacementNodeBlockedCount, + String replacementNodeFirstBlocker, + int graphPatchCount, + int graphPatchReadyCount, + int graphPatchBlockedCount, + String graphPatchFirstBlocker, + int transformedGraphCount, + int transformedGraphReadyCount, + int transformedGraphBlockedCount, + String transformedGraphFirstBlocker, + int irArtifactEnvelopeCount, + int irArtifactEnvelopeReadyCount, + int irArtifactEnvelopeBlockedCount, + String irArtifactEnvelopeFirstBlocker, int rewriteSketchCount, int rewriteSketchReadyCount, int rewriteSketchBlockedCount, @@ -83,6 +115,38 @@ public static GpuRuntimeOptimizerDriftArtifact from(GpuRuntimeCompileArtifactSna 0, "none", 0, + 0, + 0, + "none", + 0, + 0, + 0, + "none", + 0, + 0, + 0, + "none", + 0, + 0, + 0, + "none", + 0, + 0, + 0, + "none", + 0, + 0, + 0, + "none", + 0, + 0, + 0, + "none", + 0, + 0, + 0, + "none", + 0, "none", "not-required", "no-rewrite-sketches", @@ -112,6 +176,38 @@ public static GpuRuntimeOptimizerDriftArtifact from(GpuRuntimeCompileArtifactSna int rewriteSketchReadyCount = rewriteSketchReadyCount(report); int rewriteSketchBlockedCount = rewriteSketchBlockedCount(report); String rewriteSketchFirstBlocker = rewriteSketchFirstBlocker(report); + int rewriteVisitorCount = rewriteVisitorCount(report); + int rewriteVisitorReadyCount = rewriteVisitorReadyCount(report); + int rewriteVisitorBlockedCount = rewriteVisitorBlockedCount(report); + String rewriteVisitorFirstBlocker = rewriteVisitorFirstBlocker(report); + int replacementBlueprintCount = replacementBlueprintCount(report); + int replacementBlueprintReadyCount = replacementBlueprintReadyCount(report); + int replacementBlueprintBlockedCount = replacementBlueprintBlockedCount(report); + String replacementBlueprintFirstBlocker = replacementBlueprintFirstBlocker(report); + int rewriteTransactionCount = rewriteTransactionCount(report); + int rewriteTransactionReadyCount = rewriteTransactionReadyCount(report); + int rewriteTransactionBlockedCount = rewriteTransactionBlockedCount(report); + String rewriteTransactionFirstBlocker = rewriteTransactionFirstBlocker(report); + int nodeIdAllocationCount = nodeIdAllocationCount(report); + int nodeIdAllocationReadyCount = nodeIdAllocationReadyCount(report); + int nodeIdAllocationBlockedCount = nodeIdAllocationBlockedCount(report); + String nodeIdAllocationFirstBlocker = nodeIdAllocationFirstBlocker(report); + int replacementNodeCount = replacementNodeCount(report); + int replacementNodeReadyCount = replacementNodeReadyCount(report); + int replacementNodeBlockedCount = replacementNodeBlockedCount(report); + String replacementNodeFirstBlocker = replacementNodeFirstBlocker(report); + int graphPatchCount = graphPatchCount(report); + int graphPatchReadyCount = graphPatchReadyCount(report); + int graphPatchBlockedCount = graphPatchBlockedCount(report); + String graphPatchFirstBlocker = graphPatchFirstBlocker(report); + int transformedGraphCount = transformedGraphCount(report); + int transformedGraphReadyCount = transformedGraphReadyCount(report); + int transformedGraphBlockedCount = transformedGraphBlockedCount(report); + String transformedGraphFirstBlocker = transformedGraphFirstBlocker(report); + int irArtifactEnvelopeCount = irArtifactEnvelopeCount(report); + int irArtifactEnvelopeReadyCount = irArtifactEnvelopeReadyCount(report); + int irArtifactEnvelopeBlockedCount = irArtifactEnvelopeBlockedCount(report); + String irArtifactEnvelopeFirstBlocker = irArtifactEnvelopeFirstBlocker(report); int rewriteSketchConflictCount = rewriteSketchConflictCount(report); String rewriteSketchConflictFirstBlocker = rewriteSketchConflictFirstBlocker(report); String rewriteProofStatus = rewriteProofStatus(report, rewriteSketchReadyCount); @@ -140,6 +236,38 @@ public static GpuRuntimeOptimizerDriftArtifact from(GpuRuntimeCompileArtifactSna replacementPlanValidationValidCount(report), replacementPlanValidationInvalidCount(report), replacementPlanValidationFirstBlocker(report), + rewriteVisitorCount, + rewriteVisitorReadyCount, + rewriteVisitorBlockedCount, + rewriteVisitorFirstBlocker, + replacementBlueprintCount, + replacementBlueprintReadyCount, + replacementBlueprintBlockedCount, + replacementBlueprintFirstBlocker, + rewriteTransactionCount, + rewriteTransactionReadyCount, + rewriteTransactionBlockedCount, + rewriteTransactionFirstBlocker, + nodeIdAllocationCount, + nodeIdAllocationReadyCount, + nodeIdAllocationBlockedCount, + nodeIdAllocationFirstBlocker, + replacementNodeCount, + replacementNodeReadyCount, + replacementNodeBlockedCount, + replacementNodeFirstBlocker, + graphPatchCount, + graphPatchReadyCount, + graphPatchBlockedCount, + graphPatchFirstBlocker, + transformedGraphCount, + transformedGraphReadyCount, + transformedGraphBlockedCount, + transformedGraphFirstBlocker, + irArtifactEnvelopeCount, + irArtifactEnvelopeReadyCount, + irArtifactEnvelopeBlockedCount, + irArtifactEnvelopeFirstBlocker, rewriteSketchCount, rewriteSketchReadyCount, rewriteSketchBlockedCount, @@ -206,6 +334,90 @@ public String toPropertiesText() { builder.append("replacementPlan.validation.valid.count=").append(replacementPlanValidationValidCount).append('\n'); builder.append("replacementPlan.validation.invalid.count=").append(replacementPlanValidationInvalidCount).append('\n'); builder.append("replacementPlan.validation.firstBlocker=").append(replacementPlanValidationFirstBlocker).append('\n'); + builder.append("rewriteVisitor.count=").append(rewriteVisitorCount).append('\n'); + builder.append("rewriteVisitor.ready.count=").append(rewriteVisitorReadyCount).append('\n'); + builder.append("rewriteVisitor.blocked.count=").append(rewriteVisitorBlockedCount).append('\n'); + builder.append("rewriteVisitor.firstBlocker=").append(rewriteVisitorFirstBlocker).append('\n'); + builder.append("rewriteVisitor.visitorImplemented=true\n"); + builder.append("rewriteVisitor.replacementBuilderImplemented=false\n"); + builder.append("rewriteVisitor.transformedIrBuilt=false\n"); + builder.append("rewriteVisitor.mutationAllowed=false\n"); + builder.append("rewriteVisitor.selectedIrReplacement=false\n"); + builder.append("replacementBlueprint.count=").append(replacementBlueprintCount).append('\n'); + builder.append("replacementBlueprint.ready.count=").append(replacementBlueprintReadyCount).append('\n'); + builder.append("replacementBlueprint.blocked.count=").append(replacementBlueprintBlockedCount).append('\n'); + builder.append("replacementBlueprint.firstBlocker=").append(replacementBlueprintFirstBlocker).append('\n'); + builder.append("replacementBlueprint.blueprintImplemented=true\n"); + builder.append("replacementBlueprint.replacementBuilderImplemented=false\n"); + builder.append("replacementBlueprint.transformedIrBuilt=false\n"); + builder.append("replacementBlueprint.mutationAllowed=false\n"); + builder.append("replacementBlueprint.selectedIrReplacement=false\n"); + builder.append("rewriteTransaction.count=").append(rewriteTransactionCount).append('\n'); + builder.append("rewriteTransaction.ready.count=").append(rewriteTransactionReadyCount).append('\n'); + builder.append("rewriteTransaction.blocked.count=").append(rewriteTransactionBlockedCount).append('\n'); + builder.append("rewriteTransaction.firstBlocker=").append(rewriteTransactionFirstBlocker).append('\n'); + builder.append("rewriteTransaction.transactionPreflightImplemented=true\n"); + builder.append("rewriteTransaction.nodeIdAllocatorImplemented=false\n"); + builder.append("rewriteTransaction.graphRewriteImplemented=false\n"); + builder.append("rewriteTransaction.transformedIrBuilt=false\n"); + builder.append("rewriteTransaction.mutationAllowed=false\n"); + builder.append("rewriteTransaction.selectedIrReplacement=false\n"); + builder.append("nodeIdAllocation.count=").append(nodeIdAllocationCount).append('\n'); + builder.append("nodeIdAllocation.ready.count=").append(nodeIdAllocationReadyCount).append('\n'); + builder.append("nodeIdAllocation.blocked.count=").append(nodeIdAllocationBlockedCount).append('\n'); + builder.append("nodeIdAllocation.firstBlocker=").append(nodeIdAllocationFirstBlocker).append('\n'); + builder.append("nodeIdAllocation.allocationPreflightImplemented=true\n"); + builder.append("nodeIdAllocation.nodeIdsReserved=false\n"); + builder.append("nodeIdAllocation.nodeIdAllocatorApplied=false\n"); + builder.append("nodeIdAllocation.graphRewriteImplemented=false\n"); + builder.append("nodeIdAllocation.transformedIrBuilt=false\n"); + builder.append("nodeIdAllocation.mutationAllowed=false\n"); + builder.append("nodeIdAllocation.selectedIrReplacement=false\n"); + builder.append("replacementNode.count=").append(replacementNodeCount).append('\n'); + builder.append("replacementNode.ready.count=").append(replacementNodeReadyCount).append('\n'); + builder.append("replacementNode.blocked.count=").append(replacementNodeBlockedCount).append('\n'); + builder.append("replacementNode.firstBlocker=").append(replacementNodeFirstBlocker).append('\n'); + builder.append("replacementNode.replacementNodePreflightImplemented=true\n"); + builder.append("replacementNode.replacementNodeBuilt=false\n"); + builder.append("replacementNode.replacementBuilderImplemented=false\n"); + builder.append("replacementNode.graphRewriteImplemented=false\n"); + builder.append("replacementNode.transformedIrBuilt=false\n"); + builder.append("replacementNode.mutationAllowed=false\n"); + builder.append("replacementNode.selectedIrReplacement=false\n"); + builder.append("graphPatch.count=").append(graphPatchCount).append('\n'); + builder.append("graphPatch.ready.count=").append(graphPatchReadyCount).append('\n'); + builder.append("graphPatch.blocked.count=").append(graphPatchBlockedCount).append('\n'); + builder.append("graphPatch.firstBlocker=").append(graphPatchFirstBlocker).append('\n'); + builder.append("graphPatch.graphPatchPreflightImplemented=true\n"); + builder.append("graphPatch.graphPatchApplied=false\n"); + builder.append("graphPatch.graphRewriteImplemented=false\n"); + builder.append("graphPatch.transformedIrBuilt=false\n"); + builder.append("graphPatch.mutationAllowed=false\n"); + builder.append("graphPatch.selectedIrReplacement=false\n"); + builder.append("transformedGraph.count=").append(transformedGraphCount).append('\n'); + builder.append("transformedGraph.ready.count=").append(transformedGraphReadyCount).append('\n'); + builder.append("transformedGraph.blocked.count=").append(transformedGraphBlockedCount).append('\n'); + builder.append("transformedGraph.firstBlocker=").append(transformedGraphFirstBlocker).append('\n'); + builder.append("transformedGraph.materializationPreflightImplemented=true\n"); + builder.append("transformedGraph.transformedGraphBuilt=false\n"); + builder.append("transformedGraph.transformedIrBuilt=false\n"); + builder.append("transformedGraph.graphPatchApplied=false\n"); + builder.append("transformedGraph.graphRewriteImplemented=false\n"); + builder.append("transformedGraph.mutationAllowed=false\n"); + builder.append("transformedGraph.selectedIrReplacement=false\n"); + builder.append("irArtifactEnvelope.count=").append(irArtifactEnvelopeCount).append('\n'); + builder.append("irArtifactEnvelope.ready.count=").append(irArtifactEnvelopeReadyCount).append('\n'); + builder.append("irArtifactEnvelope.blocked.count=").append(irArtifactEnvelopeBlockedCount).append('\n'); + builder.append("irArtifactEnvelope.firstBlocker=").append(irArtifactEnvelopeFirstBlocker).append('\n'); + builder.append("irArtifactEnvelope.artifactEnvelopePreflightImplemented=true\n"); + builder.append("irArtifactEnvelope.artifactEnvelopeBuilt=false\n"); + builder.append("irArtifactEnvelope.optimizedArtifactBuilt=false\n"); + builder.append("irArtifactEnvelope.transformedGraphBuilt=false\n"); + builder.append("irArtifactEnvelope.transformedIrBuilt=false\n"); + builder.append("irArtifactEnvelope.graphPatchApplied=false\n"); + builder.append("irArtifactEnvelope.graphRewriteImplemented=false\n"); + builder.append("irArtifactEnvelope.mutationAllowed=false\n"); + builder.append("irArtifactEnvelope.selectedIrReplacement=false\n"); builder.append("rewriteSketch.count=").append(rewriteSketchCount).append('\n'); builder.append("rewriteSketch.ready.count=").append(rewriteSketchReadyCount).append('\n'); builder.append("rewriteSketch.blocked.count=").append(rewriteSketchBlockedCount).append('\n'); @@ -385,6 +597,446 @@ private static String replacementPlanValidationFirstBlocker(GpuRuntimeIrOptimiza return "none"; } + private static int rewriteVisitorCount(GpuRuntimeIrOptimizationReport report) { + return rewriteVisitorRuleCount(report, ".rewriteVisitor.count"); + } + + private static int rewriteVisitorReadyCount(GpuRuntimeIrOptimizationReport report) { + return rewriteVisitorRuleCount(report, ".rewriteVisitor.ready.count"); + } + + private static int rewriteVisitorBlockedCount(GpuRuntimeIrOptimizationReport report) { + int total = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + Map fields = passReport.proofArtifact().fields(); + int passTotal = parseInt(fields.get("rewriteVisitor.blocked.count")); + total += passTotal > 0 ? passTotal : rewriteVisitorRuleCount(fields, ".rewriteVisitor.blocked.count"); + } + } + return total; + } + + private static int rewriteVisitorRuleCount(GpuRuntimeIrOptimizationReport report, String suffix) { + int total = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + total += rewriteVisitorRuleCount(passReport.proofArtifact().fields(), suffix); + } + } + return total; + } + + private static int rewriteVisitorRuleCount(Map fields, String suffix) { + return fields.entrySet().stream() + .filter(entry -> entry.getKey().startsWith("rule.")) + .filter(entry -> entry.getKey().endsWith(suffix)) + .mapToInt(entry -> parseInt(entry.getValue())) + .sum(); + } + + private static String rewriteVisitorFirstBlocker(GpuRuntimeIrOptimizationReport report) { + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + Map fields = passReport.proofArtifact().fields(); + String blocker = fields.getOrDefault("rewriteVisitor.firstBlocker", "none"); + if (!blocker.isBlank() && !"none".equals(blocker)) { + return blocker; + } + String ruleBlocker = firstRuleBlocker(fields, ".rewriteVisitor.firstBlocker"); + if (!"none".equals(ruleBlocker)) { + return ruleBlocker; + } + } + } + return "none"; + } + + private static int replacementBlueprintCount(GpuRuntimeIrOptimizationReport report) { + return replacementBlueprintRuleCount(report, ".replacementBlueprint.count"); + } + + private static int replacementBlueprintReadyCount(GpuRuntimeIrOptimizationReport report) { + return replacementBlueprintRuleCount(report, ".replacementBlueprint.ready.count"); + } + + private static int replacementBlueprintBlockedCount(GpuRuntimeIrOptimizationReport report) { + int total = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + Map fields = passReport.proofArtifact().fields(); + int passTotal = parseInt(fields.get("replacementBlueprint.blocked.count")); + total += passTotal > 0 ? passTotal : replacementBlueprintRuleCount(fields, ".replacementBlueprint.blocked.count"); + } + } + return total; + } + + private static int replacementBlueprintRuleCount(GpuRuntimeIrOptimizationReport report, String suffix) { + int total = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + total += replacementBlueprintRuleCount(passReport.proofArtifact().fields(), suffix); + } + } + return total; + } + + private static int replacementBlueprintRuleCount(Map fields, String suffix) { + return fields.entrySet().stream() + .filter(entry -> entry.getKey().startsWith("rule.")) + .filter(entry -> entry.getKey().endsWith(suffix)) + .mapToInt(entry -> parseInt(entry.getValue())) + .sum(); + } + + private static String replacementBlueprintFirstBlocker(GpuRuntimeIrOptimizationReport report) { + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + Map fields = passReport.proofArtifact().fields(); + String blocker = fields.getOrDefault("replacementBlueprint.firstBlocker", "none"); + if (!blocker.isBlank() && !"none".equals(blocker)) { + return blocker; + } + String ruleBlocker = firstRuleBlocker(fields, ".replacementBlueprint.firstBlocker"); + if (!"none".equals(ruleBlocker)) { + return ruleBlocker; + } + } + } + return "none"; + } + + private static int rewriteTransactionCount(GpuRuntimeIrOptimizationReport report) { + return rewriteTransactionRuleCount(report, ".rewriteTransaction.count"); + } + + private static int rewriteTransactionReadyCount(GpuRuntimeIrOptimizationReport report) { + return rewriteTransactionRuleCount(report, ".rewriteTransaction.ready.count"); + } + + private static int rewriteTransactionBlockedCount(GpuRuntimeIrOptimizationReport report) { + int total = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + Map fields = passReport.proofArtifact().fields(); + int passTotal = parseInt(fields.get("rewriteTransaction.blocked.count")); + total += passTotal > 0 ? passTotal : rewriteTransactionRuleCount(fields, ".rewriteTransaction.blocked.count"); + } + } + return total; + } + + private static int rewriteTransactionRuleCount(GpuRuntimeIrOptimizationReport report, String suffix) { + int total = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + total += rewriteTransactionRuleCount(passReport.proofArtifact().fields(), suffix); + } + } + return total; + } + + private static int rewriteTransactionRuleCount(Map fields, String suffix) { + return fields.entrySet().stream() + .filter(entry -> entry.getKey().startsWith("rule.")) + .filter(entry -> entry.getKey().endsWith(suffix)) + .mapToInt(entry -> parseInt(entry.getValue())) + .sum(); + } + + private static String rewriteTransactionFirstBlocker(GpuRuntimeIrOptimizationReport report) { + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + Map fields = passReport.proofArtifact().fields(); + String blocker = fields.getOrDefault("rewriteTransaction.firstBlocker", "none"); + if (!blocker.isBlank() && !"none".equals(blocker)) { + return blocker; + } + String ruleBlocker = firstRuleBlocker(fields, ".rewriteTransaction.firstBlocker"); + if (!"none".equals(ruleBlocker)) { + return ruleBlocker; + } + } + } + return "none"; + } + + private static int nodeIdAllocationCount(GpuRuntimeIrOptimizationReport report) { + return nodeIdAllocationRuleCount(report, ".nodeIdAllocation.count"); + } + + private static int nodeIdAllocationReadyCount(GpuRuntimeIrOptimizationReport report) { + return nodeIdAllocationRuleCount(report, ".nodeIdAllocation.ready.count"); + } + + private static int nodeIdAllocationBlockedCount(GpuRuntimeIrOptimizationReport report) { + int total = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + Map fields = passReport.proofArtifact().fields(); + int passTotal = parseInt(fields.get("nodeIdAllocation.blocked.count")); + total += passTotal > 0 ? passTotal : nodeIdAllocationRuleCount(fields, ".nodeIdAllocation.blocked.count"); + } + } + return total; + } + + private static int nodeIdAllocationRuleCount(GpuRuntimeIrOptimizationReport report, String suffix) { + int total = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + total += nodeIdAllocationRuleCount(passReport.proofArtifact().fields(), suffix); + } + } + return total; + } + + private static int nodeIdAllocationRuleCount(Map fields, String suffix) { + return fields.entrySet().stream() + .filter(entry -> entry.getKey().startsWith("rule.")) + .filter(entry -> entry.getKey().endsWith(suffix)) + .mapToInt(entry -> parseInt(entry.getValue())) + .sum(); + } + + private static String nodeIdAllocationFirstBlocker(GpuRuntimeIrOptimizationReport report) { + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + Map fields = passReport.proofArtifact().fields(); + String blocker = fields.getOrDefault("nodeIdAllocation.firstBlocker", "none"); + if (!blocker.isBlank() && !"none".equals(blocker)) { + return blocker; + } + String ruleBlocker = firstRuleBlocker(fields, ".nodeIdAllocation.firstBlocker"); + if (!"none".equals(ruleBlocker)) { + return ruleBlocker; + } + } + } + return "none"; + } + + private static int replacementNodeCount(GpuRuntimeIrOptimizationReport report) { + return replacementNodeRuleCount(report, ".replacementNode.count"); + } + + private static int replacementNodeReadyCount(GpuRuntimeIrOptimizationReport report) { + return replacementNodeRuleCount(report, ".replacementNode.ready.count"); + } + + private static int replacementNodeBlockedCount(GpuRuntimeIrOptimizationReport report) { + int total = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + Map fields = passReport.proofArtifact().fields(); + int passTotal = parseInt(fields.get("replacementNode.blocked.count")); + total += passTotal > 0 ? passTotal : replacementNodeRuleCount(fields, ".replacementNode.blocked.count"); + } + } + return total; + } + + private static int replacementNodeRuleCount(GpuRuntimeIrOptimizationReport report, String suffix) { + int total = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + total += replacementNodeRuleCount(passReport.proofArtifact().fields(), suffix); + } + } + return total; + } + + private static int replacementNodeRuleCount(Map fields, String suffix) { + return fields.entrySet().stream() + .filter(entry -> entry.getKey().startsWith("rule.")) + .filter(entry -> entry.getKey().endsWith(suffix)) + .mapToInt(entry -> parseInt(entry.getValue())) + .sum(); + } + + private static String replacementNodeFirstBlocker(GpuRuntimeIrOptimizationReport report) { + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + Map fields = passReport.proofArtifact().fields(); + String blocker = fields.getOrDefault("replacementNode.firstBlocker", "none"); + if (!blocker.isBlank() && !"none".equals(blocker)) { + return blocker; + } + String ruleBlocker = firstRuleBlocker(fields, ".replacementNode.firstBlocker"); + if (!"none".equals(ruleBlocker)) { + return ruleBlocker; + } + } + } + return "none"; + } + + private static int graphPatchCount(GpuRuntimeIrOptimizationReport report) { + return graphPatchRuleCount(report, ".graphPatch.count"); + } + + private static int graphPatchReadyCount(GpuRuntimeIrOptimizationReport report) { + return graphPatchRuleCount(report, ".graphPatch.ready.count"); + } + + private static int graphPatchBlockedCount(GpuRuntimeIrOptimizationReport report) { + int total = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + Map fields = passReport.proofArtifact().fields(); + int passTotal = parseInt(fields.get("graphPatch.blocked.count")); + total += passTotal > 0 ? passTotal : graphPatchRuleCount(fields, ".graphPatch.blocked.count"); + } + } + return total; + } + + private static int graphPatchRuleCount(GpuRuntimeIrOptimizationReport report, String suffix) { + int total = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + total += graphPatchRuleCount(passReport.proofArtifact().fields(), suffix); + } + } + return total; + } + + private static int graphPatchRuleCount(Map fields, String suffix) { + return fields.entrySet().stream() + .filter(entry -> entry.getKey().startsWith("rule.")) + .filter(entry -> entry.getKey().endsWith(suffix)) + .mapToInt(entry -> parseInt(entry.getValue())) + .sum(); + } + + private static String graphPatchFirstBlocker(GpuRuntimeIrOptimizationReport report) { + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + Map fields = passReport.proofArtifact().fields(); + String blocker = fields.getOrDefault("graphPatch.firstBlocker", "none"); + if (!blocker.isBlank() && !"none".equals(blocker)) { + return blocker; + } + String ruleBlocker = firstRuleBlocker(fields, ".graphPatch.firstBlocker"); + if (!"none".equals(ruleBlocker)) { + return ruleBlocker; + } + } + } + return "none"; + } + + private static int transformedGraphCount(GpuRuntimeIrOptimizationReport report) { + return transformedGraphRuleCount(report, ".transformedGraph.count"); + } + + private static int transformedGraphReadyCount(GpuRuntimeIrOptimizationReport report) { + return transformedGraphRuleCount(report, ".transformedGraph.ready.count"); + } + + private static int transformedGraphBlockedCount(GpuRuntimeIrOptimizationReport report) { + int total = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + Map fields = passReport.proofArtifact().fields(); + int passTotal = parseInt(fields.get("transformedGraph.blocked.count")); + total += passTotal > 0 ? passTotal : transformedGraphRuleCount(fields, ".transformedGraph.blocked.count"); + } + } + return total; + } + + private static int transformedGraphRuleCount(GpuRuntimeIrOptimizationReport report, String suffix) { + int total = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + total += transformedGraphRuleCount(passReport.proofArtifact().fields(), suffix); + } + } + return total; + } + + private static int transformedGraphRuleCount(Map fields, String suffix) { + return fields.entrySet().stream() + .filter(entry -> entry.getKey().startsWith("rule.")) + .filter(entry -> entry.getKey().endsWith(suffix)) + .mapToInt(entry -> parseInt(entry.getValue())) + .sum(); + } + + private static String transformedGraphFirstBlocker(GpuRuntimeIrOptimizationReport report) { + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + Map fields = passReport.proofArtifact().fields(); + String blocker = fields.getOrDefault("transformedGraph.firstBlocker", "none"); + if (!blocker.isBlank() && !"none".equals(blocker)) { + return blocker; + } + String ruleBlocker = firstRuleBlocker(fields, ".transformedGraph.firstBlocker"); + if (!"none".equals(ruleBlocker)) { + return ruleBlocker; + } + } + } + return "none"; + } + + private static int irArtifactEnvelopeCount(GpuRuntimeIrOptimizationReport report) { + return irArtifactEnvelopeRuleCount(report, ".irArtifactEnvelope.count"); + } + + private static int irArtifactEnvelopeReadyCount(GpuRuntimeIrOptimizationReport report) { + return irArtifactEnvelopeRuleCount(report, ".irArtifactEnvelope.ready.count"); + } + + private static int irArtifactEnvelopeBlockedCount(GpuRuntimeIrOptimizationReport report) { + int total = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + Map fields = passReport.proofArtifact().fields(); + int passTotal = parseInt(fields.get("irArtifactEnvelope.blocked.count")); + total += passTotal > 0 ? passTotal : irArtifactEnvelopeRuleCount(fields, ".irArtifactEnvelope.blocked.count"); + } + } + return total; + } + + private static int irArtifactEnvelopeRuleCount(GpuRuntimeIrOptimizationReport report, String suffix) { + int total = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + total += irArtifactEnvelopeRuleCount(passReport.proofArtifact().fields(), suffix); + } + } + return total; + } + + private static int irArtifactEnvelopeRuleCount(Map fields, String suffix) { + return fields.entrySet().stream() + .filter(entry -> entry.getKey().startsWith("rule.")) + .filter(entry -> entry.getKey().endsWith(suffix)) + .mapToInt(entry -> parseInt(entry.getValue())) + .sum(); + } + + private static String irArtifactEnvelopeFirstBlocker(GpuRuntimeIrOptimizationReport report) { + for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { + if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { + Map fields = passReport.proofArtifact().fields(); + String blocker = fields.getOrDefault("irArtifactEnvelope.firstBlocker", "none"); + if (!blocker.isBlank() && !"none".equals(blocker)) { + return blocker; + } + String ruleBlocker = firstRuleBlocker(fields, ".irArtifactEnvelope.firstBlocker"); + if (!"none".equals(ruleBlocker)) { + return ruleBlocker; + } + } + } + return "none"; + } + private static int rewriteSketchCount(GpuRuntimeIrOptimizationReport report) { return rewriteSketchRuleCount(report, ".rewriteSketch.count"); } @@ -694,6 +1346,70 @@ private static String formatOptimizerRuleSummary(Map fields, String prefix) { && !"none".equals(nextSketchBlocker)) { sketchBlocker = nextSketchBlocker; } + String visitorBlocker = rewriteVisitorFirstBlocker; + String nextVisitorBlocker = fields.getOrDefault(prefix + ".rewriteVisitor.firstBlocker", "none"); + if ("none".equals(visitorBlocker) + && !nextVisitorBlocker.isBlank() + && !"none".equals(nextVisitorBlocker)) { + visitorBlocker = nextVisitorBlocker; + } + String blueprintBlocker = replacementBlueprintFirstBlocker; + String nextBlueprintBlocker = fields.getOrDefault(prefix + ".replacementBlueprint.firstBlocker", "none"); + if ("none".equals(blueprintBlocker) + && !nextBlueprintBlocker.isBlank() + && !"none".equals(nextBlueprintBlocker)) { + blueprintBlocker = nextBlueprintBlocker; + } + String transactionBlocker = rewriteTransactionFirstBlocker; + String nextTransactionBlocker = fields.getOrDefault(prefix + ".rewriteTransaction.firstBlocker", "none"); + if ("none".equals(transactionBlocker) + && !nextTransactionBlocker.isBlank() + && !"none".equals(nextTransactionBlocker)) { + transactionBlocker = nextTransactionBlocker; + } + String allocationBlocker = nodeIdAllocationFirstBlocker; + String nextAllocationBlocker = fields.getOrDefault(prefix + ".nodeIdAllocation.firstBlocker", "none"); + if ("none".equals(allocationBlocker) + && !nextAllocationBlocker.isBlank() + && !"none".equals(nextAllocationBlocker)) { + allocationBlocker = nextAllocationBlocker; + } + String replacementNodeBlocker = replacementNodeFirstBlocker; + String nextReplacementNodeBlocker = fields.getOrDefault(prefix + ".replacementNode.firstBlocker", "none"); + if ("none".equals(replacementNodeBlocker) + && !nextReplacementNodeBlocker.isBlank() + && !"none".equals(nextReplacementNodeBlocker)) { + replacementNodeBlocker = nextReplacementNodeBlocker; + } + String graphPatchBlocker = graphPatchFirstBlocker; + String nextGraphPatchBlocker = fields.getOrDefault(prefix + ".graphPatch.firstBlocker", "none"); + if ("none".equals(graphPatchBlocker) + && !nextGraphPatchBlocker.isBlank() + && !"none".equals(nextGraphPatchBlocker)) { + graphPatchBlocker = nextGraphPatchBlocker; + } + String transformedGraphBlocker = transformedGraphFirstBlocker; + String nextTransformedGraphBlocker = fields.getOrDefault(prefix + ".transformedGraph.firstBlocker", "none"); + if ("none".equals(transformedGraphBlocker) + && !nextTransformedGraphBlocker.isBlank() + && !"none".equals(nextTransformedGraphBlocker)) { + transformedGraphBlocker = nextTransformedGraphBlocker; + } + String irArtifactEnvelopeBlocker = irArtifactEnvelopeFirstBlocker; + String nextIrArtifactEnvelopeBlocker = fields.getOrDefault(prefix + ".irArtifactEnvelope.firstBlocker", "none"); + if ("none".equals(irArtifactEnvelopeBlocker) + && !nextIrArtifactEnvelopeBlocker.isBlank() + && !"none".equals(nextIrArtifactEnvelopeBlocker)) { + irArtifactEnvelopeBlocker = nextIrArtifactEnvelopeBlocker; + } String selectionStatus = firstNonDefault( rewriteSelectionStatus, fields.getOrDefault(prefix + ".rewriteSelection.status", "not-required"), @@ -1036,6 +1956,38 @@ private OptimizerRuleEvidence add(Map fields, String prefix) { replacementPlanValidationValidCount + parseInt(fields.get(prefix + ".replacementPlan.validation.valid.count")), replacementPlanValidationInvalidCount + parseInt(fields.get(prefix + ".replacementPlan.validation.invalid.count")), validationBlocker, + rewriteVisitorCount + parseInt(fields.get(prefix + ".rewriteVisitor.count")), + rewriteVisitorReadyCount + parseInt(fields.get(prefix + ".rewriteVisitor.ready.count")), + rewriteVisitorBlockedCount + parseInt(fields.get(prefix + ".rewriteVisitor.blocked.count")), + visitorBlocker, + replacementBlueprintCount + parseInt(fields.get(prefix + ".replacementBlueprint.count")), + replacementBlueprintReadyCount + parseInt(fields.get(prefix + ".replacementBlueprint.ready.count")), + replacementBlueprintBlockedCount + parseInt(fields.get(prefix + ".replacementBlueprint.blocked.count")), + blueprintBlocker, + rewriteTransactionCount + parseInt(fields.get(prefix + ".rewriteTransaction.count")), + rewriteTransactionReadyCount + parseInt(fields.get(prefix + ".rewriteTransaction.ready.count")), + rewriteTransactionBlockedCount + parseInt(fields.get(prefix + ".rewriteTransaction.blocked.count")), + transactionBlocker, + nodeIdAllocationCount + parseInt(fields.get(prefix + ".nodeIdAllocation.count")), + nodeIdAllocationReadyCount + parseInt(fields.get(prefix + ".nodeIdAllocation.ready.count")), + nodeIdAllocationBlockedCount + parseInt(fields.get(prefix + ".nodeIdAllocation.blocked.count")), + allocationBlocker, + replacementNodeCount + parseInt(fields.get(prefix + ".replacementNode.count")), + replacementNodeReadyCount + parseInt(fields.get(prefix + ".replacementNode.ready.count")), + replacementNodeBlockedCount + parseInt(fields.get(prefix + ".replacementNode.blocked.count")), + replacementNodeBlocker, + graphPatchCount + parseInt(fields.get(prefix + ".graphPatch.count")), + graphPatchReadyCount + parseInt(fields.get(prefix + ".graphPatch.ready.count")), + graphPatchBlockedCount + parseInt(fields.get(prefix + ".graphPatch.blocked.count")), + graphPatchBlocker, + transformedGraphCount + parseInt(fields.get(prefix + ".transformedGraph.count")), + transformedGraphReadyCount + parseInt(fields.get(prefix + ".transformedGraph.ready.count")), + transformedGraphBlockedCount + parseInt(fields.get(prefix + ".transformedGraph.blocked.count")), + transformedGraphBlocker, + irArtifactEnvelopeCount + parseInt(fields.get(prefix + ".irArtifactEnvelope.count")), + irArtifactEnvelopeReadyCount + parseInt(fields.get(prefix + ".irArtifactEnvelope.ready.count")), + irArtifactEnvelopeBlockedCount + parseInt(fields.get(prefix + ".irArtifactEnvelope.blocked.count")), + irArtifactEnvelopeBlocker, rewriteSketchCount + parseInt(fields.get(prefix + ".rewriteSketch.count")), rewriteSketchReadyCount + parseInt(fields.get(prefix + ".rewriteSketch.ready.count")), rewriteSketchBlockedCount + parseInt(fields.get(prefix + ".rewriteSketch.blocked.count")), diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java index b27d2fd0..2d646668 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java @@ -2021,6 +2021,38 @@ void optimizerDriftArtifactCapturesReadOnlyReplacementPlanTelemetry() { Map.entry("rewriteSketch.ready.count", "5"), Map.entry("rewriteSketch.blocked.count", "1"), Map.entry("rewriteSketch.firstBlocker", "replacement-plan-root-missing"), + Map.entry("rewriteVisitor.count", "6"), + Map.entry("rewriteVisitor.ready.count", "5"), + Map.entry("rewriteVisitor.blocked.count", "1"), + Map.entry("rewriteVisitor.firstBlocker", "replacement-plan-root-missing"), + Map.entry("replacementBlueprint.count", "6"), + Map.entry("replacementBlueprint.ready.count", "5"), + Map.entry("replacementBlueprint.blocked.count", "1"), + Map.entry("replacementBlueprint.firstBlocker", "replacement-plan-root-missing"), + Map.entry("rewriteTransaction.count", "6"), + Map.entry("rewriteTransaction.ready.count", "5"), + Map.entry("rewriteTransaction.blocked.count", "1"), + Map.entry("rewriteTransaction.firstBlocker", "replacement-plan-root-missing"), + Map.entry("nodeIdAllocation.count", "6"), + Map.entry("nodeIdAllocation.ready.count", "5"), + Map.entry("nodeIdAllocation.blocked.count", "1"), + Map.entry("nodeIdAllocation.firstBlocker", "replacement-plan-root-missing"), + Map.entry("replacementNode.count", "6"), + Map.entry("replacementNode.ready.count", "5"), + Map.entry("replacementNode.blocked.count", "1"), + Map.entry("replacementNode.firstBlocker", "replacement-plan-root-missing"), + Map.entry("graphPatch.count", "6"), + Map.entry("graphPatch.ready.count", "5"), + Map.entry("graphPatch.blocked.count", "1"), + Map.entry("graphPatch.firstBlocker", "replacement-plan-root-missing"), + Map.entry("transformedGraph.count", "6"), + Map.entry("transformedGraph.ready.count", "5"), + Map.entry("transformedGraph.blocked.count", "1"), + Map.entry("transformedGraph.firstBlocker", "replacement-plan-root-missing"), + Map.entry("irArtifactEnvelope.count", "6"), + Map.entry("irArtifactEnvelope.ready.count", "5"), + Map.entry("irArtifactEnvelope.blocked.count", "1"), + Map.entry("irArtifactEnvelope.firstBlocker", "replacement-plan-root-missing"), Map.entry("rewriteSketch.conflict.count", "1"), Map.entry("rewriteSketch.conflict.firstBlocker", "rewrite-sketch-covered-node-overlap"), Map.entry("rewriteSelection.status", "blocked"), @@ -2055,6 +2087,38 @@ void optimizerDriftArtifactCapturesReadOnlyReplacementPlanTelemetry() { Map.entry("rule.0.rewriteSketch.ready.count", "2"), Map.entry("rule.0.rewriteSketch.blocked.count", "1"), Map.entry("rule.0.rewriteSketch.firstBlocker", "replacement-plan-root-missing"), + Map.entry("rule.0.rewriteVisitor.count", "3"), + Map.entry("rule.0.rewriteVisitor.ready.count", "2"), + Map.entry("rule.0.rewriteVisitor.blocked.count", "1"), + Map.entry("rule.0.rewriteVisitor.firstBlocker", "replacement-plan-root-missing"), + Map.entry("rule.0.replacementBlueprint.count", "3"), + Map.entry("rule.0.replacementBlueprint.ready.count", "2"), + Map.entry("rule.0.replacementBlueprint.blocked.count", "1"), + Map.entry("rule.0.replacementBlueprint.firstBlocker", "replacement-plan-root-missing"), + Map.entry("rule.0.rewriteTransaction.count", "3"), + Map.entry("rule.0.rewriteTransaction.ready.count", "2"), + Map.entry("rule.0.rewriteTransaction.blocked.count", "1"), + Map.entry("rule.0.rewriteTransaction.firstBlocker", "replacement-plan-root-missing"), + Map.entry("rule.0.nodeIdAllocation.count", "3"), + Map.entry("rule.0.nodeIdAllocation.ready.count", "2"), + Map.entry("rule.0.nodeIdAllocation.blocked.count", "1"), + Map.entry("rule.0.nodeIdAllocation.firstBlocker", "replacement-plan-root-missing"), + Map.entry("rule.0.replacementNode.count", "3"), + Map.entry("rule.0.replacementNode.ready.count", "2"), + Map.entry("rule.0.replacementNode.blocked.count", "1"), + Map.entry("rule.0.replacementNode.firstBlocker", "replacement-plan-root-missing"), + Map.entry("rule.0.graphPatch.count", "3"), + Map.entry("rule.0.graphPatch.ready.count", "2"), + Map.entry("rule.0.graphPatch.blocked.count", "1"), + Map.entry("rule.0.graphPatch.firstBlocker", "replacement-plan-root-missing"), + Map.entry("rule.0.transformedGraph.count", "3"), + Map.entry("rule.0.transformedGraph.ready.count", "2"), + Map.entry("rule.0.transformedGraph.blocked.count", "1"), + Map.entry("rule.0.transformedGraph.firstBlocker", "replacement-plan-root-missing"), + Map.entry("rule.0.irArtifactEnvelope.count", "3"), + Map.entry("rule.0.irArtifactEnvelope.ready.count", "2"), + Map.entry("rule.0.irArtifactEnvelope.blocked.count", "1"), + Map.entry("rule.0.irArtifactEnvelope.firstBlocker", "replacement-plan-root-missing"), Map.entry("rule.0.rewriteSelection.status", "blocked"), Map.entry("rule.0.rewriteSelection.firstBlocker", "replacement-plan-root-missing"), Map.entry("rule.0.rewriteProof.status", "blocked"), @@ -2087,6 +2151,38 @@ void optimizerDriftArtifactCapturesReadOnlyReplacementPlanTelemetry() { Map.entry("rule.1.rewriteSketch.ready.count", "3"), Map.entry("rule.1.rewriteSketch.blocked.count", "0"), Map.entry("rule.1.rewriteSketch.firstBlocker", "none"), + Map.entry("rule.1.rewriteVisitor.count", "3"), + Map.entry("rule.1.rewriteVisitor.ready.count", "3"), + Map.entry("rule.1.rewriteVisitor.blocked.count", "0"), + Map.entry("rule.1.rewriteVisitor.firstBlocker", "none"), + Map.entry("rule.1.replacementBlueprint.count", "3"), + Map.entry("rule.1.replacementBlueprint.ready.count", "3"), + Map.entry("rule.1.replacementBlueprint.blocked.count", "0"), + Map.entry("rule.1.replacementBlueprint.firstBlocker", "none"), + Map.entry("rule.1.rewriteTransaction.count", "3"), + Map.entry("rule.1.rewriteTransaction.ready.count", "3"), + Map.entry("rule.1.rewriteTransaction.blocked.count", "0"), + Map.entry("rule.1.rewriteTransaction.firstBlocker", "none"), + Map.entry("rule.1.nodeIdAllocation.count", "3"), + Map.entry("rule.1.nodeIdAllocation.ready.count", "3"), + Map.entry("rule.1.nodeIdAllocation.blocked.count", "0"), + Map.entry("rule.1.nodeIdAllocation.firstBlocker", "none"), + Map.entry("rule.1.replacementNode.count", "3"), + Map.entry("rule.1.replacementNode.ready.count", "3"), + Map.entry("rule.1.replacementNode.blocked.count", "0"), + Map.entry("rule.1.replacementNode.firstBlocker", "none"), + Map.entry("rule.1.graphPatch.count", "3"), + Map.entry("rule.1.graphPatch.ready.count", "3"), + Map.entry("rule.1.graphPatch.blocked.count", "0"), + Map.entry("rule.1.graphPatch.firstBlocker", "none"), + Map.entry("rule.1.transformedGraph.count", "3"), + Map.entry("rule.1.transformedGraph.ready.count", "3"), + Map.entry("rule.1.transformedGraph.blocked.count", "0"), + Map.entry("rule.1.transformedGraph.firstBlocker", "none"), + Map.entry("rule.1.irArtifactEnvelope.count", "3"), + Map.entry("rule.1.irArtifactEnvelope.ready.count", "3"), + Map.entry("rule.1.irArtifactEnvelope.blocked.count", "0"), + Map.entry("rule.1.irArtifactEnvelope.firstBlocker", "none"), Map.entry("rule.1.rewriteSelection.status", "blocked"), Map.entry("rule.1.rewriteSelection.firstBlocker", "rewrite-builder-not-implemented"), Map.entry("rule.1.rewriteProof.status", "blocked"), @@ -2125,6 +2221,80 @@ void optimizerDriftArtifactCapturesReadOnlyReplacementPlanTelemetry() { assertTrue(drift.contains("replacementPlan.validation.valid.count=5")); assertTrue(drift.contains("replacementPlan.validation.invalid.count=1")); assertTrue(drift.contains("replacementPlan.validation.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("rewriteVisitor.count=6")); + assertTrue(drift.contains("rewriteVisitor.ready.count=5")); + assertTrue(drift.contains("rewriteVisitor.blocked.count=1")); + assertTrue(drift.contains("rewriteVisitor.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("rewriteVisitor.visitorImplemented=true")); + assertTrue(drift.contains("rewriteVisitor.replacementBuilderImplemented=false")); + assertTrue(drift.contains("rewriteVisitor.transformedIrBuilt=false")); + assertTrue(drift.contains("rewriteVisitor.selectedIrReplacement=false")); + assertTrue(drift.contains("replacementBlueprint.count=6")); + assertTrue(drift.contains("replacementBlueprint.ready.count=5")); + assertTrue(drift.contains("replacementBlueprint.blocked.count=1")); + assertTrue(drift.contains("replacementBlueprint.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("replacementBlueprint.blueprintImplemented=true")); + assertTrue(drift.contains("replacementBlueprint.replacementBuilderImplemented=false")); + assertTrue(drift.contains("replacementBlueprint.transformedIrBuilt=false")); + assertTrue(drift.contains("replacementBlueprint.selectedIrReplacement=false")); + assertTrue(drift.contains("rewriteTransaction.count=6")); + assertTrue(drift.contains("rewriteTransaction.ready.count=5")); + assertTrue(drift.contains("rewriteTransaction.blocked.count=1")); + assertTrue(drift.contains("rewriteTransaction.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("rewriteTransaction.transactionPreflightImplemented=true")); + assertTrue(drift.contains("rewriteTransaction.nodeIdAllocatorImplemented=false")); + assertTrue(drift.contains("rewriteTransaction.graphRewriteImplemented=false")); + assertTrue(drift.contains("rewriteTransaction.transformedIrBuilt=false")); + assertTrue(drift.contains("rewriteTransaction.selectedIrReplacement=false")); + assertTrue(drift.contains("nodeIdAllocation.count=6")); + assertTrue(drift.contains("nodeIdAllocation.ready.count=5")); + assertTrue(drift.contains("nodeIdAllocation.blocked.count=1")); + assertTrue(drift.contains("nodeIdAllocation.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("nodeIdAllocation.allocationPreflightImplemented=true")); + assertTrue(drift.contains("nodeIdAllocation.nodeIdsReserved=false")); + assertTrue(drift.contains("nodeIdAllocation.nodeIdAllocatorApplied=false")); + assertTrue(drift.contains("nodeIdAllocation.graphRewriteImplemented=false")); + assertTrue(drift.contains("nodeIdAllocation.transformedIrBuilt=false")); + assertTrue(drift.contains("nodeIdAllocation.selectedIrReplacement=false")); + assertTrue(drift.contains("replacementNode.count=6")); + assertTrue(drift.contains("replacementNode.ready.count=5")); + assertTrue(drift.contains("replacementNode.blocked.count=1")); + assertTrue(drift.contains("replacementNode.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("replacementNode.replacementNodePreflightImplemented=true")); + assertTrue(drift.contains("replacementNode.replacementNodeBuilt=false")); + assertTrue(drift.contains("replacementNode.replacementBuilderImplemented=false")); + assertTrue(drift.contains("replacementNode.graphRewriteImplemented=false")); + assertTrue(drift.contains("replacementNode.transformedIrBuilt=false")); + assertTrue(drift.contains("replacementNode.selectedIrReplacement=false")); + assertTrue(drift.contains("graphPatch.count=6")); + assertTrue(drift.contains("graphPatch.ready.count=5")); + assertTrue(drift.contains("graphPatch.blocked.count=1")); + assertTrue(drift.contains("graphPatch.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("graphPatch.graphPatchPreflightImplemented=true")); + assertTrue(drift.contains("graphPatch.graphPatchApplied=false")); + assertTrue(drift.contains("graphPatch.graphRewriteImplemented=false")); + assertTrue(drift.contains("graphPatch.transformedIrBuilt=false")); + assertTrue(drift.contains("graphPatch.selectedIrReplacement=false")); + assertTrue(drift.contains("transformedGraph.count=6")); + assertTrue(drift.contains("transformedGraph.ready.count=5")); + assertTrue(drift.contains("transformedGraph.blocked.count=1")); + assertTrue(drift.contains("transformedGraph.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("transformedGraph.materializationPreflightImplemented=true")); + assertTrue(drift.contains("transformedGraph.transformedGraphBuilt=false")); + assertTrue(drift.contains("transformedGraph.transformedIrBuilt=false")); + assertTrue(drift.contains("transformedGraph.graphPatchApplied=false")); + assertTrue(drift.contains("transformedGraph.graphRewriteImplemented=false")); + assertTrue(drift.contains("transformedGraph.selectedIrReplacement=false")); + assertTrue(drift.contains("irArtifactEnvelope.count=6")); + assertTrue(drift.contains("irArtifactEnvelope.ready.count=5")); + assertTrue(drift.contains("irArtifactEnvelope.blocked.count=1")); + assertTrue(drift.contains("irArtifactEnvelope.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("irArtifactEnvelope.artifactEnvelopePreflightImplemented=true")); + assertTrue(drift.contains("irArtifactEnvelope.artifactEnvelopeBuilt=false")); + assertTrue(drift.contains("irArtifactEnvelope.optimizedArtifactBuilt=false")); + assertTrue(drift.contains("irArtifactEnvelope.transformedGraphBuilt=false")); + assertTrue(drift.contains("irArtifactEnvelope.transformedIrBuilt=false")); + assertTrue(drift.contains("irArtifactEnvelope.selectedIrReplacement=false")); assertTrue(drift.contains("rewriteSketch.count=6")); assertTrue(drift.contains("rewriteSketch.ready.count=5")); assertTrue(drift.contains("rewriteSketch.blocked.count=1")); @@ -2171,6 +2341,52 @@ void optimizerDriftArtifactCapturesReadOnlyReplacementPlanTelemetry() { assertTrue(drift.contains("optimizerRule.0.replacementPlan.validation.count=3")); assertTrue(drift.contains("optimizerRule.0.replacementPlan.validation.invalid.count=1")); assertTrue(drift.contains("optimizerRule.0.replacementPlan.validation.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("optimizerRule.0.rewriteVisitor.count=3")); + assertTrue(drift.contains("optimizerRule.0.rewriteVisitor.ready.count=2")); + assertTrue(drift.contains("optimizerRule.0.rewriteVisitor.blocked.count=1")); + assertTrue(drift.contains("optimizerRule.0.rewriteVisitor.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("optimizerRule.0.rewriteVisitor.selectedIrReplacement=false")); + assertTrue(drift.contains("optimizerRule.0.replacementBlueprint.count=3")); + assertTrue(drift.contains("optimizerRule.0.replacementBlueprint.ready.count=2")); + assertTrue(drift.contains("optimizerRule.0.replacementBlueprint.blocked.count=1")); + assertTrue(drift.contains("optimizerRule.0.replacementBlueprint.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("optimizerRule.0.replacementBlueprint.selectedIrReplacement=false")); + assertTrue(drift.contains("optimizerRule.0.rewriteTransaction.count=3")); + assertTrue(drift.contains("optimizerRule.0.rewriteTransaction.ready.count=2")); + assertTrue(drift.contains("optimizerRule.0.rewriteTransaction.blocked.count=1")); + assertTrue(drift.contains("optimizerRule.0.rewriteTransaction.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("optimizerRule.0.rewriteTransaction.selectedIrReplacement=false")); + assertTrue(drift.contains("optimizerRule.0.nodeIdAllocation.count=3")); + assertTrue(drift.contains("optimizerRule.0.nodeIdAllocation.ready.count=2")); + assertTrue(drift.contains("optimizerRule.0.nodeIdAllocation.blocked.count=1")); + assertTrue(drift.contains("optimizerRule.0.nodeIdAllocation.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("optimizerRule.0.nodeIdAllocation.nodeIdsReserved=false")); + assertTrue(drift.contains("optimizerRule.0.nodeIdAllocation.selectedIrReplacement=false")); + assertTrue(drift.contains("optimizerRule.0.replacementNode.count=3")); + assertTrue(drift.contains("optimizerRule.0.replacementNode.ready.count=2")); + assertTrue(drift.contains("optimizerRule.0.replacementNode.blocked.count=1")); + assertTrue(drift.contains("optimizerRule.0.replacementNode.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("optimizerRule.0.replacementNode.replacementNodeBuilt=false")); + assertTrue(drift.contains("optimizerRule.0.replacementNode.selectedIrReplacement=false")); + assertTrue(drift.contains("optimizerRule.0.graphPatch.count=3")); + assertTrue(drift.contains("optimizerRule.0.graphPatch.ready.count=2")); + assertTrue(drift.contains("optimizerRule.0.graphPatch.blocked.count=1")); + assertTrue(drift.contains("optimizerRule.0.graphPatch.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("optimizerRule.0.graphPatch.graphPatchApplied=false")); + assertTrue(drift.contains("optimizerRule.0.graphPatch.selectedIrReplacement=false")); + assertTrue(drift.contains("optimizerRule.0.transformedGraph.count=3")); + assertTrue(drift.contains("optimizerRule.0.transformedGraph.ready.count=2")); + assertTrue(drift.contains("optimizerRule.0.transformedGraph.blocked.count=1")); + assertTrue(drift.contains("optimizerRule.0.transformedGraph.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("optimizerRule.0.transformedGraph.transformedGraphBuilt=false")); + assertTrue(drift.contains("optimizerRule.0.transformedGraph.selectedIrReplacement=false")); + assertTrue(drift.contains("optimizerRule.0.irArtifactEnvelope.count=3")); + assertTrue(drift.contains("optimizerRule.0.irArtifactEnvelope.ready.count=2")); + assertTrue(drift.contains("optimizerRule.0.irArtifactEnvelope.blocked.count=1")); + assertTrue(drift.contains("optimizerRule.0.irArtifactEnvelope.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("optimizerRule.0.irArtifactEnvelope.artifactEnvelopeBuilt=false")); + assertTrue(drift.contains("optimizerRule.0.irArtifactEnvelope.optimizedArtifactBuilt=false")); + assertTrue(drift.contains("optimizerRule.0.irArtifactEnvelope.selectedIrReplacement=false")); assertTrue(drift.contains("optimizerRule.0.rewriteSketch.count=3")); assertTrue(drift.contains("optimizerRule.0.rewriteSketch.ready.count=2")); assertTrue(drift.contains("optimizerRule.0.rewriteSketch.blocked.count=1")); @@ -2190,6 +2406,10 @@ void optimizerDriftArtifactCapturesReadOnlyReplacementPlanTelemetry() { assertTrue(drift.contains("optimizerRule.1.skipped.count=1")); assertTrue(drift.contains("optimizerRule.summary=madFma[candidates=2, proposals=0, applied=0, skipped=0, blocked=1")); assertTrue(drift.contains("planValidations=3, invalidPlanValidations=1, planValidationFirstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("replacementNodes=3, readyReplacementNodes=2, blockedReplacementNodes=1, replacementNodeFirstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("graphPatches=3, readyGraphPatches=2, blockedGraphPatches=1, graphPatchFirstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("transformedGraphs=3, readyTransformedGraphs=2, blockedTransformedGraphs=1, transformedGraphFirstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("irArtifactEnvelopes=3, readyIrArtifactEnvelopes=2, blockedIrArtifactEnvelopes=1, irArtifactEnvelopeFirstBlocker=replacement-plan-root-missing")); assertTrue(drift.contains("rewriteSketches=3, readySketches=2, blockedSketches=1, rewriteSketchFirstBlocker=replacement-plan-root-missing, rewriteSelectionStatus=blocked, rewriteSelectionFirstBlocker=replacement-plan-root-missing, rewriteProofStatus=blocked, rewriteProofFirstBlocker=runtime-equivalence-payload-missing")); assertTrue(drift.contains("optimizerFamily.count=1")); assertTrue(drift.contains("optimizerFamily.summary=peephole[passes=1, acceptedProof=0, blockingProof=1, rolledBack=0, failed=0, promotionReady=false]")); diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistryTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistryTest.java index 505f264c..bd1db202 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistryTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistryTest.java @@ -579,6 +579,139 @@ void diagnosticPeepholePassFindsTypedMadFmaCandidateWithoutMutatingIr() { assertEquals("true", passReport.proofArtifact().fields().get("rule.0.replacementPlan.validation.0.coveredIncludesRoot")); assertEquals("none", passReport.proofArtifact().fields().get("rule.0.replacementPlan.validation.0.missingCoveredNodeIds")); assertEquals("none", passReport.proofArtifact().fields().get("rule.0.replacementPlan.validation.0.missingInputNodeIds")); + assertEquals("1", passReport.proofArtifact().fields().get("rewriteVisitor.count")); + assertEquals("1", passReport.proofArtifact().fields().get("rewriteVisitor.ready.count")); + assertEquals("0", passReport.proofArtifact().fields().get("rewriteVisitor.blocked.count")); + assertEquals("none", passReport.proofArtifact().fields().get("rewriteVisitor.firstBlocker")); + assertEquals("true", passReport.proofArtifact().fields().get("rewriteVisitor.visitorImplemented")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteVisitor.replacementBuilderImplemented")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteVisitor.transformedIrBuilt")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteVisitor.mutationAllowed")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteVisitor.selectedIrReplacement")); + assertEquals("visitor-ready", passReport.proofArtifact().fields().get("rule.0.rewriteVisitor.0.status")); + assertEquals("1,2,3,4,5", passReport.proofArtifact().fields().get("rule.0.rewriteVisitor.0.visitOrderNodeIds")); + assertEquals("5", passReport.proofArtifact().fields().get("rule.0.rewriteVisitor.0.graphNodeMaxId")); + assertEquals("true", passReport.proofArtifact().fields().get("rule.0.rewriteVisitor.0.visitorReady")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.rewriteVisitor.0.transformedIrBuilt")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.rewriteVisitor.0.selectedIrReplacement")); + assertEquals("1", passReport.proofArtifact().fields().get("replacementBlueprint.count")); + assertEquals("1", passReport.proofArtifact().fields().get("replacementBlueprint.ready.count")); + assertEquals("0", passReport.proofArtifact().fields().get("replacementBlueprint.blocked.count")); + assertEquals("none", passReport.proofArtifact().fields().get("replacementBlueprint.firstBlocker")); + assertEquals("true", passReport.proofArtifact().fields().get("replacementBlueprint.blueprintImplemented")); + assertEquals("false", passReport.proofArtifact().fields().get("replacementBlueprint.replacementBuilderImplemented")); + assertEquals("false", passReport.proofArtifact().fields().get("replacementBlueprint.transformedIrBuilt")); + assertEquals("false", passReport.proofArtifact().fields().get("replacementBlueprint.selectedIrReplacement")); + assertEquals("blueprint-ready", passReport.proofArtifact().fields().get("rule.0.replacementBlueprint.0.status")); + assertEquals("GpuIrIntrinsicCall", passReport.proofArtifact().fields().get("rule.0.replacementBlueprint.0.targetNodeKind")); + assertEquals("mad-fma", passReport.proofArtifact().fields().get("rule.0.replacementBlueprint.0.targetOperation")); + assertEquals("3,4,5", passReport.proofArtifact().fields().get("rule.0.replacementBlueprint.0.argumentNodeIds")); + assertEquals("arg0,arg1,arg2", passReport.proofArtifact().fields().get("rule.0.replacementBlueprint.0.argumentRoles")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.replacementBlueprint.0.transformedIrBuilt")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.replacementBlueprint.0.selectedIrReplacement")); + assertEquals("1", passReport.proofArtifact().fields().get("rewriteTransaction.count")); + assertEquals("1", passReport.proofArtifact().fields().get("rewriteTransaction.ready.count")); + assertEquals("0", passReport.proofArtifact().fields().get("rewriteTransaction.blocked.count")); + assertEquals("none", passReport.proofArtifact().fields().get("rewriteTransaction.firstBlocker")); + assertEquals("true", passReport.proofArtifact().fields().get("rewriteTransaction.transactionPreflightImplemented")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteTransaction.nodeIdAllocatorImplemented")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteTransaction.graphRewriteImplemented")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteTransaction.transformedIrBuilt")); + assertEquals("false", passReport.proofArtifact().fields().get("rewriteTransaction.selectedIrReplacement")); + assertEquals("transaction-ready", passReport.proofArtifact().fields().get("rule.0.rewriteTransaction.0.status")); + assertEquals("1", passReport.proofArtifact().fields().get("rule.0.rewriteTransaction.0.replacedNodeIds")); + assertEquals("1,2", passReport.proofArtifact().fields().get("rule.0.rewriteTransaction.0.removedNodeIds")); + assertEquals("3,4,5", passReport.proofArtifact().fields().get("rule.0.rewriteTransaction.0.retainedInputNodeIds")); + assertEquals("1", passReport.proofArtifact().fields().get("rule.0.rewriteTransaction.0.plannedAddedNode.count")); + assertEquals("not-allocated", passReport.proofArtifact().fields().get("rule.0.rewriteTransaction.0.plannedAddedNodeIds")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.rewriteTransaction.0.graphRewriteImplemented")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.rewriteTransaction.0.selectedIrReplacement")); + assertEquals("1", passReport.proofArtifact().fields().get("nodeIdAllocation.count")); + assertEquals("1", passReport.proofArtifact().fields().get("nodeIdAllocation.ready.count")); + assertEquals("0", passReport.proofArtifact().fields().get("nodeIdAllocation.blocked.count")); + assertEquals("none", passReport.proofArtifact().fields().get("nodeIdAllocation.firstBlocker")); + assertEquals("true", passReport.proofArtifact().fields().get("nodeIdAllocation.allocationPreflightImplemented")); + assertEquals("false", passReport.proofArtifact().fields().get("nodeIdAllocation.nodeIdsReserved")); + assertEquals("false", passReport.proofArtifact().fields().get("nodeIdAllocation.nodeIdAllocatorApplied")); + assertEquals("false", passReport.proofArtifact().fields().get("nodeIdAllocation.graphRewriteImplemented")); + assertEquals("false", passReport.proofArtifact().fields().get("nodeIdAllocation.selectedIrReplacement")); + assertEquals("allocation-ready", passReport.proofArtifact().fields().get("rule.0.nodeIdAllocation.0.status")); + assertEquals("5", passReport.proofArtifact().fields().get("rule.0.nodeIdAllocation.0.graphNodeMaxId")); + assertEquals("1", passReport.proofArtifact().fields().get("rule.0.nodeIdAllocation.0.plannedAddedNode.count")); + assertEquals("6", passReport.proofArtifact().fields().get("rule.0.nodeIdAllocation.0.candidateNodeIds")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.nodeIdAllocation.0.nodeIdsReserved")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.nodeIdAllocation.0.selectedIrReplacement")); + assertEquals("1", passReport.proofArtifact().fields().get("replacementNode.count")); + assertEquals("1", passReport.proofArtifact().fields().get("replacementNode.ready.count")); + assertEquals("0", passReport.proofArtifact().fields().get("replacementNode.blocked.count")); + assertEquals("none", passReport.proofArtifact().fields().get("replacementNode.firstBlocker")); + assertEquals("true", passReport.proofArtifact().fields().get("replacementNode.replacementNodePreflightImplemented")); + assertEquals("false", passReport.proofArtifact().fields().get("replacementNode.replacementNodeBuilt")); + assertEquals("false", passReport.proofArtifact().fields().get("replacementNode.replacementBuilderImplemented")); + assertEquals("false", passReport.proofArtifact().fields().get("replacementNode.graphRewriteImplemented")); + assertEquals("false", passReport.proofArtifact().fields().get("replacementNode.selectedIrReplacement")); + assertEquals("replacement-node-ready", passReport.proofArtifact().fields().get("rule.0.replacementNode.0.status")); + assertEquals("6", passReport.proofArtifact().fields().get("rule.0.replacementNode.0.candidateReplacementNodeId")); + assertEquals("GpuIrIntrinsicCall", passReport.proofArtifact().fields().get("rule.0.replacementNode.0.targetNodeKind")); + assertEquals("mad-fma", passReport.proofArtifact().fields().get("rule.0.replacementNode.0.targetOperation")); + assertEquals("3,4,5", passReport.proofArtifact().fields().get("rule.0.replacementNode.0.argumentNodeIds")); + assertEquals("arg0,arg1,arg2", passReport.proofArtifact().fields().get("rule.0.replacementNode.0.argumentRoles")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.replacementNode.0.replacementNodeBuilt")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.replacementNode.0.selectedIrReplacement")); + assertEquals("1", passReport.proofArtifact().fields().get("graphPatch.count")); + assertEquals("1", passReport.proofArtifact().fields().get("graphPatch.ready.count")); + assertEquals("0", passReport.proofArtifact().fields().get("graphPatch.blocked.count")); + assertEquals("none", passReport.proofArtifact().fields().get("graphPatch.firstBlocker")); + assertEquals("true", passReport.proofArtifact().fields().get("graphPatch.graphPatchPreflightImplemented")); + assertEquals("false", passReport.proofArtifact().fields().get("graphPatch.graphPatchApplied")); + assertEquals("false", passReport.proofArtifact().fields().get("graphPatch.graphRewriteImplemented")); + assertEquals("false", passReport.proofArtifact().fields().get("graphPatch.selectedIrReplacement")); + assertEquals("graph-patch-ready", passReport.proofArtifact().fields().get("rule.0.graphPatch.0.status")); + assertEquals("6", passReport.proofArtifact().fields().get("rule.0.graphPatch.0.replacementNodeId")); + assertEquals("1", passReport.proofArtifact().fields().get("rule.0.graphPatch.0.replacedNodeIds")); + assertEquals("1,2", passReport.proofArtifact().fields().get("rule.0.graphPatch.0.removedNodeIds")); + assertEquals("3,4,5", passReport.proofArtifact().fields().get("rule.0.graphPatch.0.retainedInputNodeIds")); + assertEquals("6", passReport.proofArtifact().fields().get("rule.0.graphPatch.0.insertedNodeIds")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.graphPatch.0.graphPatchApplied")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.graphPatch.0.selectedIrReplacement")); + assertEquals("1", passReport.proofArtifact().fields().get("transformedGraph.count")); + assertEquals("1", passReport.proofArtifact().fields().get("transformedGraph.ready.count")); + assertEquals("0", passReport.proofArtifact().fields().get("transformedGraph.blocked.count")); + assertEquals("none", passReport.proofArtifact().fields().get("transformedGraph.firstBlocker")); + assertEquals("true", passReport.proofArtifact().fields().get("transformedGraph.materializationPreflightImplemented")); + assertEquals("false", passReport.proofArtifact().fields().get("transformedGraph.transformedGraphBuilt")); + assertEquals("false", passReport.proofArtifact().fields().get("transformedGraph.transformedIrBuilt")); + assertEquals("false", passReport.proofArtifact().fields().get("transformedGraph.graphPatchApplied")); + assertEquals("false", passReport.proofArtifact().fields().get("transformedGraph.selectedIrReplacement")); + assertEquals("materialization-ready", passReport.proofArtifact().fields().get("rule.0.transformedGraph.0.status")); + assertEquals("not-built", passReport.proofArtifact().fields().get("rule.0.transformedGraph.0.transformedGraphIdentity")); + assertEquals(passReport.originalIrIdentity(), passReport.proofArtifact().fields().get("rule.0.transformedGraph.0.originalIrIdentity")); + String materializationKey = "madFma|kernel|1|mad-fma|replacement=6|replace=1|remove=1,2|retain=3,4,5|insert=6"; + assertEquals(materializationKey, passReport.proofArtifact().fields().get("rule.0.transformedGraph.0.materializationKey")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.transformedGraph.0.transformedGraphBuilt")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.transformedGraph.0.selectedIrReplacement")); + assertEquals("1", passReport.proofArtifact().fields().get("irArtifactEnvelope.count")); + assertEquals("1", passReport.proofArtifact().fields().get("irArtifactEnvelope.ready.count")); + assertEquals("0", passReport.proofArtifact().fields().get("irArtifactEnvelope.blocked.count")); + assertEquals("none", passReport.proofArtifact().fields().get("irArtifactEnvelope.firstBlocker")); + assertEquals("true", passReport.proofArtifact().fields().get("irArtifactEnvelope.artifactEnvelopePreflightImplemented")); + assertEquals("false", passReport.proofArtifact().fields().get("irArtifactEnvelope.artifactEnvelopeBuilt")); + assertEquals("false", passReport.proofArtifact().fields().get("irArtifactEnvelope.optimizedArtifactBuilt")); + assertEquals("false", passReport.proofArtifact().fields().get("irArtifactEnvelope.selectedIrReplacement")); + assertEquals("artifact-envelope-ready", passReport.proofArtifact().fields().get("rule.0.irArtifactEnvelope.0.status")); + assertEquals("not-built", passReport.proofArtifact().fields().get("rule.0.irArtifactEnvelope.0.optimizedArtifactIdentity")); + assertEquals(passReport.originalIrIdentity(), passReport.proofArtifact().fields().get("rule.0.irArtifactEnvelope.0.originalIrIdentity")); + assertEquals(materializationKey, passReport.proofArtifact().fields().get("rule.0.irArtifactEnvelope.0.materializationKey")); + assertEquals("runtime-equivalence-required", passReport.proofArtifact().fields().get("rule.0.irArtifactEnvelope.0.proofAnchor")); + assertEquals("original-ir", passReport.proofArtifact().fields().get("rule.0.irArtifactEnvelope.0.rollbackAnchor")); + assertEquals( + "madFma|kernel|1|mad-fma|original=" + passReport.originalIrIdentity() + + "|graph=not-built|materialization=" + materializationKey + + "|proof=runtime-equivalence-required|rollback=original-ir", + passReport.proofArtifact().fields().get("rule.0.irArtifactEnvelope.0.envelopeKey") + ); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.irArtifactEnvelope.0.artifactEnvelopeBuilt")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.irArtifactEnvelope.0.selectedIrReplacement")); assertEquals("1", passReport.proofArtifact().fields().get("rewriteSketch.count")); assertEquals("1", passReport.proofArtifact().fields().get("rewriteSketch.ready.count")); assertEquals("0", passReport.proofArtifact().fields().get("rewriteSketch.blocked.count")); @@ -970,6 +1103,93 @@ public String extensionId() { assertEquals("1", fields.get("replacementPlan.validation.invalid.count")); assertEquals("replacement-plan-root-missing", fields.get("replacementPlan.validation.firstBlocker")); assertEquals("1", fields.get("rewriteSketch.count")); + assertEquals("1", fields.get("rewriteVisitor.count")); + assertEquals("0", fields.get("rewriteVisitor.ready.count")); + assertEquals("1", fields.get("rewriteVisitor.blocked.count")); + assertEquals("replacement-plan-root-missing", fields.get("rewriteVisitor.firstBlocker")); + assertEquals("blocked", fields.get("rule.0.rewriteVisitor.0.status")); + assertEquals("replacement-plan-root-missing", fields.get("rule.0.rewriteVisitor.0.firstBlocker")); + assertEquals("false", fields.get("rule.0.rewriteVisitor.0.rootVisitable")); + assertEquals("5", fields.get("rule.0.rewriteVisitor.0.graphNodeMaxId")); + assertEquals("false", fields.get("rule.0.rewriteVisitor.0.visitorReady")); + assertEquals("false", fields.get("rule.0.rewriteVisitor.0.selectedIrReplacement")); + assertEquals("true", fields.get("rewriteVisitor.visitorImplemented")); + assertEquals("false", fields.get("rewriteVisitor.replacementBuilderImplemented")); + assertEquals("1", fields.get("replacementBlueprint.count")); + assertEquals("0", fields.get("replacementBlueprint.ready.count")); + assertEquals("1", fields.get("replacementBlueprint.blocked.count")); + assertEquals("replacement-plan-root-missing", fields.get("replacementBlueprint.firstBlocker")); + assertEquals("blocked", fields.get("rule.0.replacementBlueprint.0.status")); + assertEquals("replacement-plan-root-missing", fields.get("rule.0.replacementBlueprint.0.firstBlocker")); + assertEquals("false", fields.get("rule.0.replacementBlueprint.0.blueprintReady")); + assertEquals("false", fields.get("rule.0.replacementBlueprint.0.selectedIrReplacement")); + assertEquals("false", fields.get("replacementBlueprint.replacementBuilderImplemented")); + assertEquals("1", fields.get("rewriteTransaction.count")); + assertEquals("0", fields.get("rewriteTransaction.ready.count")); + assertEquals("1", fields.get("rewriteTransaction.blocked.count")); + assertEquals("replacement-plan-root-missing", fields.get("rewriteTransaction.firstBlocker")); + assertEquals("blocked", fields.get("rule.0.rewriteTransaction.0.status")); + assertEquals("replacement-plan-root-missing", fields.get("rule.0.rewriteTransaction.0.firstBlocker")); + assertEquals("false", fields.get("rule.0.rewriteTransaction.0.transactionReady")); + assertEquals("0", fields.get("rule.0.rewriteTransaction.0.plannedAddedNode.count")); + assertEquals("none", fields.get("rule.0.rewriteTransaction.0.plannedAddedNodeIds")); + assertEquals("false", fields.get("rewriteTransaction.nodeIdAllocatorImplemented")); + assertEquals("false", fields.get("rewriteTransaction.graphRewriteImplemented")); + assertEquals("false", fields.get("rewriteTransaction.selectedIrReplacement")); + assertEquals("1", fields.get("nodeIdAllocation.count")); + assertEquals("0", fields.get("nodeIdAllocation.ready.count")); + assertEquals("1", fields.get("nodeIdAllocation.blocked.count")); + assertEquals("replacement-plan-root-missing", fields.get("nodeIdAllocation.firstBlocker")); + assertEquals("blocked", fields.get("rule.0.nodeIdAllocation.0.status")); + assertEquals("replacement-plan-root-missing", fields.get("rule.0.nodeIdAllocation.0.firstBlocker")); + assertEquals("none", fields.get("rule.0.nodeIdAllocation.0.candidateNodeIds")); + assertEquals("false", fields.get("rule.0.nodeIdAllocation.0.allocationReady")); + assertEquals("false", fields.get("rule.0.nodeIdAllocation.0.nodeIdsReserved")); + assertEquals("false", fields.get("nodeIdAllocation.nodeIdAllocatorApplied")); + assertEquals("false", fields.get("nodeIdAllocation.graphRewriteImplemented")); + assertEquals("1", fields.get("replacementNode.count")); + assertEquals("0", fields.get("replacementNode.ready.count")); + assertEquals("1", fields.get("replacementNode.blocked.count")); + assertEquals("replacement-plan-root-missing", fields.get("replacementNode.firstBlocker")); + assertEquals("blocked", fields.get("rule.0.replacementNode.0.status")); + assertEquals("replacement-plan-root-missing", fields.get("rule.0.replacementNode.0.firstBlocker")); + assertEquals("none", fields.get("rule.0.replacementNode.0.candidateReplacementNodeId")); + assertEquals("false", fields.get("rule.0.replacementNode.0.replacementNodeReady")); + assertEquals("false", fields.get("rule.0.replacementNode.0.replacementNodeBuilt")); + assertEquals("false", fields.get("replacementNode.replacementBuilderImplemented")); + assertEquals("false", fields.get("replacementNode.graphRewriteImplemented")); + assertEquals("1", fields.get("graphPatch.count")); + assertEquals("0", fields.get("graphPatch.ready.count")); + assertEquals("1", fields.get("graphPatch.blocked.count")); + assertEquals("replacement-plan-root-missing", fields.get("graphPatch.firstBlocker")); + assertEquals("blocked", fields.get("rule.0.graphPatch.0.status")); + assertEquals("replacement-plan-root-missing", fields.get("rule.0.graphPatch.0.firstBlocker")); + assertEquals("none", fields.get("rule.0.graphPatch.0.insertedNodeIds")); + assertEquals("false", fields.get("rule.0.graphPatch.0.graphPatchReady")); + assertEquals("false", fields.get("rule.0.graphPatch.0.graphPatchApplied")); + assertEquals("false", fields.get("graphPatch.graphRewriteImplemented")); + assertEquals("1", fields.get("transformedGraph.count")); + assertEquals("0", fields.get("transformedGraph.ready.count")); + assertEquals("1", fields.get("transformedGraph.blocked.count")); + assertEquals("replacement-plan-root-missing", fields.get("transformedGraph.firstBlocker")); + assertEquals("blocked", fields.get("rule.0.transformedGraph.0.status")); + assertEquals("replacement-plan-root-missing", fields.get("rule.0.transformedGraph.0.firstBlocker")); + assertEquals("not-built", fields.get("rule.0.transformedGraph.0.transformedGraphIdentity")); + assertEquals("false", fields.get("rule.0.transformedGraph.0.materializationReady")); + assertEquals("false", fields.get("rule.0.transformedGraph.0.transformedGraphBuilt")); + assertEquals("false", fields.get("transformedGraph.graphPatchApplied")); + assertEquals("false", fields.get("transformedGraph.graphRewriteImplemented")); + assertEquals("1", fields.get("irArtifactEnvelope.count")); + assertEquals("0", fields.get("irArtifactEnvelope.ready.count")); + assertEquals("1", fields.get("irArtifactEnvelope.blocked.count")); + assertEquals("replacement-plan-root-missing", fields.get("irArtifactEnvelope.firstBlocker")); + assertEquals("blocked", fields.get("rule.0.irArtifactEnvelope.0.status")); + assertEquals("replacement-plan-root-missing", fields.get("rule.0.irArtifactEnvelope.0.firstBlocker")); + assertEquals("not-built", fields.get("rule.0.irArtifactEnvelope.0.optimizedArtifactIdentity")); + assertEquals("false", fields.get("rule.0.irArtifactEnvelope.0.artifactEnvelopeReady")); + assertEquals("false", fields.get("rule.0.irArtifactEnvelope.0.artifactEnvelopeBuilt")); + assertEquals("false", fields.get("irArtifactEnvelope.optimizedArtifactBuilt")); + assertEquals("false", fields.get("irArtifactEnvelope.selectedIrReplacement")); assertEquals("0", fields.get("rewriteSketch.ready.count")); assertEquals("1", fields.get("rewriteSketch.blocked.count")); assertEquals("replacement-plan-root-missing", fields.get("rewriteSketch.firstBlocker")); From 5a8b73616546875149c77a3f9304f97330d596c9 Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Mon, 13 Jul 2026 13:50:33 +0300 Subject: [PATCH 24/32] wip: Add read-only layer & update formatter/default behavior --- ROADMAP.md | 5 + docs/IR-Optimizer.md | 4 + docs/Runtime-Guide.md | 4 + ...dSourcePromotionWorkloadGateFormatter.java | 684 ++++++---------- ...PeepholeArtifactProofBindingPreflight.java | 337 ++++++++ ...leOptimizedArtifactSelectionPreflight.java | 236 ++++++ .../runtime/GpuRuntimeIrPeepholePass.java | 379 +++++---- .../GpuRuntimeOptimizerDriftArtifact.java | 740 +++++++----------- ...rcePromotionWorkloadGateFormatterTest.java | 90 +++ .../GpuRuntimeCompileArtifactDumperTest.java | 58 ++ .../GpuRuntimeIrOptimizerRegistryTest.java | 162 ++++ ...PeepholeArtifactBoundaryPreflightTest.java | 209 +++++ 12 files changed, 1853 insertions(+), 1055 deletions(-) create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeArtifactProofBindingPreflight.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeOptimizedArtifactSelectionPreflight.java create mode 100644 processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeArtifactBoundaryPreflightTest.java diff --git a/ROADMAP.md b/ROADMAP.md index a36ec40c..216f50bc 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -447,6 +447,8 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i The current typed preflight safely detects the initial `mad/fma` candidate shape without editing nodes. Next, add reusable typed-node traversal/replacement APIs that can prove complete-pattern replacement before editing anything. Partial matches must produce diagnostics only. Rule implementations should remain isolated (`mix`, `mad/fma`, `clamp`, `step`, `dot`) and report applied/skipped/blocked counts per rule so optimizer drift artifacts can detect regressions. `GpuRuntimeIrPeepholeReplacementPlan` now adds the first read-only typed replacement-planning contract. The `mad/fma` rule records root, covered, and input node ids plus complete/partial blocker state for detected `a*b+c` / `c+a*b` shapes, `clamp` records read-only complete plans for `min(max(x, lo), hi)` typed call shapes, `step` records read-only complete plans for simple `x < edge ? 0 : 1` / `x >= edge ? 1 : 0` ternary shapes, `dot` records read-only plans for fixed-width additive multiply trees such as `(a0*b0)+(a1*b1)`, and `mix` records read-only complete plans for `a + t * (b - a)` / `t * (b - a) + a`. `GpuRuntimeIrPeepholeTypedRewriteVisitor` now adds shared read-only traversal preflight, `GpuRuntimeIrPeepholeReplacementBlueprint` adds a target intrinsic-call blueprint, `GpuRuntimeIrPeepholeRewriteTransactionPreflight` adds a transaction-shape preflight, `GpuRuntimeIrPeepholeNodeIdAllocationPreflight` adds deterministic candidate id diagnostics above transaction-ready plans, `GpuRuntimeIrPeepholeReplacementNodePreflight` adds a replacement-node construction preview that pairs the candidate id with the intrinsic-call blueprint without building an IR node, `GpuRuntimeIrPeepholeGraphPatchPreflight` adds a read-only replace/remove/retain/insert graph-patch preview without applying it, and `GpuRuntimeIrPeepholeTransformedGraphPreflight` adds a materialization-key/original-IR-identity preview while keeping `transformedGraphIdentity=not-built`. Together they record deterministic visit order, blueprint target node kind/operation, argument ids/roles, replaced/removed/retained/inserted node ids, planned added-node count, candidate ids from current graph max id, candidate replacement-node id, materialization key, ready/blocked counts, first blockers, and disabled id reservation / allocator application / replacement-node build / graph-patch application / transformed-graph build / graph-rewrite / replacement-builder / transformed-IR / mutation / selected-IR flags. Rewrite-sketch overlap/conflict detection, `rewriteSelection.*` selection-readiness preflight, `rewriteProof.*` proof/runtime-equivalence/rollback preflight, and `rewriteReviewPackage.*` manual-review package preflight now make conflict-resolution, rewrite-builder, runtime-equivalence payload, rollback evidence, approval, mutation, selected-IR, and package-completeness blockers explicit before any rewrite engine exists. The pass still reports `rewrite-engine-not-implemented` and never mutates selected IR. All current peephole families now have diagnostic matchers, shared traversal evidence, replacement blueprints, transaction-shape evidence, node-id allocation previews, replacement-node construction previews, graph-patch previews, and transformed-graph materialization previews; the remaining work is real id reservation, actual replacement-node construction, graph rewrite mechanics, transformed IR artifact construction, and proof gates. `GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight` now adds the next read-only artifact-envelope boundary above transformed-graph materialization. It records original IR identity, absent transformed-graph / optimized-artifact identities, deterministic envelope key, proof anchor, rollback anchor, ready/blocked counts, first blocker, and disabled artifact build / optimized-artifact build / transformed-IR / graph-patch / graph-rewrite / mutation / selected-IR flags before any optimized artifact can exist. + `GpuRuntimeIrPeepholeArtifactProofBindingPreflight` now adds the proof-binding boundary above that envelope. It records proof/review blockers, runtime-equivalence payload completeness, rollback evidence, approval state, ready/blocked counts, and disabled proof / rollback / approval binding plus optimized-artifact / transformed-IR / mutation / selected-IR flags, so the future artifact handoff cannot be mistaken for accepted proof. + `GpuRuntimeIrPeepholeOptimizedArtifactSelectionPreflight` now adds the final read-only selection boundary above proof binding. It records proof-binding readiness, production-gate requirement/acceptance, mutation-policy allowance, ready/blocked counts, first blocker, and disabled selection / optimized-artifact-selected / mutation / selected-IR flags so the current selected runtime IR cannot change accidentally. - [x] Add Java-source fast-math policy metadata for runtime optimizer decisions. `@GPUOptimize(fastMath = false)` is now the production-safe default policy surface. Java-source frontend artifacts persist the explicit annotation source or `default-strict`, runtime optimization requests expose the resolved policy, and optimizer hooks can declare `requiresFastMath()` so the registry skips them without invocation unless the method explicitly opts in. This does not enable production mutation by itself. - [x] Preserve fast-math policy through the ASM frontend. @@ -458,6 +460,9 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i - [ ] Surface peephole telemetry in optimizer drift and promotion artifacts. `runtime-optimizer-drift.properties` now surfaces read-only replacement-plan evidence with aggregate complete/partial counts, structural validation counts, rewrite-visitor ready/blocked counts, replacement-blueprint ready/blocked counts, rewrite-transaction ready/blocked counts, node-id allocation ready/blocked counts, replacement-node preview ready/blocked counts, graph-patch preview ready/blocked counts, transformed-graph materialization ready/blocked counts, rewrite-sketch ready/blocked counts, overlap/conflict counters, aggregate and per-rule `rewriteSelection.*` status/first-blocker guardrails, aggregate and per-rule `rewriteProof.*` proof/runtime-equivalence/rollback blockers, aggregate and per-rule `rewriteReviewPackage.*` package-completeness blockers, and compact `optimizerRule.*` summaries from the existing `rule.N.*` proof fields, so CI can detect `mad/fma`, `clamp`, `step`, `dot`, and `mix` planning drift before any rewrite engine exists. Workload gates carry those visitor/blueprint/transaction/allocation/replacement-node/graph-patch/transformed-graph fields forward with the rest of the drift payload, and compact workload summaries, OpenCL validation Markdown/history, I3 summaries, and production-promotion explainability continue to treat them as evidence-only telemetry. Production promotion must remain disabled until these rule families have stable A1/A2 runtime evidence and backend/vendor confidence. Drift artifacts and workload gates now also carry `irArtifactEnvelope.*` aggregate/per-rule telemetry so CI can see the future optimized-artifact envelope boundary separately from transformed-graph materialization, while `artifactEnvelopeBuilt=false`, `optimizedArtifactBuilt=false`, `mutationAllowed=false`, and `selectedIrReplacement=false` stay pinned. + Drift artifacts and workload gates now also carry `artifactProofBinding.*` aggregate/per-rule telemetry, keeping proof binding, rollback binding, approval binding, optimized artifact construction, mutation, and selected-IR replacement explicitly disabled until accepted proof and complete review-package evidence exist. + Drift artifacts and workload gates now also carry `artifactSelection.*` aggregate/per-rule telemetry, making the final selection blocker explicit while `selectionApplied=false`, `optimizedArtifactSelected=false`, `mutationAllowed=false`, and `selectedIrReplacement=false` remain pinned. + Registry regression coverage now scans all peephole preflight proof fields across current typed matcher fixtures and fails if any production-enabling, binding, selection, mutation, or selected-IR replacement flag becomes `true` before the real rewrite/proof gate is implemented. #### I3.5 Vendor/device-specific optimization strategy diff --git a/docs/IR-Optimizer.md b/docs/IR-Optimizer.md index 042626f4..52f672e9 100644 --- a/docs/IR-Optimizer.md +++ b/docs/IR-Optimizer.md @@ -72,6 +72,10 @@ Runtime peephole/InstCombine proof is also kept read-only. `GpuRuntimeIrPeephole `GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight` now sits above transformed-graph materialization as the future optimized-artifact boundary. It records the original IR identity, absent transformed graph and optimized artifact identities, deterministic envelope key, proof anchor, rollback anchor, ready/blocked counts, first blocker, and disabled artifact build / optimized-artifact build / transformed-IR / graph-patch / graph-rewrite / mutation / selected-IR flags. The pass emits aggregate and `rule.N.irArtifactEnvelope.*` fields, `runtime-optimizer-drift.properties` aggregates them, and workload gates carry them forward as evidence-only telemetry; no optimized artifact is constructed or selected. +`GpuRuntimeIrPeepholeArtifactProofBindingPreflight` records the next fail-closed proof-binding boundary between that artifact envelope and proof/review evidence. It reports binding counts, proof and review blockers, runtime-equivalence payload completeness, rollback evidence, approval state, and disabled proof/rollback/approval binding flags. Binding remains blocked until proof is accepted, runtime-equivalence payloads are complete, rollback evidence is clean, approval is accepted, and the manual-review package is complete; even then this preflight does not build optimized IR or select it. + +`GpuRuntimeIrPeepholeOptimizedArtifactSelectionPreflight` records the final read-only selection boundary above proof binding. It reports whether proof binding is ready, whether production gate and mutation policy would still block selection, and keeps optimized-artifact selection, selection application, mutation, and selected-IR replacement disabled. This makes the future handoff to runtime IR selection auditable without changing the selected artifact. + The same evidence artifact now records `approvalTemplate.*` fields for each optimizer pass. Real proposal rewrites with distinct original/optimized identities and accepted proof metadata are marked `pending`; preview and no-change proposals are marked `not-applicable` with a first blocker such as `proposal-decision-not-proposed`. ## Vendor Optimizer SPI diff --git a/docs/Runtime-Guide.md b/docs/Runtime-Guide.md index 35abe4e1..3e2da1c2 100644 --- a/docs/Runtime-Guide.md +++ b/docs/Runtime-Guide.md @@ -239,6 +239,10 @@ Runtime peephole/InstCombine remains diagnostic-only. `GpuRuntimeIrPeepholeRepla `GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight` adds the next evidence-only boundary above transformed-graph materialization. It records the metadata, proof, and rollback anchors a future optimized IR artifact would require, including deterministic envelope keys and aggregate / `rule.N.irArtifactEnvelope.*` ready or blocked counts. Drift artifacts and workload gates now carry these fields, but `artifactEnvelopeBuilt`, `optimizedArtifactBuilt`, `transformedIrBuilt`, `mutationAllowed`, and `selectedIrReplacement` remain `false`. +`GpuRuntimeIrPeepholeArtifactProofBindingPreflight` adds a proof-binding preflight above that envelope. It records aggregate and `rule.N.artifactProofBinding.*` counts, first blockers, proof status, review-package status, runtime-equivalence payload completeness, rollback evidence, approval state, and disabled proof/rollback/approval binding flags. Drift artifacts and workload gates carry these fields as telemetry only; `proofBound`, `rollbackBound`, `approvalBound`, `optimizedArtifactBuilt`, `mutationAllowed`, and `selectedIrReplacement` remain `false`. + +`GpuRuntimeIrPeepholeOptimizedArtifactSelectionPreflight` adds the final selection preflight above proof binding. It records aggregate and `rule.N.artifactSelection.*` counts, first blockers, proof-binding readiness, production-gate requirement/acceptance, mutation-policy allowance, and disabled selection / optimized-artifact-selected / selected-IR replacement flags. Runtime drift and workload gates expose this as evidence only; runtime IR selection remains unchanged. + The same index records `familyBinding.*`. Binding remains `not-bound` unless exactly one optimizer family has passed runtime evidence whose comparison mode is explicitly family-specific (`optimizer-family::...`). Descriptor-versus-reconstructed-source evidence is pipeline validation and is deliberately rejected as optimizer-family proof. Run `./gradlew :processor:openClOptimizerFamilyPayloadFixtureTest` to materialize the CI contract fixture under `processor/build/reports/opencl/optimizer-family-payload-fixture/`. The fixture contains complete CSE and auto-vectorization payload trees plus `fixture-summary.properties`, including one structured raw case per family. It validates artifact layout and upload behavior, not production rewrite readiness. diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatter.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatter.java index 1797e39b..30d070d6 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatter.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatter.java @@ -17,6 +17,191 @@ */ public final class GpuBackendSourcePromotionWorkloadGateFormatter { + private static final String RUNTIME_OPTIMIZER_DRIFT_PREFIX = "runtimeOptimizerDrift."; + + private static final DriftProperty[] RUNTIME_OPTIMIZER_DRIFT_PROPERTIES = { + driftProperty("pass.count", "0", "unknown"), + driftProperty("pass.applied.count", "0", "unknown"), + driftProperty("pass.skipped.count", "0", "unknown"), + driftProperty("pass.rolledBack.count", "0", "unknown"), + driftProperty("pass.failed.count", "0", "unknown"), + driftProperty("proofArtifact.count", "0", "unknown"), + driftProperty("proofArtifact.accepted.count", "0", "unknown"), + driftProperty("proofArtifact.blocking.count", "0", "unknown"), + driftProperty("replacementPlan.complete.count", "0"), + driftProperty("replacementPlan.partial.count", "0"), + driftProperty("replacementPlan.firstBlocker", "none"), + driftProperty("replacementPlan.validation.count", "0"), + driftProperty("replacementPlan.validation.valid.count", "0"), + driftProperty("replacementPlan.validation.invalid.count", "0"), + driftProperty("replacementPlan.validation.firstBlocker", "none"), + driftProperty("rewriteVisitor.count", "0"), + driftProperty("rewriteVisitor.ready.count", "0"), + driftProperty("rewriteVisitor.blocked.count", "0"), + driftProperty("rewriteVisitor.firstBlocker", "none"), + driftProperty("rewriteVisitor.visitorImplemented", "true"), + driftProperty("rewriteVisitor.replacementBuilderImplemented", "false"), + driftProperty("rewriteVisitor.transformedIrBuilt", "false"), + driftProperty("rewriteVisitor.mutationAllowed", "false"), + driftProperty("rewriteVisitor.selectedIrReplacement", "false"), + driftProperty("replacementBlueprint.count", "0"), + driftProperty("replacementBlueprint.ready.count", "0"), + driftProperty("replacementBlueprint.blocked.count", "0"), + driftProperty("replacementBlueprint.firstBlocker", "none"), + driftProperty("replacementBlueprint.blueprintImplemented", "true"), + driftProperty("replacementBlueprint.replacementBuilderImplemented", "false"), + driftProperty("replacementBlueprint.transformedIrBuilt", "false"), + driftProperty("replacementBlueprint.mutationAllowed", "false"), + driftProperty("replacementBlueprint.selectedIrReplacement", "false"), + driftProperty("rewriteTransaction.count", "0"), + driftProperty("rewriteTransaction.ready.count", "0"), + driftProperty("rewriteTransaction.blocked.count", "0"), + driftProperty("rewriteTransaction.firstBlocker", "none"), + driftProperty("rewriteTransaction.transactionPreflightImplemented", "true"), + driftProperty("rewriteTransaction.nodeIdAllocatorImplemented", "false"), + driftProperty("rewriteTransaction.graphRewriteImplemented", "false"), + driftProperty("rewriteTransaction.transformedIrBuilt", "false"), + driftProperty("rewriteTransaction.mutationAllowed", "false"), + driftProperty("rewriteTransaction.selectedIrReplacement", "false"), + driftProperty("nodeIdAllocation.count", "0"), + driftProperty("nodeIdAllocation.ready.count", "0"), + driftProperty("nodeIdAllocation.blocked.count", "0"), + driftProperty("nodeIdAllocation.firstBlocker", "none"), + driftProperty("nodeIdAllocation.allocationPreflightImplemented", "true"), + driftProperty("nodeIdAllocation.nodeIdsReserved", "false"), + driftProperty("nodeIdAllocation.nodeIdAllocatorApplied", "false"), + driftProperty("nodeIdAllocation.graphRewriteImplemented", "false"), + driftProperty("nodeIdAllocation.transformedIrBuilt", "false"), + driftProperty("nodeIdAllocation.mutationAllowed", "false"), + driftProperty("nodeIdAllocation.selectedIrReplacement", "false"), + driftProperty("replacementNode.count", "0"), + driftProperty("replacementNode.ready.count", "0"), + driftProperty("replacementNode.blocked.count", "0"), + driftProperty("replacementNode.firstBlocker", "none"), + driftProperty("replacementNode.replacementNodePreflightImplemented", "true"), + driftProperty("replacementNode.replacementNodeBuilt", "false"), + driftProperty("replacementNode.replacementBuilderImplemented", "false"), + driftProperty("replacementNode.graphRewriteImplemented", "false"), + driftProperty("replacementNode.transformedIrBuilt", "false"), + driftProperty("replacementNode.mutationAllowed", "false"), + driftProperty("replacementNode.selectedIrReplacement", "false"), + driftProperty("graphPatch.count", "0"), + driftProperty("graphPatch.ready.count", "0"), + driftProperty("graphPatch.blocked.count", "0"), + driftProperty("graphPatch.firstBlocker", "none"), + driftProperty("graphPatch.graphPatchPreflightImplemented", "true"), + driftProperty("graphPatch.graphPatchApplied", "false"), + driftProperty("graphPatch.graphRewriteImplemented", "false"), + driftProperty("graphPatch.transformedIrBuilt", "false"), + driftProperty("graphPatch.mutationAllowed", "false"), + driftProperty("graphPatch.selectedIrReplacement", "false"), + driftProperty("transformedGraph.count", "0"), + driftProperty("transformedGraph.ready.count", "0"), + driftProperty("transformedGraph.blocked.count", "0"), + driftProperty("transformedGraph.firstBlocker", "none"), + driftProperty("transformedGraph.materializationPreflightImplemented", "true"), + driftProperty("transformedGraph.transformedGraphBuilt", "false"), + driftProperty("transformedGraph.transformedIrBuilt", "false"), + driftProperty("transformedGraph.graphPatchApplied", "false"), + driftProperty("transformedGraph.graphRewriteImplemented", "false"), + driftProperty("transformedGraph.mutationAllowed", "false"), + driftProperty("transformedGraph.selectedIrReplacement", "false"), + driftProperty("irArtifactEnvelope.count", "0"), + driftProperty("irArtifactEnvelope.ready.count", "0"), + driftProperty("irArtifactEnvelope.blocked.count", "0"), + driftProperty("irArtifactEnvelope.firstBlocker", "none"), + driftProperty("irArtifactEnvelope.artifactEnvelopePreflightImplemented", "true"), + driftProperty("irArtifactEnvelope.artifactEnvelopeBuilt", "false"), + driftProperty("irArtifactEnvelope.optimizedArtifactBuilt", "false"), + driftProperty("irArtifactEnvelope.transformedGraphBuilt", "false"), + driftProperty("irArtifactEnvelope.transformedIrBuilt", "false"), + driftProperty("irArtifactEnvelope.graphPatchApplied", "false"), + driftProperty("irArtifactEnvelope.graphRewriteImplemented", "false"), + driftProperty("irArtifactEnvelope.mutationAllowed", "false"), + driftProperty("irArtifactEnvelope.selectedIrReplacement", "false"), + driftProperty("artifactProofBinding.count", "0"), + driftProperty("artifactProofBinding.ready.count", "0"), + driftProperty("artifactProofBinding.blocked.count", "0"), + driftProperty("artifactProofBinding.firstBlocker", "none"), + driftProperty("artifactProofBinding.bindingPreflightImplemented", "true"), + driftProperty("artifactProofBinding.proofBound", "false"), + driftProperty("artifactProofBinding.rollbackBound", "false"), + driftProperty("artifactProofBinding.approvalBound", "false"), + driftProperty("artifactProofBinding.optimizedArtifactBuilt", "false"), + driftProperty("artifactProofBinding.transformedIrBuilt", "false"), + driftProperty("artifactProofBinding.mutationAllowed", "false"), + driftProperty("artifactProofBinding.selectedIrReplacement", "false"), + driftProperty("artifactSelection.count", "0"), + driftProperty("artifactSelection.ready.count", "0"), + driftProperty("artifactSelection.blocked.count", "0"), + driftProperty("artifactSelection.firstBlocker", "none"), + driftProperty("artifactSelection.selectionPreflightImplemented", "true"), + driftProperty("artifactSelection.productionGateRequired", "false"), + driftProperty("artifactSelection.productionGateAccepted", "false"), + driftProperty("artifactSelection.mutationPolicyAllowed", "false"), + driftProperty("artifactSelection.selectionApplied", "false"), + driftProperty("artifactSelection.optimizedArtifactSelected", "false"), + driftProperty("artifactSelection.optimizedArtifactBuilt", "false"), + driftProperty("artifactSelection.transformedIrBuilt", "false"), + driftProperty("artifactSelection.mutationAllowed", "false"), + driftProperty("artifactSelection.selectedIrReplacement", "false"), + driftProperty("rewriteSketch.count", "0"), + driftProperty("rewriteSketch.ready.count", "0"), + driftProperty("rewriteSketch.blocked.count", "0"), + driftProperty("rewriteSketch.firstBlocker", "none"), + driftProperty("rewriteSketch.rewriteBuilderImplemented", "false"), + driftProperty("rewriteSketch.mutationAllowed", "false"), + driftProperty("rewriteSketch.selectedIrReplacement", "false"), + driftProperty("rewriteSketch.conflict.count", "0"), + driftProperty("rewriteSketch.conflict.firstBlocker", "none"), + driftProperty("rewriteSketch.conflict.conflictResolutionImplemented", "false"), + driftProperty("rewriteSketch.conflict.selectionApplied", "false"), + driftProperty("rewriteSketch.conflict.mutationAllowed", "false"), + driftProperty("rewriteSketch.conflict.selectedIrReplacement", "false"), + driftProperty("rewriteSelection.sketch.count", "0"), + driftProperty("rewriteSelection.sketch.ready.count", "0"), + driftProperty("rewriteSelection.sketch.blocked.count", "0"), + driftProperty("rewriteSelection.conflict.count", "0"), + driftProperty("rewriteSelection.status", "not-required"), + driftProperty("rewriteSelection.firstBlocker", "no-rewrite-sketches"), + driftProperty("rewriteSelection.rewriteBuilderImplemented", "false"), + driftProperty("rewriteSelection.conflictResolutionImplemented", "false"), + driftProperty("rewriteSelection.runtimeEquivalenceRequired", "false"), + driftProperty("rewriteSelection.runtimeEquivalenceProven", "false"), + driftProperty("rewriteSelection.approvalRequired", "false"), + driftProperty("rewriteSelection.approvalAccepted", "false"), + driftProperty("rewriteSelection.mutationAllowed", "false"), + driftProperty("rewriteSelection.selectionApplied", "false"), + driftProperty("rewriteSelection.selectedIrReplacement", "false"), + driftProperty("rewriteProof.status", "not-required"), + driftProperty("rewriteProof.firstBlocker", "no-proof-candidates"), + driftProperty("rewriteProof.proofAccepted", "false"), + driftProperty("rewriteProof.runtimeEquivalencePayload.present", "false"), + driftProperty("rewriteProof.runtimeEquivalencePayload.complete", "false"), + driftProperty("rewriteProof.rollbackEvidence.present", "false"), + driftProperty("rewriteProof.rollbackClean", "false"), + driftProperty("rewriteProof.approvalAccepted", "false"), + driftProperty("rewriteProof.mutationAllowed", "false"), + driftProperty("rewriteProof.selectedIrReplacement", "false"), + driftProperty("rewriteReviewPackage.status", "not-required"), + driftProperty("rewriteReviewPackage.firstBlocker", "no-review-candidates"), + driftProperty("rewriteReviewPackage.required", "false"), + driftProperty("rewriteReviewPackage.complete", "false"), + driftProperty("rewriteReviewPackage.conflict.count", "0"), + driftProperty("rewriteReviewPackage.proofAccepted", "false"), + driftProperty("rewriteReviewPackage.runtimeEquivalencePayload.present", "false"), + driftProperty("rewriteReviewPackage.runtimeEquivalencePayload.complete", "false"), + driftProperty("rewriteReviewPackage.rollbackEvidence.present", "false"), + driftProperty("rewriteReviewPackage.rollbackClean", "false"), + driftProperty("rewriteReviewPackage.approvalAccepted", "false"), + driftProperty("rewriteReviewPackage.mutationAllowed", "false"), + driftProperty("rewriteReviewPackage.selectionApplied", "false"), + driftProperty("rewriteReviewPackage.selectedIrReplacement", "false"), + driftProperty("rewriteReviewPackage.manualReviewOnly", "true"), + driftProperty("optimizerRule.count", "0"), + driftProperty("optimizerRule.summary", "none"), + }; + private GpuBackendSourcePromotionWorkloadGateFormatter() { } @@ -446,160 +631,7 @@ private static void copyI3ReadinessSummaryProperty(Properties source, Properties private static void copyRuntimeOptimizerDriftProperties(Properties source, Properties target) { if (source == null || source.isEmpty()) { target.setProperty("runtimeOptimizerDrift.status", "not-recorded"); - target.setProperty("runtimeOptimizerDrift.pass.count", "0"); - target.setProperty("runtimeOptimizerDrift.pass.applied.count", "0"); - target.setProperty("runtimeOptimizerDrift.pass.skipped.count", "0"); - target.setProperty("runtimeOptimizerDrift.pass.rolledBack.count", "0"); - target.setProperty("runtimeOptimizerDrift.pass.failed.count", "0"); - target.setProperty("runtimeOptimizerDrift.proofArtifact.count", "0"); - target.setProperty("runtimeOptimizerDrift.proofArtifact.accepted.count", "0"); - target.setProperty("runtimeOptimizerDrift.proofArtifact.blocking.count", "0"); - target.setProperty("runtimeOptimizerDrift.replacementPlan.complete.count", "0"); - target.setProperty("runtimeOptimizerDrift.replacementPlan.partial.count", "0"); - target.setProperty("runtimeOptimizerDrift.replacementPlan.firstBlocker", "none"); - target.setProperty("runtimeOptimizerDrift.replacementPlan.validation.count", "0"); - target.setProperty("runtimeOptimizerDrift.replacementPlan.validation.valid.count", "0"); - target.setProperty("runtimeOptimizerDrift.replacementPlan.validation.invalid.count", "0"); - target.setProperty("runtimeOptimizerDrift.replacementPlan.validation.firstBlocker", "none"); - target.setProperty("runtimeOptimizerDrift.rewriteVisitor.count", "0"); - target.setProperty("runtimeOptimizerDrift.rewriteVisitor.ready.count", "0"); - target.setProperty("runtimeOptimizerDrift.rewriteVisitor.blocked.count", "0"); - target.setProperty("runtimeOptimizerDrift.rewriteVisitor.firstBlocker", "none"); - target.setProperty("runtimeOptimizerDrift.rewriteVisitor.visitorImplemented", "true"); - target.setProperty("runtimeOptimizerDrift.rewriteVisitor.replacementBuilderImplemented", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteVisitor.transformedIrBuilt", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteVisitor.mutationAllowed", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteVisitor.selectedIrReplacement", "false"); - target.setProperty("runtimeOptimizerDrift.replacementBlueprint.count", "0"); - target.setProperty("runtimeOptimizerDrift.replacementBlueprint.ready.count", "0"); - target.setProperty("runtimeOptimizerDrift.replacementBlueprint.blocked.count", "0"); - target.setProperty("runtimeOptimizerDrift.replacementBlueprint.firstBlocker", "none"); - target.setProperty("runtimeOptimizerDrift.replacementBlueprint.blueprintImplemented", "true"); - target.setProperty("runtimeOptimizerDrift.replacementBlueprint.replacementBuilderImplemented", "false"); - target.setProperty("runtimeOptimizerDrift.replacementBlueprint.transformedIrBuilt", "false"); - target.setProperty("runtimeOptimizerDrift.replacementBlueprint.mutationAllowed", "false"); - target.setProperty("runtimeOptimizerDrift.replacementBlueprint.selectedIrReplacement", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteTransaction.count", "0"); - target.setProperty("runtimeOptimizerDrift.rewriteTransaction.ready.count", "0"); - target.setProperty("runtimeOptimizerDrift.rewriteTransaction.blocked.count", "0"); - target.setProperty("runtimeOptimizerDrift.rewriteTransaction.firstBlocker", "none"); - target.setProperty("runtimeOptimizerDrift.rewriteTransaction.transactionPreflightImplemented", "true"); - target.setProperty("runtimeOptimizerDrift.rewriteTransaction.nodeIdAllocatorImplemented", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteTransaction.graphRewriteImplemented", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteTransaction.transformedIrBuilt", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteTransaction.mutationAllowed", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteTransaction.selectedIrReplacement", "false"); - target.setProperty("runtimeOptimizerDrift.nodeIdAllocation.count", "0"); - target.setProperty("runtimeOptimizerDrift.nodeIdAllocation.ready.count", "0"); - target.setProperty("runtimeOptimizerDrift.nodeIdAllocation.blocked.count", "0"); - target.setProperty("runtimeOptimizerDrift.nodeIdAllocation.firstBlocker", "none"); - target.setProperty("runtimeOptimizerDrift.nodeIdAllocation.allocationPreflightImplemented", "true"); - target.setProperty("runtimeOptimizerDrift.nodeIdAllocation.nodeIdsReserved", "false"); - target.setProperty("runtimeOptimizerDrift.nodeIdAllocation.nodeIdAllocatorApplied", "false"); - target.setProperty("runtimeOptimizerDrift.nodeIdAllocation.graphRewriteImplemented", "false"); - target.setProperty("runtimeOptimizerDrift.nodeIdAllocation.transformedIrBuilt", "false"); - target.setProperty("runtimeOptimizerDrift.nodeIdAllocation.mutationAllowed", "false"); - target.setProperty("runtimeOptimizerDrift.nodeIdAllocation.selectedIrReplacement", "false"); - target.setProperty("runtimeOptimizerDrift.replacementNode.count", "0"); - target.setProperty("runtimeOptimizerDrift.replacementNode.ready.count", "0"); - target.setProperty("runtimeOptimizerDrift.replacementNode.blocked.count", "0"); - target.setProperty("runtimeOptimizerDrift.replacementNode.firstBlocker", "none"); - target.setProperty("runtimeOptimizerDrift.replacementNode.replacementNodePreflightImplemented", "true"); - target.setProperty("runtimeOptimizerDrift.replacementNode.replacementNodeBuilt", "false"); - target.setProperty("runtimeOptimizerDrift.replacementNode.replacementBuilderImplemented", "false"); - target.setProperty("runtimeOptimizerDrift.replacementNode.graphRewriteImplemented", "false"); - target.setProperty("runtimeOptimizerDrift.replacementNode.transformedIrBuilt", "false"); - target.setProperty("runtimeOptimizerDrift.replacementNode.mutationAllowed", "false"); - target.setProperty("runtimeOptimizerDrift.replacementNode.selectedIrReplacement", "false"); - target.setProperty("runtimeOptimizerDrift.graphPatch.count", "0"); - target.setProperty("runtimeOptimizerDrift.graphPatch.ready.count", "0"); - target.setProperty("runtimeOptimizerDrift.graphPatch.blocked.count", "0"); - target.setProperty("runtimeOptimizerDrift.graphPatch.firstBlocker", "none"); - target.setProperty("runtimeOptimizerDrift.graphPatch.graphPatchPreflightImplemented", "true"); - target.setProperty("runtimeOptimizerDrift.graphPatch.graphPatchApplied", "false"); - target.setProperty("runtimeOptimizerDrift.graphPatch.graphRewriteImplemented", "false"); - target.setProperty("runtimeOptimizerDrift.graphPatch.transformedIrBuilt", "false"); - target.setProperty("runtimeOptimizerDrift.graphPatch.mutationAllowed", "false"); - target.setProperty("runtimeOptimizerDrift.graphPatch.selectedIrReplacement", "false"); - target.setProperty("runtimeOptimizerDrift.transformedGraph.count", "0"); - target.setProperty("runtimeOptimizerDrift.transformedGraph.ready.count", "0"); - target.setProperty("runtimeOptimizerDrift.transformedGraph.blocked.count", "0"); - target.setProperty("runtimeOptimizerDrift.transformedGraph.firstBlocker", "none"); - target.setProperty("runtimeOptimizerDrift.transformedGraph.materializationPreflightImplemented", "true"); - target.setProperty("runtimeOptimizerDrift.transformedGraph.transformedGraphBuilt", "false"); - target.setProperty("runtimeOptimizerDrift.transformedGraph.transformedIrBuilt", "false"); - target.setProperty("runtimeOptimizerDrift.transformedGraph.graphPatchApplied", "false"); - target.setProperty("runtimeOptimizerDrift.transformedGraph.graphRewriteImplemented", "false"); - target.setProperty("runtimeOptimizerDrift.transformedGraph.mutationAllowed", "false"); - target.setProperty("runtimeOptimizerDrift.transformedGraph.selectedIrReplacement", "false"); - target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.count", "0"); - target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.ready.count", "0"); - target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.blocked.count", "0"); - target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.firstBlocker", "none"); - target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.artifactEnvelopePreflightImplemented", "true"); - target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.artifactEnvelopeBuilt", "false"); - target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.optimizedArtifactBuilt", "false"); - target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.transformedGraphBuilt", "false"); - target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.transformedIrBuilt", "false"); - target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.graphPatchApplied", "false"); - target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.graphRewriteImplemented", "false"); - target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.mutationAllowed", "false"); - target.setProperty("runtimeOptimizerDrift.irArtifactEnvelope.selectedIrReplacement", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteSketch.count", "0"); - target.setProperty("runtimeOptimizerDrift.rewriteSketch.ready.count", "0"); - target.setProperty("runtimeOptimizerDrift.rewriteSketch.blocked.count", "0"); - target.setProperty("runtimeOptimizerDrift.rewriteSketch.firstBlocker", "none"); - target.setProperty("runtimeOptimizerDrift.rewriteSketch.rewriteBuilderImplemented", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteSketch.mutationAllowed", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteSketch.selectedIrReplacement", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteSketch.conflict.count", "0"); - target.setProperty("runtimeOptimizerDrift.rewriteSketch.conflict.firstBlocker", "none"); - target.setProperty("runtimeOptimizerDrift.rewriteSketch.conflict.conflictResolutionImplemented", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteSketch.conflict.selectionApplied", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteSketch.conflict.mutationAllowed", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteSketch.conflict.selectedIrReplacement", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteSelection.sketch.count", "0"); - target.setProperty("runtimeOptimizerDrift.rewriteSelection.sketch.ready.count", "0"); - target.setProperty("runtimeOptimizerDrift.rewriteSelection.sketch.blocked.count", "0"); - target.setProperty("runtimeOptimizerDrift.rewriteSelection.conflict.count", "0"); - target.setProperty("runtimeOptimizerDrift.rewriteSelection.status", "not-required"); - target.setProperty("runtimeOptimizerDrift.rewriteSelection.firstBlocker", "no-rewrite-sketches"); - target.setProperty("runtimeOptimizerDrift.rewriteSelection.rewriteBuilderImplemented", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteSelection.conflictResolutionImplemented", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteSelection.runtimeEquivalenceRequired", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteSelection.runtimeEquivalenceProven", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteSelection.approvalRequired", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteSelection.approvalAccepted", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteSelection.mutationAllowed", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteSelection.selectionApplied", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteSelection.selectedIrReplacement", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteProof.status", "not-required"); - target.setProperty("runtimeOptimizerDrift.rewriteProof.firstBlocker", "no-proof-candidates"); - target.setProperty("runtimeOptimizerDrift.rewriteProof.proofAccepted", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteProof.runtimeEquivalencePayload.present", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteProof.runtimeEquivalencePayload.complete", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteProof.rollbackEvidence.present", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteProof.rollbackClean", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteProof.approvalAccepted", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteProof.mutationAllowed", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteProof.selectedIrReplacement", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.status", "not-required"); - target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.firstBlocker", "no-review-candidates"); - target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.required", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.complete", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.conflict.count", "0"); - target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.proofAccepted", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.runtimeEquivalencePayload.present", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.runtimeEquivalencePayload.complete", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.rollbackEvidence.present", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.rollbackClean", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.approvalAccepted", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.mutationAllowed", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.selectionApplied", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.selectedIrReplacement", "false"); - target.setProperty("runtimeOptimizerDrift.rewriteReviewPackage.manualReviewOnly", "true"); - target.setProperty("runtimeOptimizerDrift.optimizerRule.count", "0"); - target.setProperty("runtimeOptimizerDrift.optimizerRule.summary", "none"); + copyDefaultRuntimeOptimizerDriftProperties(target); target.setProperty("runtimeOptimizerDrift.fallbackDecision", target.getProperty("runtimeIrHandoff.fallbackDecision", "none")); target.setProperty("runtimeOptimizerDrift.selectedRuntimeIrStage", target.getProperty("runtimeIrHandoff.selectedStage", "original")); target.setProperty("runtimeOptimizerDrift.selectedRuntimeIrIdentity", target.getProperty("runtimeIrHandoff.selected.identity", "unknown")); @@ -612,160 +644,7 @@ private static void copyRuntimeOptimizerDriftProperties(Properties source, Prope target.setProperty("runtimeOptimizerDrift.productionProfileRequested", target.getProperty("runtimeProductionMutationSafety.productionProfileRequested", "unknown")); return; } - copyRuntimeOptimizerDriftProperty(source, target, "pass.count"); - copyRuntimeOptimizerDriftProperty(source, target, "pass.applied.count"); - copyRuntimeOptimizerDriftProperty(source, target, "pass.skipped.count"); - copyRuntimeOptimizerDriftProperty(source, target, "pass.rolledBack.count"); - copyRuntimeOptimizerDriftProperty(source, target, "pass.failed.count"); - copyRuntimeOptimizerDriftProperty(source, target, "proofArtifact.count"); - copyRuntimeOptimizerDriftProperty(source, target, "proofArtifact.accepted.count"); - copyRuntimeOptimizerDriftProperty(source, target, "proofArtifact.blocking.count"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementPlan.complete.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementPlan.partial.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementPlan.firstBlocker", "none"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementPlan.validation.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementPlan.validation.valid.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementPlan.validation.invalid.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementPlan.validation.firstBlocker", "none"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteVisitor.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteVisitor.ready.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteVisitor.blocked.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteVisitor.firstBlocker", "none"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteVisitor.visitorImplemented", "true"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteVisitor.replacementBuilderImplemented", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteVisitor.transformedIrBuilt", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteVisitor.mutationAllowed", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteVisitor.selectedIrReplacement", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementBlueprint.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementBlueprint.ready.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementBlueprint.blocked.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementBlueprint.firstBlocker", "none"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementBlueprint.blueprintImplemented", "true"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementBlueprint.replacementBuilderImplemented", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementBlueprint.transformedIrBuilt", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementBlueprint.mutationAllowed", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementBlueprint.selectedIrReplacement", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteTransaction.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteTransaction.ready.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteTransaction.blocked.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteTransaction.firstBlocker", "none"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteTransaction.transactionPreflightImplemented", "true"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteTransaction.nodeIdAllocatorImplemented", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteTransaction.graphRewriteImplemented", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteTransaction.transformedIrBuilt", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteTransaction.mutationAllowed", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteTransaction.selectedIrReplacement", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "nodeIdAllocation.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "nodeIdAllocation.ready.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "nodeIdAllocation.blocked.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "nodeIdAllocation.firstBlocker", "none"); - copyRuntimeOptimizerDriftProperty(source, target, "nodeIdAllocation.allocationPreflightImplemented", "true"); - copyRuntimeOptimizerDriftProperty(source, target, "nodeIdAllocation.nodeIdsReserved", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "nodeIdAllocation.nodeIdAllocatorApplied", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "nodeIdAllocation.graphRewriteImplemented", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "nodeIdAllocation.transformedIrBuilt", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "nodeIdAllocation.mutationAllowed", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "nodeIdAllocation.selectedIrReplacement", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementNode.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementNode.ready.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementNode.blocked.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementNode.firstBlocker", "none"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementNode.replacementNodePreflightImplemented", "true"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementNode.replacementNodeBuilt", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementNode.replacementBuilderImplemented", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementNode.graphRewriteImplemented", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementNode.transformedIrBuilt", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementNode.mutationAllowed", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "replacementNode.selectedIrReplacement", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "graphPatch.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "graphPatch.ready.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "graphPatch.blocked.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "graphPatch.firstBlocker", "none"); - copyRuntimeOptimizerDriftProperty(source, target, "graphPatch.graphPatchPreflightImplemented", "true"); - copyRuntimeOptimizerDriftProperty(source, target, "graphPatch.graphPatchApplied", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "graphPatch.graphRewriteImplemented", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "graphPatch.transformedIrBuilt", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "graphPatch.mutationAllowed", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "graphPatch.selectedIrReplacement", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "transformedGraph.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "transformedGraph.ready.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "transformedGraph.blocked.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "transformedGraph.firstBlocker", "none"); - copyRuntimeOptimizerDriftProperty(source, target, "transformedGraph.materializationPreflightImplemented", "true"); - copyRuntimeOptimizerDriftProperty(source, target, "transformedGraph.transformedGraphBuilt", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "transformedGraph.transformedIrBuilt", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "transformedGraph.graphPatchApplied", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "transformedGraph.graphRewriteImplemented", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "transformedGraph.mutationAllowed", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "transformedGraph.selectedIrReplacement", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.ready.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.blocked.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.firstBlocker", "none"); - copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.artifactEnvelopePreflightImplemented", "true"); - copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.artifactEnvelopeBuilt", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.optimizedArtifactBuilt", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.transformedGraphBuilt", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.transformedIrBuilt", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.graphPatchApplied", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.graphRewriteImplemented", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.mutationAllowed", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "irArtifactEnvelope.selectedIrReplacement", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.ready.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.blocked.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.firstBlocker", "none"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.rewriteBuilderImplemented", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.mutationAllowed", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.selectedIrReplacement", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.conflict.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.conflict.firstBlocker", "none"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.conflict.conflictResolutionImplemented", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.conflict.selectionApplied", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.conflict.mutationAllowed", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSketch.conflict.selectedIrReplacement", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.sketch.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.sketch.ready.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.sketch.blocked.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.conflict.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.status", "not-required"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.firstBlocker", "no-rewrite-sketches"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.rewriteBuilderImplemented", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.conflictResolutionImplemented", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.runtimeEquivalenceRequired", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.runtimeEquivalenceProven", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.approvalRequired", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.approvalAccepted", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.mutationAllowed", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.selectionApplied", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteSelection.selectedIrReplacement", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteProof.status", "not-required"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteProof.firstBlocker", "no-proof-candidates"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteProof.proofAccepted", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteProof.runtimeEquivalencePayload.present", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteProof.runtimeEquivalencePayload.complete", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteProof.rollbackEvidence.present", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteProof.rollbackClean", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteProof.approvalAccepted", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteProof.mutationAllowed", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteProof.selectedIrReplacement", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.status", "not-required"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.firstBlocker", "no-review-candidates"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.required", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.complete", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.conflict.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.proofAccepted", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.runtimeEquivalencePayload.present", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.runtimeEquivalencePayload.complete", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.rollbackEvidence.present", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.rollbackClean", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.approvalAccepted", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.mutationAllowed", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.selectionApplied", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.selectedIrReplacement", "false"); - copyRuntimeOptimizerDriftProperty(source, target, "rewriteReviewPackage.manualReviewOnly", "true"); - copyRuntimeOptimizerDriftProperty(source, target, "optimizerRule.count", "0"); - copyRuntimeOptimizerDriftProperty(source, target, "optimizerRule.summary", "none"); + copyRuntimeOptimizerDriftPropertiesFromSource(source, target); copyIndexedPropertyGroup(source, target, "runtimeOptimizerDrift.optimizerRule", "optimizerRule"); copyRuntimeOptimizerDriftProperty(source, target, "optimizerFamily.count"); copyRuntimeOptimizerDriftProperty(source, target, "optimizerFamily.promotionReady.count"); @@ -783,8 +662,20 @@ private static void copyRuntimeOptimizerDriftProperties(Properties source, Prope target.setProperty("runtimeOptimizerDrift.status", "recorded"); } + private static void copyDefaultRuntimeOptimizerDriftProperties(Properties target) { + for (DriftProperty property : RUNTIME_OPTIMIZER_DRIFT_PROPERTIES) { + target.setProperty(RUNTIME_OPTIMIZER_DRIFT_PREFIX + property.key(), property.emptySourceDefault()); + } + } + + private static void copyRuntimeOptimizerDriftPropertiesFromSource(Properties source, Properties target) { + for (DriftProperty property : RUNTIME_OPTIMIZER_DRIFT_PROPERTIES) { + copyRuntimeOptimizerDriftProperty(source, target, property.key(), property.recordedSourceDefault()); + } + } + private static void copyRuntimeOptimizerDriftProperty(Properties source, Properties target, String key) { - target.setProperty("runtimeOptimizerDrift." + key, source.getProperty(key, "unknown")); + target.setProperty(RUNTIME_OPTIMIZER_DRIFT_PREFIX + key, source.getProperty(key, "unknown")); } private static void copyRuntimeOptimizerDriftProperty( @@ -793,7 +684,18 @@ private static void copyRuntimeOptimizerDriftProperty( String key, String defaultValue ) { - target.setProperty("runtimeOptimizerDrift." + key, source.getProperty(key, defaultValue)); + target.setProperty(RUNTIME_OPTIMIZER_DRIFT_PREFIX + key, source.getProperty(key, defaultValue)); + } + + private static DriftProperty driftProperty(String key, String defaultValue) { + return driftProperty(key, defaultValue, defaultValue); + } + + private static DriftProperty driftProperty(String key, String emptySourceDefault, String recordedSourceDefault) { + return new DriftProperty(key, emptySourceDefault, recordedSourceDefault); + } + + private record DriftProperty(String key, String emptySourceDefault, String recordedSourceDefault) { } private static void copyIndexedPropertyGroup( @@ -1086,160 +988,7 @@ private static void appendI3ReadinessSummary(StringBuilder builder, String prefi private static void appendRuntimeOptimizerDrift(StringBuilder builder, String prefix, Properties entry) { builder.append(prefix).append("runtimeOptimizerDrift.status=").append(entry.getProperty("runtimeOptimizerDrift.status", "not-recorded")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.pass.count=").append(entry.getProperty("runtimeOptimizerDrift.pass.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.pass.applied.count=").append(entry.getProperty("runtimeOptimizerDrift.pass.applied.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.pass.skipped.count=").append(entry.getProperty("runtimeOptimizerDrift.pass.skipped.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.pass.rolledBack.count=").append(entry.getProperty("runtimeOptimizerDrift.pass.rolledBack.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.pass.failed.count=").append(entry.getProperty("runtimeOptimizerDrift.pass.failed.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.proofArtifact.count=").append(entry.getProperty("runtimeOptimizerDrift.proofArtifact.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.proofArtifact.accepted.count=").append(entry.getProperty("runtimeOptimizerDrift.proofArtifact.accepted.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.proofArtifact.blocking.count=").append(entry.getProperty("runtimeOptimizerDrift.proofArtifact.blocking.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementPlan.complete.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementPlan.complete.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementPlan.partial.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementPlan.partial.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementPlan.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.replacementPlan.firstBlocker", "none")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementPlan.validation.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementPlan.validation.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementPlan.validation.valid.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementPlan.validation.valid.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementPlan.validation.invalid.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementPlan.validation.invalid.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementPlan.validation.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.replacementPlan.validation.firstBlocker", "none")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteVisitor.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteVisitor.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteVisitor.ready.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteVisitor.ready.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteVisitor.blocked.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteVisitor.blocked.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteVisitor.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.rewriteVisitor.firstBlocker", "none")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteVisitor.visitorImplemented=").append(entry.getProperty("runtimeOptimizerDrift.rewriteVisitor.visitorImplemented", "true")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteVisitor.replacementBuilderImplemented=").append(entry.getProperty("runtimeOptimizerDrift.rewriteVisitor.replacementBuilderImplemented", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteVisitor.transformedIrBuilt=").append(entry.getProperty("runtimeOptimizerDrift.rewriteVisitor.transformedIrBuilt", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteVisitor.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.rewriteVisitor.mutationAllowed", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteVisitor.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.rewriteVisitor.selectedIrReplacement", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementBlueprint.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementBlueprint.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementBlueprint.ready.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementBlueprint.ready.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementBlueprint.blocked.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementBlueprint.blocked.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementBlueprint.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.replacementBlueprint.firstBlocker", "none")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementBlueprint.blueprintImplemented=").append(entry.getProperty("runtimeOptimizerDrift.replacementBlueprint.blueprintImplemented", "true")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementBlueprint.replacementBuilderImplemented=").append(entry.getProperty("runtimeOptimizerDrift.replacementBlueprint.replacementBuilderImplemented", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementBlueprint.transformedIrBuilt=").append(entry.getProperty("runtimeOptimizerDrift.replacementBlueprint.transformedIrBuilt", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementBlueprint.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.replacementBlueprint.mutationAllowed", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementBlueprint.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.replacementBlueprint.selectedIrReplacement", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteTransaction.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteTransaction.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteTransaction.ready.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteTransaction.ready.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteTransaction.blocked.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteTransaction.blocked.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteTransaction.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.rewriteTransaction.firstBlocker", "none")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteTransaction.transactionPreflightImplemented=").append(entry.getProperty("runtimeOptimizerDrift.rewriteTransaction.transactionPreflightImplemented", "true")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteTransaction.nodeIdAllocatorImplemented=").append(entry.getProperty("runtimeOptimizerDrift.rewriteTransaction.nodeIdAllocatorImplemented", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteTransaction.graphRewriteImplemented=").append(entry.getProperty("runtimeOptimizerDrift.rewriteTransaction.graphRewriteImplemented", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteTransaction.transformedIrBuilt=").append(entry.getProperty("runtimeOptimizerDrift.rewriteTransaction.transformedIrBuilt", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteTransaction.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.rewriteTransaction.mutationAllowed", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteTransaction.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.rewriteTransaction.selectedIrReplacement", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.nodeIdAllocation.count=").append(entry.getProperty("runtimeOptimizerDrift.nodeIdAllocation.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.nodeIdAllocation.ready.count=").append(entry.getProperty("runtimeOptimizerDrift.nodeIdAllocation.ready.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.nodeIdAllocation.blocked.count=").append(entry.getProperty("runtimeOptimizerDrift.nodeIdAllocation.blocked.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.nodeIdAllocation.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.nodeIdAllocation.firstBlocker", "none")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.nodeIdAllocation.allocationPreflightImplemented=").append(entry.getProperty("runtimeOptimizerDrift.nodeIdAllocation.allocationPreflightImplemented", "true")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.nodeIdAllocation.nodeIdsReserved=").append(entry.getProperty("runtimeOptimizerDrift.nodeIdAllocation.nodeIdsReserved", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.nodeIdAllocation.nodeIdAllocatorApplied=").append(entry.getProperty("runtimeOptimizerDrift.nodeIdAllocation.nodeIdAllocatorApplied", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.nodeIdAllocation.graphRewriteImplemented=").append(entry.getProperty("runtimeOptimizerDrift.nodeIdAllocation.graphRewriteImplemented", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.nodeIdAllocation.transformedIrBuilt=").append(entry.getProperty("runtimeOptimizerDrift.nodeIdAllocation.transformedIrBuilt", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.nodeIdAllocation.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.nodeIdAllocation.mutationAllowed", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.nodeIdAllocation.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.nodeIdAllocation.selectedIrReplacement", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementNode.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementNode.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementNode.ready.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementNode.ready.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementNode.blocked.count=").append(entry.getProperty("runtimeOptimizerDrift.replacementNode.blocked.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementNode.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.replacementNode.firstBlocker", "none")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementNode.replacementNodePreflightImplemented=").append(entry.getProperty("runtimeOptimizerDrift.replacementNode.replacementNodePreflightImplemented", "true")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementNode.replacementNodeBuilt=").append(entry.getProperty("runtimeOptimizerDrift.replacementNode.replacementNodeBuilt", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementNode.replacementBuilderImplemented=").append(entry.getProperty("runtimeOptimizerDrift.replacementNode.replacementBuilderImplemented", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementNode.graphRewriteImplemented=").append(entry.getProperty("runtimeOptimizerDrift.replacementNode.graphRewriteImplemented", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementNode.transformedIrBuilt=").append(entry.getProperty("runtimeOptimizerDrift.replacementNode.transformedIrBuilt", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementNode.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.replacementNode.mutationAllowed", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.replacementNode.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.replacementNode.selectedIrReplacement", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.graphPatch.count=").append(entry.getProperty("runtimeOptimizerDrift.graphPatch.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.graphPatch.ready.count=").append(entry.getProperty("runtimeOptimizerDrift.graphPatch.ready.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.graphPatch.blocked.count=").append(entry.getProperty("runtimeOptimizerDrift.graphPatch.blocked.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.graphPatch.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.graphPatch.firstBlocker", "none")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.graphPatch.graphPatchPreflightImplemented=").append(entry.getProperty("runtimeOptimizerDrift.graphPatch.graphPatchPreflightImplemented", "true")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.graphPatch.graphPatchApplied=").append(entry.getProperty("runtimeOptimizerDrift.graphPatch.graphPatchApplied", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.graphPatch.graphRewriteImplemented=").append(entry.getProperty("runtimeOptimizerDrift.graphPatch.graphRewriteImplemented", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.graphPatch.transformedIrBuilt=").append(entry.getProperty("runtimeOptimizerDrift.graphPatch.transformedIrBuilt", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.graphPatch.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.graphPatch.mutationAllowed", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.graphPatch.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.graphPatch.selectedIrReplacement", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.transformedGraph.count=").append(entry.getProperty("runtimeOptimizerDrift.transformedGraph.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.transformedGraph.ready.count=").append(entry.getProperty("runtimeOptimizerDrift.transformedGraph.ready.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.transformedGraph.blocked.count=").append(entry.getProperty("runtimeOptimizerDrift.transformedGraph.blocked.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.transformedGraph.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.transformedGraph.firstBlocker", "none")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.transformedGraph.materializationPreflightImplemented=").append(entry.getProperty("runtimeOptimizerDrift.transformedGraph.materializationPreflightImplemented", "true")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.transformedGraph.transformedGraphBuilt=").append(entry.getProperty("runtimeOptimizerDrift.transformedGraph.transformedGraphBuilt", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.transformedGraph.transformedIrBuilt=").append(entry.getProperty("runtimeOptimizerDrift.transformedGraph.transformedIrBuilt", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.transformedGraph.graphPatchApplied=").append(entry.getProperty("runtimeOptimizerDrift.transformedGraph.graphPatchApplied", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.transformedGraph.graphRewriteImplemented=").append(entry.getProperty("runtimeOptimizerDrift.transformedGraph.graphRewriteImplemented", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.transformedGraph.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.transformedGraph.mutationAllowed", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.transformedGraph.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.transformedGraph.selectedIrReplacement", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.count=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.ready.count=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.ready.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.blocked.count=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.blocked.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.firstBlocker", "none")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.artifactEnvelopePreflightImplemented=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.artifactEnvelopePreflightImplemented", "true")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.artifactEnvelopeBuilt=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.artifactEnvelopeBuilt", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.optimizedArtifactBuilt=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.optimizedArtifactBuilt", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.transformedGraphBuilt=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.transformedGraphBuilt", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.transformedIrBuilt=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.transformedIrBuilt", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.graphPatchApplied=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.graphPatchApplied", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.graphRewriteImplemented=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.graphRewriteImplemented", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.mutationAllowed", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.irArtifactEnvelope.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.irArtifactEnvelope.selectedIrReplacement", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.ready.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.ready.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.blocked.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.blocked.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.firstBlocker", "none")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.rewriteBuilderImplemented=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.rewriteBuilderImplemented", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.mutationAllowed", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.selectedIrReplacement", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.conflict.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.conflict.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.conflict.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.conflict.firstBlocker", "none")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.conflict.conflictResolutionImplemented=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.conflict.conflictResolutionImplemented", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.conflict.selectionApplied=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.conflict.selectionApplied", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.conflict.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.conflict.mutationAllowed", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSketch.conflict.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSketch.conflict.selectedIrReplacement", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.sketch.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.sketch.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.sketch.ready.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.sketch.ready.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.sketch.blocked.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.sketch.blocked.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.conflict.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.conflict.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.status=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.status", "not-required")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.firstBlocker", "no-rewrite-sketches")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.rewriteBuilderImplemented=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.rewriteBuilderImplemented", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.conflictResolutionImplemented=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.conflictResolutionImplemented", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.runtimeEquivalenceRequired=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.runtimeEquivalenceRequired", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.runtimeEquivalenceProven=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.runtimeEquivalenceProven", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.approvalRequired=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.approvalRequired", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.approvalAccepted=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.approvalAccepted", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.mutationAllowed", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.selectionApplied=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.selectionApplied", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteSelection.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.rewriteSelection.selectedIrReplacement", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteProof.status=").append(entry.getProperty("runtimeOptimizerDrift.rewriteProof.status", "not-required")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteProof.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.rewriteProof.firstBlocker", "no-proof-candidates")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteProof.proofAccepted=").append(entry.getProperty("runtimeOptimizerDrift.rewriteProof.proofAccepted", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteProof.runtimeEquivalencePayload.present=").append(entry.getProperty("runtimeOptimizerDrift.rewriteProof.runtimeEquivalencePayload.present", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteProof.runtimeEquivalencePayload.complete=").append(entry.getProperty("runtimeOptimizerDrift.rewriteProof.runtimeEquivalencePayload.complete", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteProof.rollbackEvidence.present=").append(entry.getProperty("runtimeOptimizerDrift.rewriteProof.rollbackEvidence.present", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteProof.rollbackClean=").append(entry.getProperty("runtimeOptimizerDrift.rewriteProof.rollbackClean", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteProof.approvalAccepted=").append(entry.getProperty("runtimeOptimizerDrift.rewriteProof.approvalAccepted", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteProof.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.rewriteProof.mutationAllowed", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteProof.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.rewriteProof.selectedIrReplacement", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.status=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.status", "not-required")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.firstBlocker=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.firstBlocker", "no-review-candidates")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.required=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.required", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.complete=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.complete", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.conflict.count=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.conflict.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.proofAccepted=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.proofAccepted", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.runtimeEquivalencePayload.present=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.runtimeEquivalencePayload.present", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.runtimeEquivalencePayload.complete=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.runtimeEquivalencePayload.complete", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.rollbackEvidence.present=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.rollbackEvidence.present", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.rollbackClean=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.rollbackClean", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.approvalAccepted=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.approvalAccepted", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.mutationAllowed=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.mutationAllowed", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.selectionApplied=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.selectionApplied", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.selectedIrReplacement=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.selectedIrReplacement", "false")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.rewriteReviewPackage.manualReviewOnly=").append(entry.getProperty("runtimeOptimizerDrift.rewriteReviewPackage.manualReviewOnly", "true")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.optimizerRule.count=").append(entry.getProperty("runtimeOptimizerDrift.optimizerRule.count", "0")).append('\n'); - builder.append(prefix).append("runtimeOptimizerDrift.optimizerRule.summary=").append(entry.getProperty("runtimeOptimizerDrift.optimizerRule.summary", "none")).append('\n'); + appendRuntimeOptimizerDriftProperties(builder, prefix, entry); appendIndexedPropertyGroup(builder, prefix, entry, "runtimeOptimizerDrift.optimizerRule"); builder.append(prefix).append("runtimeOptimizerDrift.optimizerFamily.count=").append(entry.getProperty("runtimeOptimizerDrift.optimizerFamily.count", "0")).append('\n'); builder.append(prefix).append("runtimeOptimizerDrift.optimizerFamily.promotionReady.count=").append(entry.getProperty("runtimeOptimizerDrift.optimizerFamily.promotionReady.count", "0")).append('\n'); @@ -1261,6 +1010,13 @@ private static void appendRuntimeOptimizerDrift(StringBuilder builder, String pr builder.append(prefix).append("optimizerFamilyPayload.family.complete.all=").append(entry.getProperty("optimizerFamilyPayload.family.complete.all", "false")).append('\n'); } + private static void appendRuntimeOptimizerDriftProperties(StringBuilder builder, String prefix, Properties entry) { + for (DriftProperty property : RUNTIME_OPTIMIZER_DRIFT_PROPERTIES) { + String key = RUNTIME_OPTIMIZER_DRIFT_PREFIX + property.key(); + builder.append(prefix).append(key).append('=').append(entry.getProperty(key, property.emptySourceDefault())).append('\n'); + } + } + private static void copyRuntimeExtensionParticipationProperties(Properties source, Properties target) { if (source == null || source.isEmpty()) { target.setProperty("runtimeExtensionParticipation.status", "not-recorded"); diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeArtifactProofBindingPreflight.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeArtifactProofBindingPreflight.java new file mode 100644 index 00000000..e4e78ecc --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeArtifactProofBindingPreflight.java @@ -0,0 +1,337 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import java.util.Map; + +/** + * Read-only binding preview between a future optimized artifact envelope and proof/review evidence. + * + *

The preview records which proof, runtime-equivalence, rollback, and approval anchors are still missing before a + * future optimized artifact could be selected. It never binds proof artifacts, builds optimized IR, or enables + * selection.

+ */ +public record GpuRuntimeIrPeepholeArtifactProofBindingPreflight( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + String originalIrIdentity, + String optimizedArtifactIdentity, + String envelopeKey, + String proofAnchor, + String rollbackAnchor, + String proofStatus, + String proofFirstBlocker, + String reviewPackageStatus, + String reviewPackageFirstBlocker, + boolean artifactEnvelopeReady, + boolean proofRequired, + boolean proofAccepted, + boolean runtimeEquivalencePayloadComplete, + boolean rollbackEvidencePresent, + boolean rollbackClean, + boolean approvalAccepted, + boolean reviewPackageComplete, + boolean bindingReady, + String status, + String firstBlocker, + boolean bindingPreflightImplemented, + boolean proofBound, + boolean rollbackBound, + boolean approvalBound, + boolean optimizedArtifactBuilt, + boolean transformedIrBuilt, + boolean mutationAllowed, + boolean selectedIrReplacement +) { + + private static final String RULE_UNKNOWN = "rule:unknown"; + private static final String UNKNOWN = "unknown"; + private static final String ORIGINAL_IR_UNKNOWN = "irgpu:unknown"; + private static final String NOT_BUILT = "not-built"; + private static final String STATUS_BLOCKED = "blocked"; + private static final String STATUS_NOT_REQUIRED = "not-required"; + private static final String STATUS_BINDING_READY = "binding-ready"; + private static final String BLOCKER_NONE = "none"; + private static final String BLOCKER_RUNTIME_EQUIVALENCE_PAYLOAD_MISSING = "runtime-equivalence-payload-missing"; + private static final String BLOCKER_NO_PROOF_CANDIDATES = "no-proof-candidates"; + private static final String BLOCKER_NO_REVIEW_CANDIDATES = "no-review-candidates"; + private static final String BLOCKER_ENVELOPE_MISSING = "envelope-missing"; + private static final String BLOCKER_ARTIFACT_PROOF_BINDING_BLOCKED = "artifact-proof-binding-blocked"; + private static final String BLOCKER_PROOF_NOT_REQUIRED_FOR_ENVELOPE = "proof-not-required-for-envelope"; + private static final String BLOCKER_ROLLBACK_EVIDENCE_MISSING = "rollback-evidence-missing"; + private static final String BLOCKER_ROLLBACK_NOT_CLEAN = "rollback-not-clean"; + private static final String BLOCKER_APPROVAL_NOT_ACCEPTED = "approval-not-accepted"; + private static final String BLOCKER_PROOF_NOT_ACCEPTED = "proof-not-accepted"; + private static final String BLOCKER_REVIEW_PACKAGE_MISSING = "review-package-missing"; + private static final String BLOCKER_MANUAL_BINDING_REQUIRED = "manual-binding-required"; + private static final String PROOF_ANCHOR_RUNTIME_EQUIVALENCE_REQUIRED = "runtime-equivalence-required"; + private static final String ROLLBACK_ANCHOR_ORIGINAL_IR = "original-ir"; + + public GpuRuntimeIrPeepholeArtifactProofBindingPreflight { + ruleId = normalize(ruleId, RULE_UNKNOWN); + methodName = normalize(methodName, UNKNOWN); + rootNodeId = Math.max(0, rootNodeId); + replacementKind = normalize(replacementKind, UNKNOWN); + originalIrIdentity = normalize(originalIrIdentity, ORIGINAL_IR_UNKNOWN); + optimizedArtifactIdentity = normalize(optimizedArtifactIdentity, NOT_BUILT); + envelopeKey = normalize(envelopeKey, NOT_BUILT); + proofAnchor = normalize(proofAnchor, PROOF_ANCHOR_RUNTIME_EQUIVALENCE_REQUIRED); + rollbackAnchor = normalize(rollbackAnchor, ROLLBACK_ANCHOR_ORIGINAL_IR); + proofStatus = normalize(proofStatus, proofRequired ? STATUS_BLOCKED : STATUS_NOT_REQUIRED); + proofFirstBlocker = normalize(proofFirstBlocker, proofFirstBlockerDefault(proofRequired)); + reviewPackageStatus = normalize(reviewPackageStatus, proofRequired ? STATUS_BLOCKED : STATUS_NOT_REQUIRED); + reviewPackageFirstBlocker = normalize(reviewPackageFirstBlocker, reviewPackageFirstBlockerDefault(proofRequired)); + status = normalize(status, bindingReady ? STATUS_BINDING_READY : STATUS_BLOCKED); + firstBlocker = normalize(firstBlocker, bindingReady ? BLOCKER_NONE : BLOCKER_ARTIFACT_PROOF_BINDING_BLOCKED); + } + + public static GpuRuntimeIrPeepholeArtifactProofBindingPreflight from( + GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight envelope, + GpuRuntimeIrPeepholeRewriteProofReadiness proofReadiness, + GpuRuntimeIrPeepholeRewriteReviewPackage reviewPackage + ) { + if (envelope == null) { + return blocked( + RULE_UNKNOWN, + UNKNOWN, + 0, + UNKNOWN, + ORIGINAL_IR_UNKNOWN, + NOT_BUILT, + NOT_BUILT, + PROOF_ANCHOR_RUNTIME_EQUIVALENCE_REQUIRED, + ROLLBACK_ANCHOR_ORIGINAL_IR, + false, + false, + false, + false, + false, + false, + false, + false, + STATUS_NOT_REQUIRED, + BLOCKER_ENVELOPE_MISSING, + STATUS_NOT_REQUIRED, + BLOCKER_ENVELOPE_MISSING, + BLOCKER_ENVELOPE_MISSING + ); + } + boolean proofRequired = proofReadiness != null && proofReadiness.proofRequired(); + boolean proofAccepted = proofReadiness != null && proofReadiness.proofAccepted(); + boolean runtimePayloadComplete = proofReadiness != null && proofReadiness.runtimeEquivalencePayloadComplete(); + boolean rollbackEvidencePresent = proofReadiness != null && proofReadiness.rollbackEvidencePresent(); + boolean rollbackClean = proofReadiness != null && proofReadiness.rollbackClean(); + boolean approvalAccepted = proofReadiness != null && proofReadiness.approvalAccepted(); + boolean reviewComplete = reviewPackage != null && reviewPackage.complete(); + boolean bindingReady = envelope.artifactEnvelopeReady() + && proofRequired + && proofAccepted + && runtimePayloadComplete + && rollbackEvidencePresent + && rollbackClean + && approvalAccepted + && reviewComplete; + if (!bindingReady) { + return blocked( + envelope.ruleId(), + envelope.methodName(), + envelope.rootNodeId(), + envelope.replacementKind(), + envelope.originalIrIdentity(), + envelope.optimizedArtifactIdentity(), + envelope.envelopeKey(), + envelope.proofAnchor(), + envelope.rollbackAnchor(), + envelope.artifactEnvelopeReady(), + proofRequired, + proofAccepted, + runtimePayloadComplete, + rollbackEvidencePresent, + rollbackClean, + approvalAccepted, + reviewComplete, + proofReadiness == null ? STATUS_NOT_REQUIRED : proofReadiness.status(), + proofReadiness == null ? BLOCKER_NO_PROOF_CANDIDATES : proofReadiness.firstBlocker(), + reviewPackage == null ? STATUS_NOT_REQUIRED : reviewPackage.status(), + reviewPackage == null ? BLOCKER_NO_REVIEW_CANDIDATES : reviewPackage.firstBlocker(), + firstBlocker(envelope, proofReadiness, reviewPackage) + ); + } + return new GpuRuntimeIrPeepholeArtifactProofBindingPreflight( + envelope.ruleId(), + envelope.methodName(), + envelope.rootNodeId(), + envelope.replacementKind(), + envelope.originalIrIdentity(), + envelope.optimizedArtifactIdentity(), + envelope.envelopeKey(), + envelope.proofAnchor(), + envelope.rollbackAnchor(), + proofReadiness.status(), + proofReadiness.firstBlocker(), + reviewPackage.status(), + reviewPackage.firstBlocker(), + true, + true, + true, + true, + true, + true, + true, + true, + true, + STATUS_BINDING_READY, + BLOCKER_NONE, + true, + false, + false, + false, + false, + false, + false, + false + ); + } + + public Map fields(String prefix) { + String normalizedPrefix = normalize(prefix, "artifactProofBinding"); + return Map.ofEntries( + Map.entry(normalizedPrefix + ".ruleId", ruleId), + Map.entry(normalizedPrefix + ".methodName", methodName), + Map.entry(normalizedPrefix + ".rootNodeId", Integer.toString(rootNodeId)), + Map.entry(normalizedPrefix + ".replacementKind", replacementKind), + Map.entry(normalizedPrefix + ".originalIrIdentity", originalIrIdentity), + Map.entry(normalizedPrefix + ".optimizedArtifactIdentity", optimizedArtifactIdentity), + Map.entry(normalizedPrefix + ".envelopeKey", envelopeKey), + Map.entry(normalizedPrefix + ".proofAnchor", proofAnchor), + Map.entry(normalizedPrefix + ".rollbackAnchor", rollbackAnchor), + Map.entry(normalizedPrefix + ".proofStatus", proofStatus), + Map.entry(normalizedPrefix + ".proofFirstBlocker", proofFirstBlocker), + Map.entry(normalizedPrefix + ".reviewPackageStatus", reviewPackageStatus), + Map.entry(normalizedPrefix + ".reviewPackageFirstBlocker", reviewPackageFirstBlocker), + Map.entry(normalizedPrefix + ".artifactEnvelopeReady", Boolean.toString(artifactEnvelopeReady)), + Map.entry(normalizedPrefix + ".proofRequired", Boolean.toString(proofRequired)), + Map.entry(normalizedPrefix + ".proofAccepted", Boolean.toString(proofAccepted)), + Map.entry(normalizedPrefix + ".runtimeEquivalencePayload.complete", Boolean.toString(runtimeEquivalencePayloadComplete)), + Map.entry(normalizedPrefix + ".rollbackEvidence.present", Boolean.toString(rollbackEvidencePresent)), + Map.entry(normalizedPrefix + ".rollbackClean", Boolean.toString(rollbackClean)), + Map.entry(normalizedPrefix + ".approvalAccepted", Boolean.toString(approvalAccepted)), + Map.entry(normalizedPrefix + ".reviewPackageComplete", Boolean.toString(reviewPackageComplete)), + Map.entry(normalizedPrefix + ".bindingReady", Boolean.toString(bindingReady)), + Map.entry(normalizedPrefix + ".status", status), + Map.entry(normalizedPrefix + ".firstBlocker", firstBlocker), + Map.entry(normalizedPrefix + ".bindingPreflightImplemented", Boolean.toString(bindingPreflightImplemented)), + Map.entry(normalizedPrefix + ".proofBound", Boolean.toString(proofBound)), + Map.entry(normalizedPrefix + ".rollbackBound", Boolean.toString(rollbackBound)), + Map.entry(normalizedPrefix + ".approvalBound", Boolean.toString(approvalBound)), + Map.entry(normalizedPrefix + ".optimizedArtifactBuilt", Boolean.toString(optimizedArtifactBuilt)), + Map.entry(normalizedPrefix + ".transformedIrBuilt", Boolean.toString(transformedIrBuilt)), + Map.entry(normalizedPrefix + ".mutationAllowed", Boolean.toString(mutationAllowed)), + Map.entry(normalizedPrefix + ".selectedIrReplacement", Boolean.toString(selectedIrReplacement)) + ); + } + + private static GpuRuntimeIrPeepholeArtifactProofBindingPreflight blocked( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + String originalIrIdentity, + String optimizedArtifactIdentity, + String envelopeKey, + String proofAnchor, + String rollbackAnchor, + boolean artifactEnvelopeReady, + boolean proofRequired, + boolean proofAccepted, + boolean runtimeEquivalencePayloadComplete, + boolean rollbackEvidencePresent, + boolean rollbackClean, + boolean approvalAccepted, + boolean reviewPackageComplete, + String proofStatus, + String proofFirstBlocker, + String reviewPackageStatus, + String reviewPackageFirstBlocker, + String firstBlocker + ) { + return new GpuRuntimeIrPeepholeArtifactProofBindingPreflight( + ruleId, + methodName, + rootNodeId, + replacementKind, + originalIrIdentity, + optimizedArtifactIdentity, + envelopeKey, + proofAnchor, + rollbackAnchor, + proofStatus, + proofFirstBlocker, + reviewPackageStatus, + reviewPackageFirstBlocker, + artifactEnvelopeReady, + proofRequired, + proofAccepted, + runtimeEquivalencePayloadComplete, + rollbackEvidencePresent, + rollbackClean, + approvalAccepted, + reviewPackageComplete, + false, + STATUS_BLOCKED, + firstBlocker, + true, + false, + false, + false, + false, + false, + false, + false + ); + } + + private static String firstBlocker( + GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight envelope, + GpuRuntimeIrPeepholeRewriteProofReadiness proofReadiness, + GpuRuntimeIrPeepholeRewriteReviewPackage reviewPackage + ) { + if (!envelope.artifactEnvelopeReady()) { + return envelope.firstBlocker(); + } + if (proofReadiness == null || !proofReadiness.proofRequired()) { + return BLOCKER_PROOF_NOT_REQUIRED_FOR_ENVELOPE; + } + if (!proofReadiness.runtimeEquivalencePayloadComplete()) { + return proofReadiness.firstBlocker(); + } + if (!proofReadiness.rollbackEvidencePresent()) { + return BLOCKER_ROLLBACK_EVIDENCE_MISSING; + } + if (!proofReadiness.rollbackClean()) { + return BLOCKER_ROLLBACK_NOT_CLEAN; + } + if (!proofReadiness.approvalAccepted()) { + return BLOCKER_APPROVAL_NOT_ACCEPTED; + } + if (!proofReadiness.proofAccepted()) { + return BLOCKER_PROOF_NOT_ACCEPTED; + } + if (reviewPackage == null || !reviewPackage.complete()) { + return reviewPackage == null ? BLOCKER_REVIEW_PACKAGE_MISSING : reviewPackage.firstBlocker(); + } + return BLOCKER_MANUAL_BINDING_REQUIRED; + } + + private static String proofFirstBlockerDefault(boolean proofRequired) { + return proofRequired ? BLOCKER_RUNTIME_EQUIVALENCE_PAYLOAD_MISSING : BLOCKER_NO_PROOF_CANDIDATES; + } + + private static String reviewPackageFirstBlockerDefault(boolean proofRequired) { + return proofRequired ? BLOCKER_RUNTIME_EQUIVALENCE_PAYLOAD_MISSING : BLOCKER_NO_REVIEW_CANDIDATES; + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeOptimizedArtifactSelectionPreflight.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeOptimizedArtifactSelectionPreflight.java new file mode 100644 index 00000000..7b503f7e --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeOptimizedArtifactSelectionPreflight.java @@ -0,0 +1,236 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import java.util.Map; + +/** + * Read-only optimized-artifact selection preview for future peephole rewrites. + * + *

The preview records why a future optimized artifact cannot become the selected runtime IR yet. It never builds, + * binds, selects, or mutates IR.

+ */ +public record GpuRuntimeIrPeepholeOptimizedArtifactSelectionPreflight( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + String originalIrIdentity, + String optimizedArtifactIdentity, + String envelopeKey, + String proofBindingStatus, + String proofBindingFirstBlocker, + boolean proofBindingReady, + boolean proofBound, + boolean rollbackBound, + boolean approvalBound, + boolean optimizedArtifactBuilt, + boolean transformedIrBuilt, + boolean productionGateRequired, + boolean productionGateAccepted, + boolean mutationPolicyAllowed, + boolean selectionReady, + String status, + String firstBlocker, + boolean selectionPreflightImplemented, + boolean selectionApplied, + boolean optimizedArtifactSelected, + boolean mutationAllowed, + boolean selectedIrReplacement +) { + + private static final String RULE_UNKNOWN = "rule:unknown"; + private static final String UNKNOWN = "unknown"; + private static final String ORIGINAL_IR_UNKNOWN = "irgpu:unknown"; + private static final String NOT_BUILT = "not-built"; + private static final String STATUS_BLOCKED = "blocked"; + private static final String STATUS_SELECTION_READY = "selection-ready"; + private static final String BLOCKER_NONE = "none"; + private static final String BLOCKER_ARTIFACT_PROOF_BINDING_BLOCKED = "artifact-proof-binding-blocked"; + private static final String BLOCKER_ARTIFACT_PROOF_BINDING_MISSING = "artifact-proof-binding-missing"; + private static final String BLOCKER_OPTIMIZED_ARTIFACT_SELECTION_BLOCKED = "optimized-artifact-selection-blocked"; + private static final String BLOCKER_PROOF_NOT_BOUND = "proof-not-bound"; + private static final String BLOCKER_ROLLBACK_NOT_BOUND = "rollback-not-bound"; + private static final String BLOCKER_APPROVAL_NOT_BOUND = "approval-not-bound"; + private static final String BLOCKER_OPTIMIZED_ARTIFACT_NOT_BUILT = "optimized-artifact-not-built"; + private static final String BLOCKER_TRANSFORMED_IR_NOT_BUILT = "transformed-ir-not-built"; + private static final String BLOCKER_PRODUCTION_GATE_NOT_ACCEPTED = "production-gate-not-accepted"; + + public GpuRuntimeIrPeepholeOptimizedArtifactSelectionPreflight { + ruleId = normalize(ruleId, RULE_UNKNOWN); + methodName = normalize(methodName, UNKNOWN); + rootNodeId = Math.max(0, rootNodeId); + replacementKind = normalize(replacementKind, UNKNOWN); + originalIrIdentity = normalize(originalIrIdentity, ORIGINAL_IR_UNKNOWN); + optimizedArtifactIdentity = normalize(optimizedArtifactIdentity, NOT_BUILT); + envelopeKey = normalize(envelopeKey, NOT_BUILT); + proofBindingStatus = normalize(proofBindingStatus, STATUS_BLOCKED); + proofBindingFirstBlocker = normalize(proofBindingFirstBlocker, BLOCKER_ARTIFACT_PROOF_BINDING_BLOCKED); + status = normalize(status, selectionReady ? STATUS_SELECTION_READY : STATUS_BLOCKED); + firstBlocker = normalize(firstBlocker, selectionReady ? BLOCKER_NONE : BLOCKER_OPTIMIZED_ARTIFACT_SELECTION_BLOCKED); + } + + public static GpuRuntimeIrPeepholeOptimizedArtifactSelectionPreflight from( + GpuRuntimeIrPeepholeArtifactProofBindingPreflight binding + ) { + if (binding == null) { + return blocked( + RULE_UNKNOWN, + UNKNOWN, + 0, + UNKNOWN, + ORIGINAL_IR_UNKNOWN, + NOT_BUILT, + NOT_BUILT, + STATUS_BLOCKED, + BLOCKER_ARTIFACT_PROOF_BINDING_MISSING, + false, + false, + false, + false, + false, + false, + BLOCKER_ARTIFACT_PROOF_BINDING_MISSING + ); + } + if (!binding.bindingReady()) { + return blocked( + binding.ruleId(), + binding.methodName(), + binding.rootNodeId(), + binding.replacementKind(), + binding.originalIrIdentity(), + binding.optimizedArtifactIdentity(), + binding.envelopeKey(), + binding.status(), + binding.firstBlocker(), + false, + binding.proofBound(), + binding.rollbackBound(), + binding.approvalBound(), + binding.optimizedArtifactBuilt(), + binding.transformedIrBuilt(), + binding.firstBlocker() + ); + } + return blocked( + binding.ruleId(), + binding.methodName(), + binding.rootNodeId(), + binding.replacementKind(), + binding.originalIrIdentity(), + binding.optimizedArtifactIdentity(), + binding.envelopeKey(), + binding.status(), + binding.firstBlocker(), + true, + binding.proofBound(), + binding.rollbackBound(), + binding.approvalBound(), + binding.optimizedArtifactBuilt(), + binding.transformedIrBuilt(), + firstBlocker(binding) + ); + } + + public Map fields(String prefix) { + String normalizedPrefix = normalize(prefix, "artifactSelection"); + return Map.ofEntries( + Map.entry(normalizedPrefix + ".ruleId", ruleId), + Map.entry(normalizedPrefix + ".methodName", methodName), + Map.entry(normalizedPrefix + ".rootNodeId", Integer.toString(rootNodeId)), + Map.entry(normalizedPrefix + ".replacementKind", replacementKind), + Map.entry(normalizedPrefix + ".originalIrIdentity", originalIrIdentity), + Map.entry(normalizedPrefix + ".optimizedArtifactIdentity", optimizedArtifactIdentity), + Map.entry(normalizedPrefix + ".envelopeKey", envelopeKey), + Map.entry(normalizedPrefix + ".proofBindingStatus", proofBindingStatus), + Map.entry(normalizedPrefix + ".proofBindingFirstBlocker", proofBindingFirstBlocker), + Map.entry(normalizedPrefix + ".proofBindingReady", Boolean.toString(proofBindingReady)), + Map.entry(normalizedPrefix + ".proofBound", Boolean.toString(proofBound)), + Map.entry(normalizedPrefix + ".rollbackBound", Boolean.toString(rollbackBound)), + Map.entry(normalizedPrefix + ".approvalBound", Boolean.toString(approvalBound)), + Map.entry(normalizedPrefix + ".optimizedArtifactBuilt", Boolean.toString(optimizedArtifactBuilt)), + Map.entry(normalizedPrefix + ".transformedIrBuilt", Boolean.toString(transformedIrBuilt)), + Map.entry(normalizedPrefix + ".productionGateRequired", Boolean.toString(productionGateRequired)), + Map.entry(normalizedPrefix + ".productionGateAccepted", Boolean.toString(productionGateAccepted)), + Map.entry(normalizedPrefix + ".mutationPolicyAllowed", Boolean.toString(mutationPolicyAllowed)), + Map.entry(normalizedPrefix + ".selectionReady", Boolean.toString(selectionReady)), + Map.entry(normalizedPrefix + ".status", status), + Map.entry(normalizedPrefix + ".firstBlocker", firstBlocker), + Map.entry(normalizedPrefix + ".selectionPreflightImplemented", Boolean.toString(selectionPreflightImplemented)), + Map.entry(normalizedPrefix + ".selectionApplied", Boolean.toString(selectionApplied)), + Map.entry(normalizedPrefix + ".optimizedArtifactSelected", Boolean.toString(optimizedArtifactSelected)), + Map.entry(normalizedPrefix + ".mutationAllowed", Boolean.toString(mutationAllowed)), + Map.entry(normalizedPrefix + ".selectedIrReplacement", Boolean.toString(selectedIrReplacement)) + ); + } + + private static GpuRuntimeIrPeepholeOptimizedArtifactSelectionPreflight blocked( + String ruleId, + String methodName, + int rootNodeId, + String replacementKind, + String originalIrIdentity, + String optimizedArtifactIdentity, + String envelopeKey, + String proofBindingStatus, + String proofBindingFirstBlocker, + boolean proofBindingReady, + boolean proofBound, + boolean rollbackBound, + boolean approvalBound, + boolean optimizedArtifactBuilt, + boolean transformedIrBuilt, + String firstBlocker + ) { + return new GpuRuntimeIrPeepholeOptimizedArtifactSelectionPreflight( + ruleId, + methodName, + rootNodeId, + replacementKind, + originalIrIdentity, + optimizedArtifactIdentity, + envelopeKey, + proofBindingStatus, + proofBindingFirstBlocker, + proofBindingReady, + proofBound, + rollbackBound, + approvalBound, + optimizedArtifactBuilt, + transformedIrBuilt, + true, + false, + false, + false, + STATUS_BLOCKED, + firstBlocker, + true, + false, + false, + false, + false + ); + } + + private static String firstBlocker(GpuRuntimeIrPeepholeArtifactProofBindingPreflight binding) { + if (!binding.proofBound()) { + return BLOCKER_PROOF_NOT_BOUND; + } + if (!binding.rollbackBound()) { + return BLOCKER_ROLLBACK_NOT_BOUND; + } + if (!binding.approvalBound()) { + return BLOCKER_APPROVAL_NOT_BOUND; + } + if (!binding.optimizedArtifactBuilt()) { + return BLOCKER_OPTIMIZED_ARTIFACT_NOT_BUILT; + } + if (!binding.transformedIrBuilt()) { + return BLOCKER_TRANSFORMED_IR_NOT_BUILT; + } + return BLOCKER_PRODUCTION_GATE_NOT_ACCEPTED; + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholePass.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholePass.java index e4b95ce4..67592220 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholePass.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholePass.java @@ -5,6 +5,8 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; /** * Diagnostic-only entrypoint for future structural runtime IR peephole rewrites. @@ -112,7 +114,8 @@ private Map diagnosticFields( appendGraphPatchFields(fields, "graphPatch", graphPatches); List transformedGraphs = transformedGraphs(graphPatches, originalIrIdentity); appendTransformedGraphFields(fields, "transformedGraph", transformedGraphs); - appendIrArtifactEnvelopeFields(fields, "irArtifactEnvelope", irArtifactEnvelopes(transformedGraphs)); + List artifactEnvelopes = irArtifactEnvelopes(transformedGraphs); + appendIrArtifactEnvelopeFields(fields, "irArtifactEnvelope", artifactEnvelopes); List rewriteSketches = rewriteSketches(analysis.ruleReports()); List rewriteSketchConflicts = rewriteSketchConflicts(rewriteSketches); appendRewriteSketchFields(fields, "rewriteSketch", rewriteSketches); @@ -123,9 +126,24 @@ private Map diagnosticFields( GpuRuntimeIrPeepholeRewriteProofReadiness proofReadiness = GpuRuntimeIrPeepholeRewriteProofReadiness.from(selectionReadiness, originalIrIdentity); fields.putAll(proofReadiness.fields("rewriteProof")); - fields.putAll(GpuRuntimeIrPeepholeRewriteReviewPackage - .from(selectionReadiness, proofReadiness) - .fields("rewriteReviewPackage")); + GpuRuntimeIrPeepholeRewriteReviewPackage reviewPackage = GpuRuntimeIrPeepholeRewriteReviewPackage + .from(selectionReadiness, proofReadiness); + fields.putAll(reviewPackage.fields("rewriteReviewPackage")); + List artifactProofBindings = artifactProofBindings( + artifactEnvelopes, + proofReadiness, + reviewPackage + ); + appendArtifactProofBindingFields( + fields, + "artifactProofBinding", + artifactProofBindings + ); + appendArtifactSelectionFields( + fields, + "artifactSelection", + artifactSelections(artifactProofBindings) + ); fields.put("rule.mix.candidate.count", Integer.toString(candidateCount(analysis, "mix"))); fields.put("rule.madFma.candidate.count", Integer.toString(candidateCount(analysis, "madFma"))); fields.put("rule.clamp.candidate.count", Integer.toString(candidateCount(analysis, "clamp"))); @@ -250,10 +268,13 @@ private void appendRuleFields( prefix + ".transformedGraph", ruleTransformedGraphs ); + List ruleArtifactEnvelopes = irArtifactEnvelopes( + ruleTransformedGraphs + ); appendIrArtifactEnvelopeFields( fields, prefix + ".irArtifactEnvelope", - irArtifactEnvelopes(ruleTransformedGraphs) + ruleArtifactEnvelopes ); List ruleSketches = rewriteSketches(reports); List ruleConflicts = rewriteSketchConflicts(ruleSketches); @@ -265,9 +286,24 @@ private void appendRuleFields( GpuRuntimeIrPeepholeRewriteProofReadiness ruleProofReadiness = GpuRuntimeIrPeepholeRewriteProofReadiness.from(ruleSelectionReadiness, originalIrIdentity); fields.putAll(ruleProofReadiness.fields(prefix + ".rewriteProof")); - fields.putAll(GpuRuntimeIrPeepholeRewriteReviewPackage - .from(ruleSelectionReadiness, ruleProofReadiness) - .fields(prefix + ".rewriteReviewPackage")); + GpuRuntimeIrPeepholeRewriteReviewPackage ruleReviewPackage = GpuRuntimeIrPeepholeRewriteReviewPackage + .from(ruleSelectionReadiness, ruleProofReadiness); + fields.putAll(ruleReviewPackage.fields(prefix + ".rewriteReviewPackage")); + List ruleArtifactProofBindings = artifactProofBindings( + ruleArtifactEnvelopes, + ruleProofReadiness, + ruleReviewPackage + ); + appendArtifactProofBindingFields( + fields, + prefix + ".artifactProofBinding", + ruleArtifactProofBindings + ); + appendArtifactSelectionFields( + fields, + prefix + ".artifactSelection", + artifactSelections(ruleArtifactProofBindings) + ); fields.put(prefix + ".firstBlocker", ruleFirstBlocker(fields, prefix, reports.isEmpty())); } } @@ -365,31 +401,88 @@ private static List rewriteVisitPrefl .toList(); } - private static void appendRewriteVisitorFields( + private static void appendPreflightSummaryFields( LinkedHashMap fields, String prefix, - List preflights + List items, + Predicate ready, + Function firstBlocker ) { - fields.put(prefix + ".count", Integer.toString(preflights.size())); - fields.put(prefix + ".ready.count", Long.toString(preflights.stream() - .filter(GpuRuntimeIrPeepholeRewriteVisitPreflight::visitorReady) + fields.put(prefix + ".count", Integer.toString(items.size())); + fields.put(prefix + ".ready.count", Long.toString(items.stream() + .filter(ready) .count())); - fields.put(prefix + ".blocked.count", Long.toString(preflights.stream() - .filter(preflight -> !preflight.visitorReady()) + fields.put(prefix + ".blocked.count", Long.toString(items.stream() + .filter(item -> !ready.test(item)) .count())); - fields.put(prefix + ".firstBlocker", preflights.stream() - .filter(preflight -> !preflight.visitorReady()) - .map(GpuRuntimeIrPeepholeRewriteVisitPreflight::firstBlocker) + fields.put(prefix + ".firstBlocker", items.stream() + .filter(item -> !ready.test(item)) + .map(firstBlocker) .findFirst() .orElse("none")); + } + + private static void appendFirstPreflightFields( + LinkedHashMap fields, + String prefix, + List items, + Function> itemFields + ) { + if (!items.isEmpty()) { + fields.putAll(itemFields.apply(items.get(0))); + } + } + + private static void appendProofBindingGuardFields(LinkedHashMap fields, String prefix) { + fields.put(prefix + ".proofBound", "false"); + fields.put(prefix + ".rollbackBound", "false"); + fields.put(prefix + ".approvalBound", "false"); + appendArtifactBuildGuardFields(fields, prefix); + appendMutationGuardFields(fields, prefix); + } + + private static void appendArtifactSelectionGuardFields( + LinkedHashMap fields, + String prefix, + boolean productionGateRequired + ) { + fields.put(prefix + ".productionGateRequired", Boolean.toString(productionGateRequired)); + fields.put(prefix + ".productionGateAccepted", "false"); + fields.put(prefix + ".mutationPolicyAllowed", "false"); + fields.put(prefix + ".selectionApplied", "false"); + fields.put(prefix + ".optimizedArtifactSelected", "false"); + appendArtifactBuildGuardFields(fields, prefix); + appendMutationGuardFields(fields, prefix); + } + + private static void appendArtifactBuildGuardFields(LinkedHashMap fields, String prefix) { + fields.put(prefix + ".optimizedArtifactBuilt", "false"); + fields.put(prefix + ".transformedIrBuilt", "false"); + } + + private static void appendMutationGuardFields(LinkedHashMap fields, String prefix) { + fields.put(prefix + ".mutationAllowed", "false"); + fields.put(prefix + ".selectedIrReplacement", "false"); + } + + private static void appendRewriteVisitorFields( + LinkedHashMap fields, + String prefix, + List preflights + ) { + appendPreflightSummaryFields( + fields, + prefix, + preflights, + GpuRuntimeIrPeepholeRewriteVisitPreflight::visitorReady, + GpuRuntimeIrPeepholeRewriteVisitPreflight::firstBlocker + ); fields.put(prefix + ".visitorImplemented", "true"); fields.put(prefix + ".replacementBuilderImplemented", "false"); fields.put(prefix + ".transformedIrBuilt", "false"); fields.put(prefix + ".mutationAllowed", "false"); fields.put(prefix + ".selectedIrReplacement", "false"); - if (!preflights.isEmpty()) { - fields.putAll(preflights.get(0).fields(prefix + ".0")); - } + appendFirstPreflightFields(fields, prefix, preflights, preflight -> preflight.fields(prefix + ".0")); } private static List replacementBlueprints( @@ -408,26 +501,19 @@ private static void appendReplacementBlueprintFields( String prefix, List blueprints ) { - fields.put(prefix + ".count", Integer.toString(blueprints.size())); - fields.put(prefix + ".ready.count", Long.toString(blueprints.stream() - .filter(GpuRuntimeIrPeepholeReplacementBlueprint::blueprintReady) - .count())); - fields.put(prefix + ".blocked.count", Long.toString(blueprints.stream() - .filter(blueprint -> !blueprint.blueprintReady()) - .count())); - fields.put(prefix + ".firstBlocker", blueprints.stream() - .filter(blueprint -> !blueprint.blueprintReady()) - .map(GpuRuntimeIrPeepholeReplacementBlueprint::firstBlocker) - .findFirst() - .orElse("none")); + appendPreflightSummaryFields( + fields, + prefix, + blueprints, + GpuRuntimeIrPeepholeReplacementBlueprint::blueprintReady, + GpuRuntimeIrPeepholeReplacementBlueprint::firstBlocker + ); fields.put(prefix + ".blueprintImplemented", "true"); fields.put(prefix + ".replacementBuilderImplemented", "false"); fields.put(prefix + ".transformedIrBuilt", "false"); fields.put(prefix + ".mutationAllowed", "false"); fields.put(prefix + ".selectedIrReplacement", "false"); - if (!blueprints.isEmpty()) { - fields.putAll(blueprints.get(0).fields(prefix + ".0")); - } + appendFirstPreflightFields(fields, prefix, blueprints, blueprint -> blueprint.fields(prefix + ".0")); } private static List rewriteTransactions( @@ -452,27 +538,20 @@ private static void appendRewriteTransactionFields( String prefix, List transactions ) { - fields.put(prefix + ".count", Integer.toString(transactions.size())); - fields.put(prefix + ".ready.count", Long.toString(transactions.stream() - .filter(GpuRuntimeIrPeepholeRewriteTransactionPreflight::transactionReady) - .count())); - fields.put(prefix + ".blocked.count", Long.toString(transactions.stream() - .filter(transaction -> !transaction.transactionReady()) - .count())); - fields.put(prefix + ".firstBlocker", transactions.stream() - .filter(transaction -> !transaction.transactionReady()) - .map(GpuRuntimeIrPeepholeRewriteTransactionPreflight::firstBlocker) - .findFirst() - .orElse("none")); + appendPreflightSummaryFields( + fields, + prefix, + transactions, + GpuRuntimeIrPeepholeRewriteTransactionPreflight::transactionReady, + GpuRuntimeIrPeepholeRewriteTransactionPreflight::firstBlocker + ); fields.put(prefix + ".transactionPreflightImplemented", "true"); fields.put(prefix + ".nodeIdAllocatorImplemented", "false"); fields.put(prefix + ".graphRewriteImplemented", "false"); fields.put(prefix + ".transformedIrBuilt", "false"); fields.put(prefix + ".mutationAllowed", "false"); fields.put(prefix + ".selectedIrReplacement", "false"); - if (!transactions.isEmpty()) { - fields.putAll(transactions.get(0).fields(prefix + ".0")); - } + appendFirstPreflightFields(fields, prefix, transactions, transaction -> transaction.fields(prefix + ".0")); } private static List nodeIdAllocations( @@ -497,18 +576,13 @@ private static void appendNodeIdAllocationFields( String prefix, List allocations ) { - fields.put(prefix + ".count", Integer.toString(allocations.size())); - fields.put(prefix + ".ready.count", Long.toString(allocations.stream() - .filter(GpuRuntimeIrPeepholeNodeIdAllocationPreflight::allocationReady) - .count())); - fields.put(prefix + ".blocked.count", Long.toString(allocations.stream() - .filter(allocation -> !allocation.allocationReady()) - .count())); - fields.put(prefix + ".firstBlocker", allocations.stream() - .filter(allocation -> !allocation.allocationReady()) - .map(GpuRuntimeIrPeepholeNodeIdAllocationPreflight::firstBlocker) - .findFirst() - .orElse("none")); + appendPreflightSummaryFields( + fields, + prefix, + allocations, + GpuRuntimeIrPeepholeNodeIdAllocationPreflight::allocationReady, + GpuRuntimeIrPeepholeNodeIdAllocationPreflight::firstBlocker + ); fields.put(prefix + ".allocationPreflightImplemented", "true"); fields.put(prefix + ".nodeIdsReserved", "false"); fields.put(prefix + ".nodeIdAllocatorApplied", "false"); @@ -516,9 +590,7 @@ private static void appendNodeIdAllocationFields( fields.put(prefix + ".transformedIrBuilt", "false"); fields.put(prefix + ".mutationAllowed", "false"); fields.put(prefix + ".selectedIrReplacement", "false"); - if (!allocations.isEmpty()) { - fields.putAll(allocations.get(0).fields(prefix + ".0")); - } + appendFirstPreflightFields(fields, prefix, allocations, allocation -> allocation.fields(prefix + ".0")); } private static List replacementNodes( @@ -543,18 +615,13 @@ private static void appendReplacementNodeFields( String prefix, List nodes ) { - fields.put(prefix + ".count", Integer.toString(nodes.size())); - fields.put(prefix + ".ready.count", Long.toString(nodes.stream() - .filter(GpuRuntimeIrPeepholeReplacementNodePreflight::replacementNodeReady) - .count())); - fields.put(prefix + ".blocked.count", Long.toString(nodes.stream() - .filter(node -> !node.replacementNodeReady()) - .count())); - fields.put(prefix + ".firstBlocker", nodes.stream() - .filter(node -> !node.replacementNodeReady()) - .map(GpuRuntimeIrPeepholeReplacementNodePreflight::firstBlocker) - .findFirst() - .orElse("none")); + appendPreflightSummaryFields( + fields, + prefix, + nodes, + GpuRuntimeIrPeepholeReplacementNodePreflight::replacementNodeReady, + GpuRuntimeIrPeepholeReplacementNodePreflight::firstBlocker + ); fields.put(prefix + ".replacementNodePreflightImplemented", "true"); fields.put(prefix + ".replacementNodeBuilt", "false"); fields.put(prefix + ".replacementBuilderImplemented", "false"); @@ -562,9 +629,7 @@ private static void appendReplacementNodeFields( fields.put(prefix + ".transformedIrBuilt", "false"); fields.put(prefix + ".mutationAllowed", "false"); fields.put(prefix + ".selectedIrReplacement", "false"); - if (!nodes.isEmpty()) { - fields.putAll(nodes.get(0).fields(prefix + ".0")); - } + appendFirstPreflightFields(fields, prefix, nodes, node -> node.fields(prefix + ".0")); } private static List graphPatches( @@ -589,27 +654,20 @@ private static void appendGraphPatchFields( String prefix, List patches ) { - fields.put(prefix + ".count", Integer.toString(patches.size())); - fields.put(prefix + ".ready.count", Long.toString(patches.stream() - .filter(GpuRuntimeIrPeepholeGraphPatchPreflight::graphPatchReady) - .count())); - fields.put(prefix + ".blocked.count", Long.toString(patches.stream() - .filter(patch -> !patch.graphPatchReady()) - .count())); - fields.put(prefix + ".firstBlocker", patches.stream() - .filter(patch -> !patch.graphPatchReady()) - .map(GpuRuntimeIrPeepholeGraphPatchPreflight::firstBlocker) - .findFirst() - .orElse("none")); + appendPreflightSummaryFields( + fields, + prefix, + patches, + GpuRuntimeIrPeepholeGraphPatchPreflight::graphPatchReady, + GpuRuntimeIrPeepholeGraphPatchPreflight::firstBlocker + ); fields.put(prefix + ".graphPatchPreflightImplemented", "true"); fields.put(prefix + ".graphPatchApplied", "false"); fields.put(prefix + ".graphRewriteImplemented", "false"); fields.put(prefix + ".transformedIrBuilt", "false"); fields.put(prefix + ".mutationAllowed", "false"); fields.put(prefix + ".selectedIrReplacement", "false"); - if (!patches.isEmpty()) { - fields.putAll(patches.get(0).fields(prefix + ".0")); - } + appendFirstPreflightFields(fields, prefix, patches, patch -> patch.fields(prefix + ".0")); } private static List transformedGraphs( @@ -629,18 +687,13 @@ private static void appendTransformedGraphFields( String prefix, List transformedGraphs ) { - fields.put(prefix + ".count", Integer.toString(transformedGraphs.size())); - fields.put(prefix + ".ready.count", Long.toString(transformedGraphs.stream() - .filter(GpuRuntimeIrPeepholeTransformedGraphPreflight::materializationReady) - .count())); - fields.put(prefix + ".blocked.count", Long.toString(transformedGraphs.stream() - .filter(graph -> !graph.materializationReady()) - .count())); - fields.put(prefix + ".firstBlocker", transformedGraphs.stream() - .filter(graph -> !graph.materializationReady()) - .map(GpuRuntimeIrPeepholeTransformedGraphPreflight::firstBlocker) - .findFirst() - .orElse("none")); + appendPreflightSummaryFields( + fields, + prefix, + transformedGraphs, + GpuRuntimeIrPeepholeTransformedGraphPreflight::materializationReady, + GpuRuntimeIrPeepholeTransformedGraphPreflight::firstBlocker + ); fields.put(prefix + ".materializationPreflightImplemented", "true"); fields.put(prefix + ".transformedGraphBuilt", "false"); fields.put(prefix + ".transformedIrBuilt", "false"); @@ -648,9 +701,7 @@ private static void appendTransformedGraphFields( fields.put(prefix + ".graphRewriteImplemented", "false"); fields.put(prefix + ".mutationAllowed", "false"); fields.put(prefix + ".selectedIrReplacement", "false"); - if (!transformedGraphs.isEmpty()) { - fields.putAll(transformedGraphs.get(0).fields(prefix + ".0")); - } + appendFirstPreflightFields(fields, prefix, transformedGraphs, graph -> graph.fields(prefix + ".0")); } private static List irArtifactEnvelopes( @@ -669,18 +720,13 @@ private static void appendIrArtifactEnvelopeFields( String prefix, List envelopes ) { - fields.put(prefix + ".count", Integer.toString(envelopes.size())); - fields.put(prefix + ".ready.count", Long.toString(envelopes.stream() - .filter(GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight::artifactEnvelopeReady) - .count())); - fields.put(prefix + ".blocked.count", Long.toString(envelopes.stream() - .filter(envelope -> !envelope.artifactEnvelopeReady()) - .count())); - fields.put(prefix + ".firstBlocker", envelopes.stream() - .filter(envelope -> !envelope.artifactEnvelopeReady()) - .map(GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight::firstBlocker) - .findFirst() - .orElse("none")); + appendPreflightSummaryFields( + fields, + prefix, + envelopes, + GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight::artifactEnvelopeReady, + GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight::firstBlocker + ); fields.put(prefix + ".artifactEnvelopePreflightImplemented", "true"); fields.put(prefix + ".artifactEnvelopeBuilt", "false"); fields.put(prefix + ".optimizedArtifactBuilt", "false"); @@ -690,9 +736,69 @@ private static void appendIrArtifactEnvelopeFields( fields.put(prefix + ".graphRewriteImplemented", "false"); fields.put(prefix + ".mutationAllowed", "false"); fields.put(prefix + ".selectedIrReplacement", "false"); - if (!envelopes.isEmpty()) { - fields.putAll(envelopes.get(0).fields(prefix + ".0")); + appendFirstPreflightFields(fields, prefix, envelopes, envelope -> envelope.fields(prefix + ".0")); + } + + private static List artifactProofBindings( + List envelopes, + GpuRuntimeIrPeepholeRewriteProofReadiness proofReadiness, + GpuRuntimeIrPeepholeRewriteReviewPackage reviewPackage + ) { + if (envelopes == null || envelopes.isEmpty()) { + return List.of(); + } + return envelopes.stream() + .map(envelope -> GpuRuntimeIrPeepholeArtifactProofBindingPreflight.from( + envelope, + proofReadiness, + reviewPackage + )) + .toList(); + } + + private static void appendArtifactProofBindingFields( + LinkedHashMap fields, + String prefix, + List bindings + ) { + appendPreflightSummaryFields( + fields, + prefix, + bindings, + GpuRuntimeIrPeepholeArtifactProofBindingPreflight::bindingReady, + GpuRuntimeIrPeepholeArtifactProofBindingPreflight::firstBlocker + ); + fields.put(prefix + ".bindingPreflightImplemented", "true"); + appendProofBindingGuardFields(fields, prefix); + appendFirstPreflightFields(fields, prefix, bindings, binding -> binding.fields(prefix + ".0")); + } + + private static List artifactSelections( + List bindings + ) { + if (bindings == null || bindings.isEmpty()) { + return List.of(); } + return bindings.stream() + .map(GpuRuntimeIrPeepholeOptimizedArtifactSelectionPreflight::from) + .toList(); + } + + private static void appendArtifactSelectionFields( + LinkedHashMap fields, + String prefix, + List selections + ) { + appendPreflightSummaryFields( + fields, + prefix, + selections, + GpuRuntimeIrPeepholeOptimizedArtifactSelectionPreflight::selectionReady, + GpuRuntimeIrPeepholeOptimizedArtifactSelectionPreflight::firstBlocker + ); + fields.put(prefix + ".selectionPreflightImplemented", "true"); + appendArtifactSelectionGuardFields(fields, prefix, !selections.isEmpty()); + appendFirstPreflightFields(fields, prefix, selections, selection -> selection.fields(prefix + ".0")); } private static void appendRewriteSketchFields( @@ -700,26 +806,19 @@ private static void appendRewriteSketchFields( String prefix, List sketches ) { - fields.put(prefix + ".count", Integer.toString(sketches.size())); - fields.put(prefix + ".ready.count", Long.toString(sketches.stream() - .filter(GpuRuntimeIrPeepholeRewriteSketch::sketchReady) - .count())); - fields.put(prefix + ".blocked.count", Long.toString(sketches.stream() - .filter(sketch -> !sketch.sketchReady()) - .count())); - fields.put(prefix + ".firstBlocker", sketches.stream() - .filter(sketch -> !sketch.sketchReady()) - .map(GpuRuntimeIrPeepholeRewriteSketch::firstBlocker) - .findFirst() - .orElse("none")); + appendPreflightSummaryFields( + fields, + prefix, + sketches, + GpuRuntimeIrPeepholeRewriteSketch::sketchReady, + GpuRuntimeIrPeepholeRewriteSketch::firstBlocker + ); fields.put(prefix + ".rewriteBuilderImplemented", "false"); fields.put(prefix + ".mutationAllowed", "false"); fields.put(prefix + ".selectedIrReplacement", "false"); fields.put(prefix + ".runtimeEquivalenceRequired", Boolean.toString(!sketches.isEmpty())); fields.put(prefix + ".approvalRequired", Boolean.toString(!sketches.isEmpty())); - if (!sketches.isEmpty()) { - fields.putAll(sketches.get(0).fields(prefix + ".0")); - } + appendFirstPreflightFields(fields, prefix, sketches, sketch -> sketch.fields(prefix + ".0")); } private static List rewriteSketchConflicts( diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeOptimizerDriftArtifact.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeOptimizerDriftArtifact.java index 0552d8cf..ff2bb749 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeOptimizerDriftArtifact.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeOptimizerDriftArtifact.java @@ -62,6 +62,14 @@ public record GpuRuntimeOptimizerDriftArtifact( int irArtifactEnvelopeReadyCount, int irArtifactEnvelopeBlockedCount, String irArtifactEnvelopeFirstBlocker, + int artifactProofBindingCount, + int artifactProofBindingReadyCount, + int artifactProofBindingBlockedCount, + String artifactProofBindingFirstBlocker, + int artifactSelectionCount, + int artifactSelectionReadyCount, + int artifactSelectionBlockedCount, + String artifactSelectionFirstBlocker, int rewriteSketchCount, int rewriteSketchReadyCount, int rewriteSketchBlockedCount, @@ -147,6 +155,14 @@ public static GpuRuntimeOptimizerDriftArtifact from(GpuRuntimeCompileArtifactSna 0, "none", 0, + 0, + 0, + "none", + 0, + 0, + 0, + "none", + 0, "none", "not-required", "no-rewrite-sketches", @@ -208,6 +224,8 @@ public static GpuRuntimeOptimizerDriftArtifact from(GpuRuntimeCompileArtifactSna int irArtifactEnvelopeReadyCount = irArtifactEnvelopeReadyCount(report); int irArtifactEnvelopeBlockedCount = irArtifactEnvelopeBlockedCount(report); String irArtifactEnvelopeFirstBlocker = irArtifactEnvelopeFirstBlocker(report); + ProofFieldSummary artifactProofBinding = proofFieldSummary(report, "artifactProofBinding"); + ProofFieldSummary artifactSelection = proofFieldSummary(report, "artifactSelection"); int rewriteSketchConflictCount = rewriteSketchConflictCount(report); String rewriteSketchConflictFirstBlocker = rewriteSketchConflictFirstBlocker(report); String rewriteProofStatus = rewriteProofStatus(report, rewriteSketchReadyCount); @@ -268,6 +286,14 @@ public static GpuRuntimeOptimizerDriftArtifact from(GpuRuntimeCompileArtifactSna irArtifactEnvelopeReadyCount, irArtifactEnvelopeBlockedCount, irArtifactEnvelopeFirstBlocker, + artifactProofBinding.count(), + artifactProofBinding.readyCount(), + artifactProofBinding.blockedCount(), + artifactProofBinding.firstBlocker(), + artifactSelection.count(), + artifactSelection.readyCount(), + artifactSelection.blockedCount(), + artifactSelection.firstBlocker(), rewriteSketchCount, rewriteSketchReadyCount, rewriteSketchBlockedCount, @@ -418,6 +444,22 @@ public String toPropertiesText() { builder.append("irArtifactEnvelope.graphRewriteImplemented=false\n"); builder.append("irArtifactEnvelope.mutationAllowed=false\n"); builder.append("irArtifactEnvelope.selectedIrReplacement=false\n"); + appendArtifactProofBindingProperties( + builder, + "", + artifactProofBindingCount, + artifactProofBindingReadyCount, + artifactProofBindingBlockedCount, + artifactProofBindingFirstBlocker + ); + appendArtifactSelectionProperties( + builder, + "", + artifactSelectionCount, + artifactSelectionReadyCount, + artifactSelectionBlockedCount, + artifactSelectionFirstBlocker + ); builder.append("rewriteSketch.count=").append(rewriteSketchCount).append('\n'); builder.append("rewriteSketch.ready.count=").append(rewriteSketchReadyCount).append('\n'); builder.append("rewriteSketch.blocked.count=").append(rewriteSketchBlockedCount).append('\n'); @@ -548,47 +590,49 @@ private static int replacementPlanRuleCount(Map fields, String s .sum(); } - private static String replacementPlanFirstBlocker(GpuRuntimeIrOptimizationReport report) { + private static int proofFieldRuleCount(GpuRuntimeIrOptimizationReport report, String suffix) { + int total = 0; for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - String blocker = passReport.proofArtifact().fields().getOrDefault("replacementPlan.firstBlocker", "none"); - if (!blocker.isBlank() && !"none".equals(blocker)) { - return blocker; - } + total += proofFieldRuleCount(passReport.proofArtifact().fields(), suffix); } } - return "none"; - } - - private static int replacementPlanValidationCount(GpuRuntimeIrOptimizationReport report) { - return replacementPlanRuleCount(report, ".replacementPlan.validation.count"); + return total; } - private static int replacementPlanValidationValidCount(GpuRuntimeIrOptimizationReport report) { - return replacementPlanRuleCount(report, ".replacementPlan.validation.valid.count"); + private static int proofFieldRuleCount(Map fields, String suffix) { + return fields.entrySet().stream() + .filter(entry -> entry.getKey().startsWith("rule.")) + .filter(entry -> entry.getKey().endsWith(suffix)) + .mapToInt(entry -> parseInt(entry.getValue())) + .sum(); } - private static int replacementPlanValidationInvalidCount(GpuRuntimeIrOptimizationReport report) { + private static int proofFieldBlockedCount(GpuRuntimeIrOptimizationReport report, String aggregateKey, String ruleSuffix) { int total = 0; for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { Map fields = passReport.proofArtifact().fields(); - int passTotal = parseInt(fields.get("replacementPlan.validation.invalid.count")); - total += passTotal > 0 ? passTotal : replacementPlanRuleCount(fields, ".replacementPlan.validation.invalid.count"); + int passTotal = parseInt(fields.get(aggregateKey)); + total += passTotal > 0 ? passTotal : proofFieldRuleCount(fields, ruleSuffix); } } return total; } - private static String replacementPlanValidationFirstBlocker(GpuRuntimeIrOptimizationReport report) { + private static String proofFieldFirstBlocker( + GpuRuntimeIrOptimizationReport report, + String aggregateKey, + String ruleSuffix + ) { for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { Map fields = passReport.proofArtifact().fields(); - String blocker = fields.getOrDefault("replacementPlan.validation.firstBlocker", "none"); + String blocker = fields.getOrDefault(aggregateKey, "none"); if (!blocker.isBlank() && !"none".equals(blocker)) { return blocker; } - String ruleBlocker = firstRuleBlocker(fields, ".replacementPlan.validation.firstBlocker"); + String ruleBlocker = firstRuleBlocker(fields, ruleSuffix); if (!"none".equals(ruleBlocker)) { return ruleBlocker; } @@ -597,108 +641,133 @@ private static String replacementPlanValidationFirstBlocker(GpuRuntimeIrOptimiza return "none"; } - private static int rewriteVisitorCount(GpuRuntimeIrOptimizationReport report) { - return rewriteVisitorRuleCount(report, ".rewriteVisitor.count"); + private static ProofFieldSummary proofFieldSummary(GpuRuntimeIrOptimizationReport report, String fieldPrefix) { + return new ProofFieldSummary( + proofFieldRuleCount(report, "." + fieldPrefix + ".count"), + proofFieldRuleCount(report, "." + fieldPrefix + ".ready.count"), + proofFieldBlockedCount(report, fieldPrefix + ".blocked.count", "." + fieldPrefix + ".blocked.count"), + proofFieldFirstBlocker(report, fieldPrefix + ".firstBlocker", "." + fieldPrefix + ".firstBlocker") + ); } - private static int rewriteVisitorReadyCount(GpuRuntimeIrOptimizationReport report) { - return rewriteVisitorRuleCount(report, ".rewriteVisitor.ready.count"); + private static void appendProofFieldSummaryProperties( + StringBuilder builder, + String prefix, + String fieldPrefix, + int count, + int readyCount, + int blockedCount, + String firstBlocker + ) { + builder.append(prefix).append(fieldPrefix).append(".count=").append(count).append('\n'); + builder.append(prefix).append(fieldPrefix).append(".ready.count=").append(readyCount).append('\n'); + builder.append(prefix).append(fieldPrefix).append(".blocked.count=").append(blockedCount).append('\n'); + builder.append(prefix).append(fieldPrefix).append(".firstBlocker=").append(firstBlocker).append('\n'); } - private static int rewriteVisitorBlockedCount(GpuRuntimeIrOptimizationReport report) { - int total = 0; - for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { - if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - Map fields = passReport.proofArtifact().fields(); - int passTotal = parseInt(fields.get("rewriteVisitor.blocked.count")); - total += passTotal > 0 ? passTotal : rewriteVisitorRuleCount(fields, ".rewriteVisitor.blocked.count"); - } - } - return total; + private static void appendArtifactProofBindingProperties( + StringBuilder builder, + String prefix, + int count, + int readyCount, + int blockedCount, + String firstBlocker + ) { + appendProofFieldSummaryProperties( + builder, + prefix, + "artifactProofBinding", + count, + readyCount, + blockedCount, + firstBlocker + ); + builder.append(prefix).append("artifactProofBinding.bindingPreflightImplemented=true\n"); + builder.append(prefix).append("artifactProofBinding.proofBound=false\n"); + builder.append(prefix).append("artifactProofBinding.rollbackBound=false\n"); + builder.append(prefix).append("artifactProofBinding.approvalBound=false\n"); + appendArtifactBuildGuardProperties(builder, prefix, "artifactProofBinding"); + appendMutationGuardProperties(builder, prefix, "artifactProofBinding"); + } + + private static void appendArtifactSelectionProperties( + StringBuilder builder, + String prefix, + int count, + int readyCount, + int blockedCount, + String firstBlocker + ) { + appendProofFieldSummaryProperties( + builder, + prefix, + "artifactSelection", + count, + readyCount, + blockedCount, + firstBlocker + ); + builder.append(prefix).append("artifactSelection.selectionPreflightImplemented=true\n"); + builder.append(prefix).append("artifactSelection.productionGateRequired=").append(count > 0).append('\n'); + builder.append(prefix).append("artifactSelection.productionGateAccepted=false\n"); + builder.append(prefix).append("artifactSelection.mutationPolicyAllowed=false\n"); + builder.append(prefix).append("artifactSelection.selectionApplied=false\n"); + builder.append(prefix).append("artifactSelection.optimizedArtifactSelected=false\n"); + appendArtifactBuildGuardProperties(builder, prefix, "artifactSelection"); + appendMutationGuardProperties(builder, prefix, "artifactSelection"); } - private static int rewriteVisitorRuleCount(GpuRuntimeIrOptimizationReport report, String suffix) { - int total = 0; - for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { - if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - total += rewriteVisitorRuleCount(passReport.proofArtifact().fields(), suffix); - } - } - return total; + private static void appendArtifactBuildGuardProperties(StringBuilder builder, String prefix, String fieldPrefix) { + builder.append(prefix).append(fieldPrefix).append(".optimizedArtifactBuilt=false\n"); + builder.append(prefix).append(fieldPrefix).append(".transformedIrBuilt=false\n"); } - private static int rewriteVisitorRuleCount(Map fields, String suffix) { - return fields.entrySet().stream() - .filter(entry -> entry.getKey().startsWith("rule.")) - .filter(entry -> entry.getKey().endsWith(suffix)) - .mapToInt(entry -> parseInt(entry.getValue())) - .sum(); + private static void appendMutationGuardProperties(StringBuilder builder, String prefix, String fieldPrefix) { + builder.append(prefix).append(fieldPrefix).append(".mutationAllowed=false\n"); + builder.append(prefix).append(fieldPrefix).append(".selectedIrReplacement=false\n"); } - private static String rewriteVisitorFirstBlocker(GpuRuntimeIrOptimizationReport report) { + private static String replacementPlanFirstBlocker(GpuRuntimeIrOptimizationReport report) { for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - Map fields = passReport.proofArtifact().fields(); - String blocker = fields.getOrDefault("rewriteVisitor.firstBlocker", "none"); + String blocker = passReport.proofArtifact().fields().getOrDefault("replacementPlan.firstBlocker", "none"); if (!blocker.isBlank() && !"none".equals(blocker)) { return blocker; } - String ruleBlocker = firstRuleBlocker(fields, ".rewriteVisitor.firstBlocker"); - if (!"none".equals(ruleBlocker)) { - return ruleBlocker; - } } } return "none"; } - private static int replacementBlueprintCount(GpuRuntimeIrOptimizationReport report) { - return replacementBlueprintRuleCount(report, ".replacementBlueprint.count"); + private static int replacementPlanValidationCount(GpuRuntimeIrOptimizationReport report) { + return replacementPlanRuleCount(report, ".replacementPlan.validation.count"); } - private static int replacementBlueprintReadyCount(GpuRuntimeIrOptimizationReport report) { - return replacementBlueprintRuleCount(report, ".replacementBlueprint.ready.count"); + private static int replacementPlanValidationValidCount(GpuRuntimeIrOptimizationReport report) { + return replacementPlanRuleCount(report, ".replacementPlan.validation.valid.count"); } - private static int replacementBlueprintBlockedCount(GpuRuntimeIrOptimizationReport report) { + private static int replacementPlanValidationInvalidCount(GpuRuntimeIrOptimizationReport report) { int total = 0; for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { Map fields = passReport.proofArtifact().fields(); - int passTotal = parseInt(fields.get("replacementBlueprint.blocked.count")); - total += passTotal > 0 ? passTotal : replacementBlueprintRuleCount(fields, ".replacementBlueprint.blocked.count"); - } - } - return total; - } - - private static int replacementBlueprintRuleCount(GpuRuntimeIrOptimizationReport report, String suffix) { - int total = 0; - for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { - if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - total += replacementBlueprintRuleCount(passReport.proofArtifact().fields(), suffix); + int passTotal = parseInt(fields.get("replacementPlan.validation.invalid.count")); + total += passTotal > 0 ? passTotal : replacementPlanRuleCount(fields, ".replacementPlan.validation.invalid.count"); } } return total; } - private static int replacementBlueprintRuleCount(Map fields, String suffix) { - return fields.entrySet().stream() - .filter(entry -> entry.getKey().startsWith("rule.")) - .filter(entry -> entry.getKey().endsWith(suffix)) - .mapToInt(entry -> parseInt(entry.getValue())) - .sum(); - } - - private static String replacementBlueprintFirstBlocker(GpuRuntimeIrOptimizationReport report) { + private static String replacementPlanValidationFirstBlocker(GpuRuntimeIrOptimizationReport report) { for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { Map fields = passReport.proofArtifact().fields(); - String blocker = fields.getOrDefault("replacementBlueprint.firstBlocker", "none"); + String blocker = fields.getOrDefault("replacementPlan.validation.firstBlocker", "none"); if (!blocker.isBlank() && !"none".equals(blocker)) { return blocker; } - String ruleBlocker = firstRuleBlocker(fields, ".replacementBlueprint.firstBlocker"); + String ruleBlocker = firstRuleBlocker(fields, ".replacementPlan.validation.firstBlocker"); if (!"none".equals(ruleBlocker)) { return ruleBlocker; } @@ -707,389 +776,148 @@ private static String replacementBlueprintFirstBlocker(GpuRuntimeIrOptimizationR return "none"; } - private static int rewriteTransactionCount(GpuRuntimeIrOptimizationReport report) { - return rewriteTransactionRuleCount(report, ".rewriteTransaction.count"); + private static int rewriteVisitorCount(GpuRuntimeIrOptimizationReport report) { + return proofFieldRuleCount(report, ".rewriteVisitor.count"); } - private static int rewriteTransactionReadyCount(GpuRuntimeIrOptimizationReport report) { - return rewriteTransactionRuleCount(report, ".rewriteTransaction.ready.count"); + private static int rewriteVisitorReadyCount(GpuRuntimeIrOptimizationReport report) { + return proofFieldRuleCount(report, ".rewriteVisitor.ready.count"); } - private static int rewriteTransactionBlockedCount(GpuRuntimeIrOptimizationReport report) { - int total = 0; - for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { - if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - Map fields = passReport.proofArtifact().fields(); - int passTotal = parseInt(fields.get("rewriteTransaction.blocked.count")); - total += passTotal > 0 ? passTotal : rewriteTransactionRuleCount(fields, ".rewriteTransaction.blocked.count"); - } - } - return total; + private static int rewriteVisitorBlockedCount(GpuRuntimeIrOptimizationReport report) { + return proofFieldBlockedCount(report, "rewriteVisitor.blocked.count", ".rewriteVisitor.blocked.count"); } - private static int rewriteTransactionRuleCount(GpuRuntimeIrOptimizationReport report, String suffix) { - int total = 0; - for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { - if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - total += rewriteTransactionRuleCount(passReport.proofArtifact().fields(), suffix); - } - } - return total; + private static String rewriteVisitorFirstBlocker(GpuRuntimeIrOptimizationReport report) { + return proofFieldFirstBlocker(report, "rewriteVisitor.firstBlocker", ".rewriteVisitor.firstBlocker"); } - private static int rewriteTransactionRuleCount(Map fields, String suffix) { - return fields.entrySet().stream() - .filter(entry -> entry.getKey().startsWith("rule.")) - .filter(entry -> entry.getKey().endsWith(suffix)) - .mapToInt(entry -> parseInt(entry.getValue())) - .sum(); + private static int replacementBlueprintCount(GpuRuntimeIrOptimizationReport report) { + return proofFieldRuleCount(report, ".replacementBlueprint.count"); } - private static String rewriteTransactionFirstBlocker(GpuRuntimeIrOptimizationReport report) { - for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { - if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - Map fields = passReport.proofArtifact().fields(); - String blocker = fields.getOrDefault("rewriteTransaction.firstBlocker", "none"); - if (!blocker.isBlank() && !"none".equals(blocker)) { - return blocker; - } - String ruleBlocker = firstRuleBlocker(fields, ".rewriteTransaction.firstBlocker"); - if (!"none".equals(ruleBlocker)) { - return ruleBlocker; - } - } - } - return "none"; + private static int replacementBlueprintReadyCount(GpuRuntimeIrOptimizationReport report) { + return proofFieldRuleCount(report, ".replacementBlueprint.ready.count"); } - private static int nodeIdAllocationCount(GpuRuntimeIrOptimizationReport report) { - return nodeIdAllocationRuleCount(report, ".nodeIdAllocation.count"); + private static int replacementBlueprintBlockedCount(GpuRuntimeIrOptimizationReport report) { + return proofFieldBlockedCount(report, "replacementBlueprint.blocked.count", ".replacementBlueprint.blocked.count"); } - private static int nodeIdAllocationReadyCount(GpuRuntimeIrOptimizationReport report) { - return nodeIdAllocationRuleCount(report, ".nodeIdAllocation.ready.count"); + private static String replacementBlueprintFirstBlocker(GpuRuntimeIrOptimizationReport report) { + return proofFieldFirstBlocker(report, "replacementBlueprint.firstBlocker", ".replacementBlueprint.firstBlocker"); } - private static int nodeIdAllocationBlockedCount(GpuRuntimeIrOptimizationReport report) { - int total = 0; - for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { - if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - Map fields = passReport.proofArtifact().fields(); - int passTotal = parseInt(fields.get("nodeIdAllocation.blocked.count")); - total += passTotal > 0 ? passTotal : nodeIdAllocationRuleCount(fields, ".nodeIdAllocation.blocked.count"); - } - } - return total; + private static int rewriteTransactionCount(GpuRuntimeIrOptimizationReport report) { + return proofFieldRuleCount(report, ".rewriteTransaction.count"); } - private static int nodeIdAllocationRuleCount(GpuRuntimeIrOptimizationReport report, String suffix) { - int total = 0; - for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { - if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - total += nodeIdAllocationRuleCount(passReport.proofArtifact().fields(), suffix); - } - } - return total; + private static int rewriteTransactionReadyCount(GpuRuntimeIrOptimizationReport report) { + return proofFieldRuleCount(report, ".rewriteTransaction.ready.count"); } - private static int nodeIdAllocationRuleCount(Map fields, String suffix) { - return fields.entrySet().stream() - .filter(entry -> entry.getKey().startsWith("rule.")) - .filter(entry -> entry.getKey().endsWith(suffix)) - .mapToInt(entry -> parseInt(entry.getValue())) - .sum(); + private static int rewriteTransactionBlockedCount(GpuRuntimeIrOptimizationReport report) { + return proofFieldBlockedCount(report, "rewriteTransaction.blocked.count", ".rewriteTransaction.blocked.count"); } - private static String nodeIdAllocationFirstBlocker(GpuRuntimeIrOptimizationReport report) { - for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { - if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - Map fields = passReport.proofArtifact().fields(); - String blocker = fields.getOrDefault("nodeIdAllocation.firstBlocker", "none"); - if (!blocker.isBlank() && !"none".equals(blocker)) { - return blocker; - } - String ruleBlocker = firstRuleBlocker(fields, ".nodeIdAllocation.firstBlocker"); - if (!"none".equals(ruleBlocker)) { - return ruleBlocker; - } - } - } - return "none"; + private static String rewriteTransactionFirstBlocker(GpuRuntimeIrOptimizationReport report) { + return proofFieldFirstBlocker(report, "rewriteTransaction.firstBlocker", ".rewriteTransaction.firstBlocker"); } - private static int replacementNodeCount(GpuRuntimeIrOptimizationReport report) { - return replacementNodeRuleCount(report, ".replacementNode.count"); + private static int nodeIdAllocationCount(GpuRuntimeIrOptimizationReport report) { + return proofFieldRuleCount(report, ".nodeIdAllocation.count"); } - private static int replacementNodeReadyCount(GpuRuntimeIrOptimizationReport report) { - return replacementNodeRuleCount(report, ".replacementNode.ready.count"); + private static int nodeIdAllocationReadyCount(GpuRuntimeIrOptimizationReport report) { + return proofFieldRuleCount(report, ".nodeIdAllocation.ready.count"); } - private static int replacementNodeBlockedCount(GpuRuntimeIrOptimizationReport report) { - int total = 0; - for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { - if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - Map fields = passReport.proofArtifact().fields(); - int passTotal = parseInt(fields.get("replacementNode.blocked.count")); - total += passTotal > 0 ? passTotal : replacementNodeRuleCount(fields, ".replacementNode.blocked.count"); - } - } - return total; + private static int nodeIdAllocationBlockedCount(GpuRuntimeIrOptimizationReport report) { + return proofFieldBlockedCount(report, "nodeIdAllocation.blocked.count", ".nodeIdAllocation.blocked.count"); } - private static int replacementNodeRuleCount(GpuRuntimeIrOptimizationReport report, String suffix) { - int total = 0; - for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { - if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - total += replacementNodeRuleCount(passReport.proofArtifact().fields(), suffix); - } - } - return total; + private static String nodeIdAllocationFirstBlocker(GpuRuntimeIrOptimizationReport report) { + return proofFieldFirstBlocker(report, "nodeIdAllocation.firstBlocker", ".nodeIdAllocation.firstBlocker"); } - private static int replacementNodeRuleCount(Map fields, String suffix) { - return fields.entrySet().stream() - .filter(entry -> entry.getKey().startsWith("rule.")) - .filter(entry -> entry.getKey().endsWith(suffix)) - .mapToInt(entry -> parseInt(entry.getValue())) - .sum(); + private static int replacementNodeCount(GpuRuntimeIrOptimizationReport report) { + return proofFieldRuleCount(report, ".replacementNode.count"); } - private static String replacementNodeFirstBlocker(GpuRuntimeIrOptimizationReport report) { - for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { - if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - Map fields = passReport.proofArtifact().fields(); - String blocker = fields.getOrDefault("replacementNode.firstBlocker", "none"); - if (!blocker.isBlank() && !"none".equals(blocker)) { - return blocker; - } - String ruleBlocker = firstRuleBlocker(fields, ".replacementNode.firstBlocker"); - if (!"none".equals(ruleBlocker)) { - return ruleBlocker; - } - } - } - return "none"; + private static int replacementNodeReadyCount(GpuRuntimeIrOptimizationReport report) { + return proofFieldRuleCount(report, ".replacementNode.ready.count"); } - private static int graphPatchCount(GpuRuntimeIrOptimizationReport report) { - return graphPatchRuleCount(report, ".graphPatch.count"); + private static int replacementNodeBlockedCount(GpuRuntimeIrOptimizationReport report) { + return proofFieldBlockedCount(report, "replacementNode.blocked.count", ".replacementNode.blocked.count"); } - private static int graphPatchReadyCount(GpuRuntimeIrOptimizationReport report) { - return graphPatchRuleCount(report, ".graphPatch.ready.count"); + private static String replacementNodeFirstBlocker(GpuRuntimeIrOptimizationReport report) { + return proofFieldFirstBlocker(report, "replacementNode.firstBlocker", ".replacementNode.firstBlocker"); } - private static int graphPatchBlockedCount(GpuRuntimeIrOptimizationReport report) { - int total = 0; - for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { - if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - Map fields = passReport.proofArtifact().fields(); - int passTotal = parseInt(fields.get("graphPatch.blocked.count")); - total += passTotal > 0 ? passTotal : graphPatchRuleCount(fields, ".graphPatch.blocked.count"); - } - } - return total; + private static int graphPatchCount(GpuRuntimeIrOptimizationReport report) { + return proofFieldRuleCount(report, ".graphPatch.count"); } - private static int graphPatchRuleCount(GpuRuntimeIrOptimizationReport report, String suffix) { - int total = 0; - for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { - if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - total += graphPatchRuleCount(passReport.proofArtifact().fields(), suffix); - } - } - return total; + private static int graphPatchReadyCount(GpuRuntimeIrOptimizationReport report) { + return proofFieldRuleCount(report, ".graphPatch.ready.count"); } - private static int graphPatchRuleCount(Map fields, String suffix) { - return fields.entrySet().stream() - .filter(entry -> entry.getKey().startsWith("rule.")) - .filter(entry -> entry.getKey().endsWith(suffix)) - .mapToInt(entry -> parseInt(entry.getValue())) - .sum(); + private static int graphPatchBlockedCount(GpuRuntimeIrOptimizationReport report) { + return proofFieldBlockedCount(report, "graphPatch.blocked.count", ".graphPatch.blocked.count"); } private static String graphPatchFirstBlocker(GpuRuntimeIrOptimizationReport report) { - for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { - if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - Map fields = passReport.proofArtifact().fields(); - String blocker = fields.getOrDefault("graphPatch.firstBlocker", "none"); - if (!blocker.isBlank() && !"none".equals(blocker)) { - return blocker; - } - String ruleBlocker = firstRuleBlocker(fields, ".graphPatch.firstBlocker"); - if (!"none".equals(ruleBlocker)) { - return ruleBlocker; - } - } - } - return "none"; + return proofFieldFirstBlocker(report, "graphPatch.firstBlocker", ".graphPatch.firstBlocker"); } private static int transformedGraphCount(GpuRuntimeIrOptimizationReport report) { - return transformedGraphRuleCount(report, ".transformedGraph.count"); + return proofFieldRuleCount(report, ".transformedGraph.count"); } private static int transformedGraphReadyCount(GpuRuntimeIrOptimizationReport report) { - return transformedGraphRuleCount(report, ".transformedGraph.ready.count"); + return proofFieldRuleCount(report, ".transformedGraph.ready.count"); } private static int transformedGraphBlockedCount(GpuRuntimeIrOptimizationReport report) { - int total = 0; - for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { - if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - Map fields = passReport.proofArtifact().fields(); - int passTotal = parseInt(fields.get("transformedGraph.blocked.count")); - total += passTotal > 0 ? passTotal : transformedGraphRuleCount(fields, ".transformedGraph.blocked.count"); - } - } - return total; - } - - private static int transformedGraphRuleCount(GpuRuntimeIrOptimizationReport report, String suffix) { - int total = 0; - for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { - if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - total += transformedGraphRuleCount(passReport.proofArtifact().fields(), suffix); - } - } - return total; - } - - private static int transformedGraphRuleCount(Map fields, String suffix) { - return fields.entrySet().stream() - .filter(entry -> entry.getKey().startsWith("rule.")) - .filter(entry -> entry.getKey().endsWith(suffix)) - .mapToInt(entry -> parseInt(entry.getValue())) - .sum(); + return proofFieldBlockedCount(report, "transformedGraph.blocked.count", ".transformedGraph.blocked.count"); } private static String transformedGraphFirstBlocker(GpuRuntimeIrOptimizationReport report) { - for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { - if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - Map fields = passReport.proofArtifact().fields(); - String blocker = fields.getOrDefault("transformedGraph.firstBlocker", "none"); - if (!blocker.isBlank() && !"none".equals(blocker)) { - return blocker; - } - String ruleBlocker = firstRuleBlocker(fields, ".transformedGraph.firstBlocker"); - if (!"none".equals(ruleBlocker)) { - return ruleBlocker; - } - } - } - return "none"; + return proofFieldFirstBlocker(report, "transformedGraph.firstBlocker", ".transformedGraph.firstBlocker"); } private static int irArtifactEnvelopeCount(GpuRuntimeIrOptimizationReport report) { - return irArtifactEnvelopeRuleCount(report, ".irArtifactEnvelope.count"); + return proofFieldRuleCount(report, ".irArtifactEnvelope.count"); } private static int irArtifactEnvelopeReadyCount(GpuRuntimeIrOptimizationReport report) { - return irArtifactEnvelopeRuleCount(report, ".irArtifactEnvelope.ready.count"); + return proofFieldRuleCount(report, ".irArtifactEnvelope.ready.count"); } private static int irArtifactEnvelopeBlockedCount(GpuRuntimeIrOptimizationReport report) { - int total = 0; - for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { - if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - Map fields = passReport.proofArtifact().fields(); - int passTotal = parseInt(fields.get("irArtifactEnvelope.blocked.count")); - total += passTotal > 0 ? passTotal : irArtifactEnvelopeRuleCount(fields, ".irArtifactEnvelope.blocked.count"); - } - } - return total; - } - - private static int irArtifactEnvelopeRuleCount(GpuRuntimeIrOptimizationReport report, String suffix) { - int total = 0; - for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { - if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - total += irArtifactEnvelopeRuleCount(passReport.proofArtifact().fields(), suffix); - } - } - return total; - } - - private static int irArtifactEnvelopeRuleCount(Map fields, String suffix) { - return fields.entrySet().stream() - .filter(entry -> entry.getKey().startsWith("rule.")) - .filter(entry -> entry.getKey().endsWith(suffix)) - .mapToInt(entry -> parseInt(entry.getValue())) - .sum(); + return proofFieldBlockedCount(report, "irArtifactEnvelope.blocked.count", ".irArtifactEnvelope.blocked.count"); } private static String irArtifactEnvelopeFirstBlocker(GpuRuntimeIrOptimizationReport report) { - for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { - if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - Map fields = passReport.proofArtifact().fields(); - String blocker = fields.getOrDefault("irArtifactEnvelope.firstBlocker", "none"); - if (!blocker.isBlank() && !"none".equals(blocker)) { - return blocker; - } - String ruleBlocker = firstRuleBlocker(fields, ".irArtifactEnvelope.firstBlocker"); - if (!"none".equals(ruleBlocker)) { - return ruleBlocker; - } - } - } - return "none"; + return proofFieldFirstBlocker(report, "irArtifactEnvelope.firstBlocker", ".irArtifactEnvelope.firstBlocker"); } private static int rewriteSketchCount(GpuRuntimeIrOptimizationReport report) { - return rewriteSketchRuleCount(report, ".rewriteSketch.count"); + return proofFieldRuleCount(report, ".rewriteSketch.count"); } private static int rewriteSketchReadyCount(GpuRuntimeIrOptimizationReport report) { - return rewriteSketchRuleCount(report, ".rewriteSketch.ready.count"); + return proofFieldRuleCount(report, ".rewriteSketch.ready.count"); } private static int rewriteSketchBlockedCount(GpuRuntimeIrOptimizationReport report) { - int total = 0; - for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { - if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - Map fields = passReport.proofArtifact().fields(); - int passTotal = parseInt(fields.get("rewriteSketch.blocked.count")); - total += passTotal > 0 ? passTotal : rewriteSketchRuleCount(fields, ".rewriteSketch.blocked.count"); - } - } - return total; - } - - private static int rewriteSketchRuleCount(GpuRuntimeIrOptimizationReport report, String suffix) { - int total = 0; - for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { - if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - total += rewriteSketchRuleCount(passReport.proofArtifact().fields(), suffix); - } - } - return total; - } - - private static int rewriteSketchRuleCount(Map fields, String suffix) { - return fields.entrySet().stream() - .filter(entry -> entry.getKey().startsWith("rule.")) - .filter(entry -> entry.getKey().endsWith(suffix)) - .mapToInt(entry -> parseInt(entry.getValue())) - .sum(); + return proofFieldBlockedCount(report, "rewriteSketch.blocked.count", ".rewriteSketch.blocked.count"); } private static String rewriteSketchFirstBlocker(GpuRuntimeIrOptimizationReport report) { - for (GpuRuntimeIrOptimizationPassReport passReport : report.passReports()) { - if (!passReport.analysisOnly() && hasProofArtifact(passReport)) { - Map fields = passReport.proofArtifact().fields(); - String blocker = fields.getOrDefault("rewriteSketch.firstBlocker", "none"); - if (!blocker.isBlank() && !"none".equals(blocker)) { - return blocker; - } - String ruleBlocker = firstRuleBlocker(fields, ".rewriteSketch.firstBlocker"); - if (!"none".equals(ruleBlocker)) { - return ruleBlocker; - } - } - } - return "none"; + return proofFieldFirstBlocker(report, "rewriteSketch.firstBlocker", ".rewriteSketch.firstBlocker"); } private static int rewriteSketchConflictCount(GpuRuntimeIrOptimizationReport report) { @@ -1410,6 +1238,22 @@ private static String formatOptimizerRuleSummary(Map fields, String prefix) { String blocker = firstBlocker; - String validationBlocker = replacementPlanValidationFirstBlocker; - String nextValidationBlocker = fields.getOrDefault(prefix + ".replacementPlan.validation.firstBlocker", "none"); - if ("none".equals(validationBlocker) - && !nextValidationBlocker.isBlank() - && !"none".equals(nextValidationBlocker)) { - validationBlocker = nextValidationBlocker; - } - String sketchBlocker = rewriteSketchFirstBlocker; - String nextSketchBlocker = fields.getOrDefault(prefix + ".rewriteSketch.firstBlocker", "none"); - if ("none".equals(sketchBlocker) - && !nextSketchBlocker.isBlank() - && !"none".equals(nextSketchBlocker)) { - sketchBlocker = nextSketchBlocker; - } - String visitorBlocker = rewriteVisitorFirstBlocker; - String nextVisitorBlocker = fields.getOrDefault(prefix + ".rewriteVisitor.firstBlocker", "none"); - if ("none".equals(visitorBlocker) - && !nextVisitorBlocker.isBlank() - && !"none".equals(nextVisitorBlocker)) { - visitorBlocker = nextVisitorBlocker; - } - String blueprintBlocker = replacementBlueprintFirstBlocker; - String nextBlueprintBlocker = fields.getOrDefault(prefix + ".replacementBlueprint.firstBlocker", "none"); - if ("none".equals(blueprintBlocker) - && !nextBlueprintBlocker.isBlank() - && !"none".equals(nextBlueprintBlocker)) { - blueprintBlocker = nextBlueprintBlocker; - } - String transactionBlocker = rewriteTransactionFirstBlocker; - String nextTransactionBlocker = fields.getOrDefault(prefix + ".rewriteTransaction.firstBlocker", "none"); - if ("none".equals(transactionBlocker) - && !nextTransactionBlocker.isBlank() - && !"none".equals(nextTransactionBlocker)) { - transactionBlocker = nextTransactionBlocker; - } - String allocationBlocker = nodeIdAllocationFirstBlocker; - String nextAllocationBlocker = fields.getOrDefault(prefix + ".nodeIdAllocation.firstBlocker", "none"); - if ("none".equals(allocationBlocker) - && !nextAllocationBlocker.isBlank() - && !"none".equals(nextAllocationBlocker)) { - allocationBlocker = nextAllocationBlocker; - } - String replacementNodeBlocker = replacementNodeFirstBlocker; - String nextReplacementNodeBlocker = fields.getOrDefault(prefix + ".replacementNode.firstBlocker", "none"); - if ("none".equals(replacementNodeBlocker) - && !nextReplacementNodeBlocker.isBlank() - && !"none".equals(nextReplacementNodeBlocker)) { - replacementNodeBlocker = nextReplacementNodeBlocker; - } - String graphPatchBlocker = graphPatchFirstBlocker; - String nextGraphPatchBlocker = fields.getOrDefault(prefix + ".graphPatch.firstBlocker", "none"); - if ("none".equals(graphPatchBlocker) - && !nextGraphPatchBlocker.isBlank() - && !"none".equals(nextGraphPatchBlocker)) { - graphPatchBlocker = nextGraphPatchBlocker; - } - String transformedGraphBlocker = transformedGraphFirstBlocker; - String nextTransformedGraphBlocker = fields.getOrDefault(prefix + ".transformedGraph.firstBlocker", "none"); - if ("none".equals(transformedGraphBlocker) - && !nextTransformedGraphBlocker.isBlank() - && !"none".equals(nextTransformedGraphBlocker)) { - transformedGraphBlocker = nextTransformedGraphBlocker; - } - String irArtifactEnvelopeBlocker = irArtifactEnvelopeFirstBlocker; - String nextIrArtifactEnvelopeBlocker = fields.getOrDefault(prefix + ".irArtifactEnvelope.firstBlocker", "none"); - if ("none".equals(irArtifactEnvelopeBlocker) - && !nextIrArtifactEnvelopeBlocker.isBlank() - && !"none".equals(nextIrArtifactEnvelopeBlocker)) { - irArtifactEnvelopeBlocker = nextIrArtifactEnvelopeBlocker; - } + String validationBlocker = firstNonNone(replacementPlanValidationFirstBlocker, fields.getOrDefault(prefix + ".replacementPlan.validation.firstBlocker", "none")); + String sketchBlocker = firstNonNone(rewriteSketchFirstBlocker, fields.getOrDefault(prefix + ".rewriteSketch.firstBlocker", "none")); + String visitorBlocker = firstNonNone(rewriteVisitorFirstBlocker, fields.getOrDefault(prefix + ".rewriteVisitor.firstBlocker", "none")); + String blueprintBlocker = firstNonNone(replacementBlueprintFirstBlocker, fields.getOrDefault(prefix + ".replacementBlueprint.firstBlocker", "none")); + String transactionBlocker = firstNonNone(rewriteTransactionFirstBlocker, fields.getOrDefault(prefix + ".rewriteTransaction.firstBlocker", "none")); + String allocationBlocker = firstNonNone(nodeIdAllocationFirstBlocker, fields.getOrDefault(prefix + ".nodeIdAllocation.firstBlocker", "none")); + String replacementNodeBlocker = firstNonNone(replacementNodeFirstBlocker, fields.getOrDefault(prefix + ".replacementNode.firstBlocker", "none")); + String graphPatchBlocker = firstNonNone(graphPatchFirstBlocker, fields.getOrDefault(prefix + ".graphPatch.firstBlocker", "none")); + String transformedGraphBlocker = firstNonNone(transformedGraphFirstBlocker, fields.getOrDefault(prefix + ".transformedGraph.firstBlocker", "none")); + String irArtifactEnvelopeBlocker = firstNonNone(irArtifactEnvelopeFirstBlocker, fields.getOrDefault(prefix + ".irArtifactEnvelope.firstBlocker", "none")); + String artifactProofBindingBlocker = firstNonNone(artifactProofBindingFirstBlocker, fields.getOrDefault(prefix + ".artifactProofBinding.firstBlocker", "none")); + String artifactSelectionBlocker = firstNonNone(artifactSelectionFirstBlocker, fields.getOrDefault(prefix + ".artifactSelection.firstBlocker", "none")); String selectionStatus = firstNonDefault( rewriteSelectionStatus, fields.getOrDefault(prefix + ".rewriteSelection.status", "not-required"), @@ -1988,6 +1814,14 @@ private OptimizerRuleEvidence add(Map fields, String prefix) { irArtifactEnvelopeReadyCount + parseInt(fields.get(prefix + ".irArtifactEnvelope.ready.count")), irArtifactEnvelopeBlockedCount + parseInt(fields.get(prefix + ".irArtifactEnvelope.blocked.count")), irArtifactEnvelopeBlocker, + artifactProofBindingCount + parseInt(fields.get(prefix + ".artifactProofBinding.count")), + artifactProofBindingReadyCount + parseInt(fields.get(prefix + ".artifactProofBinding.ready.count")), + artifactProofBindingBlockedCount + parseInt(fields.get(prefix + ".artifactProofBinding.blocked.count")), + artifactProofBindingBlocker, + artifactSelectionCount + parseInt(fields.get(prefix + ".artifactSelection.count")), + artifactSelectionReadyCount + parseInt(fields.get(prefix + ".artifactSelection.ready.count")), + artifactSelectionBlockedCount + parseInt(fields.get(prefix + ".artifactSelection.blocked.count")), + artifactSelectionBlocker, rewriteSketchCount + parseInt(fields.get(prefix + ".rewriteSketch.count")), rewriteSketchReadyCount + parseInt(fields.get(prefix + ".rewriteSketch.ready.count")), rewriteSketchBlockedCount + parseInt(fields.get(prefix + ".rewriteSketch.blocked.count")), @@ -2012,6 +1846,10 @@ private static String firstNonDefault(String current, String next, String defaul return defaultValue; } + private static String firstNonNone(String current, String next) { + return firstNonDefault(current, next, "none"); + } + private static String firstKnown(String current, String next) { if (current != null && !current.isBlank() && !"unknown".equals(current)) { return current; diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatterTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatterTest.java index e7ebe744..b53ee6b1 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatterTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendSourcePromotionWorkloadGateFormatterTest.java @@ -155,6 +155,96 @@ void carriesRuntimeExtensionParticipationIntoWorkloadGateEvidence() throws IOExc assertEquals("backend-compiler-feedback", gate.getProperty("kernel.0.runtimeExtensionParticipation.source.1.name")); } + @Test + void keepsRuntimeOptimizerDriftDefaultsAlignedWhenArtifactIsMissing() throws IOException { + Path gateFile = tempDir.resolve("backend-source-promotion-workload-gate.properties"); + + Properties gate = loadProperties(GpuBackendSourcePromotionWorkloadGateFormatter.merge( + gateFile, + "kernel-missing-drift.cl", + blockedGateProperties("backend source promotion remains blocked for fixture"), + "", + runtimeIrHandoffProperties( + "optimized", + "true", + "false", + "none", + "false", + "optimized runtime IR remains diagnostic-only" + ), + runtimeProductionMutationSafetyProperties( + "disabled", + "false", + "blocked", + "true", + "original", + "production mutation remains blocked" + ), + "", + "" + )); + + assertEquals("not-recorded", gate.getProperty("kernel.0.runtimeOptimizerDrift.status")); + assertEquals("0", gate.getProperty("kernel.0.runtimeOptimizerDrift.pass.count")); + assertEquals("0", gate.getProperty("kernel.0.runtimeOptimizerDrift.proofArtifact.count")); + assertEquals("0", gate.getProperty("kernel.0.runtimeOptimizerDrift.artifactProofBinding.count")); + assertEquals("false", gate.getProperty("kernel.0.runtimeOptimizerDrift.artifactProofBinding.proofBound")); + assertEquals("0", gate.getProperty("kernel.0.runtimeOptimizerDrift.artifactSelection.count")); + assertEquals("false", gate.getProperty("kernel.0.runtimeOptimizerDrift.artifactSelection.productionGateAccepted")); + assertEquals("false", gate.getProperty("kernel.0.runtimeOptimizerDrift.artifactSelection.optimizedArtifactSelected")); + assertEquals("not-required", gate.getProperty("kernel.0.runtimeOptimizerDrift.rewriteSelection.status")); + assertEquals("no-proof-candidates", gate.getProperty("kernel.0.runtimeOptimizerDrift.rewriteProof.firstBlocker")); + assertEquals("true", gate.getProperty("kernel.0.runtimeOptimizerDrift.rewriteReviewPackage.manualReviewOnly")); + assertEquals("none", gate.getProperty("kernel.0.runtimeOptimizerDrift.fallbackDecision")); + assertEquals("optimized", gate.getProperty("kernel.0.runtimeOptimizerDrift.selectedRuntimeIrStage")); + assertEquals("false", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizedIrRejected")); + assertEquals("blocked", gate.getProperty("kernel.0.runtimeOptimizerDrift.productionGateStatus")); + assertEquals("true", gate.getProperty("kernel.0.runtimeOptimizerDrift.productionProfileRequested")); + } + + @Test + void keepsRuntimeOptimizerDriftRecordedDefaultsAlignedWhenArtifactIsPartial() throws IOException { + Path gateFile = tempDir.resolve("backend-source-promotion-workload-gate.properties"); + + Properties gate = loadProperties(GpuBackendSourcePromotionWorkloadGateFormatter.merge( + gateFile, + "kernel-partial-drift.cl", + blockedGateProperties("backend source promotion remains blocked for fixture"), + "", + "", + "", + "", + String.join("\n", + "optimizerFamily.count=2", + "optimizerFamily.promotionReady.count=1", + "optimizerFamily.summary=fixture-family-summary", + "selectedProfile=diagnostic-only", + "productionGateStatus=blocked", + "productionProfileRequested=true", + "" + ) + )); + + assertEquals("recorded", gate.getProperty("kernel.0.runtimeOptimizerDrift.status")); + assertEquals("unknown", gate.getProperty("kernel.0.runtimeOptimizerDrift.pass.count")); + assertEquals("unknown", gate.getProperty("kernel.0.runtimeOptimizerDrift.pass.applied.count")); + assertEquals("unknown", gate.getProperty("kernel.0.runtimeOptimizerDrift.proofArtifact.count")); + assertEquals("0", gate.getProperty("kernel.0.runtimeOptimizerDrift.replacementPlan.complete.count")); + assertEquals("0", gate.getProperty("kernel.0.runtimeOptimizerDrift.artifactProofBinding.count")); + assertEquals("false", gate.getProperty("kernel.0.runtimeOptimizerDrift.artifactProofBinding.proofBound")); + assertEquals("0", gate.getProperty("kernel.0.runtimeOptimizerDrift.artifactSelection.count")); + assertEquals("false", gate.getProperty("kernel.0.runtimeOptimizerDrift.artifactSelection.productionGateAccepted")); + assertEquals("not-required", gate.getProperty("kernel.0.runtimeOptimizerDrift.rewriteSelection.status")); + assertEquals("no-proof-candidates", gate.getProperty("kernel.0.runtimeOptimizerDrift.rewriteProof.firstBlocker")); + assertEquals("2", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizerFamily.count")); + assertEquals("1", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizerFamily.promotionReady.count")); + assertEquals("fixture-family-summary", gate.getProperty("kernel.0.runtimeOptimizerDrift.optimizerFamily.summary")); + assertEquals("unknown", gate.getProperty("kernel.0.runtimeOptimizerDrift.fallbackDecision")); + assertEquals("diagnostic-only", gate.getProperty("kernel.0.runtimeOptimizerDrift.selectedProfile")); + assertEquals("blocked", gate.getProperty("kernel.0.runtimeOptimizerDrift.productionGateStatus")); + assertEquals("true", gate.getProperty("kernel.0.runtimeOptimizerDrift.productionProfileRequested")); + } + @Test void updatesExistingKernelResourceInsteadOfDuplicatingIt() throws IOException { Path gateFile = tempDir.resolve("backend-source-promotion-workload-gate.properties"); diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java index 2d646668..6e7cd961 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java @@ -2053,6 +2053,14 @@ void optimizerDriftArtifactCapturesReadOnlyReplacementPlanTelemetry() { Map.entry("irArtifactEnvelope.ready.count", "5"), Map.entry("irArtifactEnvelope.blocked.count", "1"), Map.entry("irArtifactEnvelope.firstBlocker", "replacement-plan-root-missing"), + Map.entry("artifactProofBinding.count", "6"), + Map.entry("artifactProofBinding.ready.count", "0"), + Map.entry("artifactProofBinding.blocked.count", "6"), + Map.entry("artifactProofBinding.firstBlocker", "replacement-plan-root-missing"), + Map.entry("artifactSelection.count", "6"), + Map.entry("artifactSelection.ready.count", "0"), + Map.entry("artifactSelection.blocked.count", "6"), + Map.entry("artifactSelection.firstBlocker", "replacement-plan-root-missing"), Map.entry("rewriteSketch.conflict.count", "1"), Map.entry("rewriteSketch.conflict.firstBlocker", "rewrite-sketch-covered-node-overlap"), Map.entry("rewriteSelection.status", "blocked"), @@ -2119,6 +2127,14 @@ void optimizerDriftArtifactCapturesReadOnlyReplacementPlanTelemetry() { Map.entry("rule.0.irArtifactEnvelope.ready.count", "2"), Map.entry("rule.0.irArtifactEnvelope.blocked.count", "1"), Map.entry("rule.0.irArtifactEnvelope.firstBlocker", "replacement-plan-root-missing"), + Map.entry("rule.0.artifactProofBinding.count", "3"), + Map.entry("rule.0.artifactProofBinding.ready.count", "0"), + Map.entry("rule.0.artifactProofBinding.blocked.count", "3"), + Map.entry("rule.0.artifactProofBinding.firstBlocker", "replacement-plan-root-missing"), + Map.entry("rule.0.artifactSelection.count", "3"), + Map.entry("rule.0.artifactSelection.ready.count", "0"), + Map.entry("rule.0.artifactSelection.blocked.count", "3"), + Map.entry("rule.0.artifactSelection.firstBlocker", "replacement-plan-root-missing"), Map.entry("rule.0.rewriteSelection.status", "blocked"), Map.entry("rule.0.rewriteSelection.firstBlocker", "replacement-plan-root-missing"), Map.entry("rule.0.rewriteProof.status", "blocked"), @@ -2183,6 +2199,14 @@ void optimizerDriftArtifactCapturesReadOnlyReplacementPlanTelemetry() { Map.entry("rule.1.irArtifactEnvelope.ready.count", "3"), Map.entry("rule.1.irArtifactEnvelope.blocked.count", "0"), Map.entry("rule.1.irArtifactEnvelope.firstBlocker", "none"), + Map.entry("rule.1.artifactProofBinding.count", "3"), + Map.entry("rule.1.artifactProofBinding.ready.count", "0"), + Map.entry("rule.1.artifactProofBinding.blocked.count", "3"), + Map.entry("rule.1.artifactProofBinding.firstBlocker", "runtime-equivalence-payload-missing"), + Map.entry("rule.1.artifactSelection.count", "3"), + Map.entry("rule.1.artifactSelection.ready.count", "0"), + Map.entry("rule.1.artifactSelection.blocked.count", "3"), + Map.entry("rule.1.artifactSelection.firstBlocker", "runtime-equivalence-payload-missing"), Map.entry("rule.1.rewriteSelection.status", "blocked"), Map.entry("rule.1.rewriteSelection.firstBlocker", "rewrite-builder-not-implemented"), Map.entry("rule.1.rewriteProof.status", "blocked"), @@ -2295,6 +2319,26 @@ void optimizerDriftArtifactCapturesReadOnlyReplacementPlanTelemetry() { assertTrue(drift.contains("irArtifactEnvelope.transformedGraphBuilt=false")); assertTrue(drift.contains("irArtifactEnvelope.transformedIrBuilt=false")); assertTrue(drift.contains("irArtifactEnvelope.selectedIrReplacement=false")); + assertTrue(drift.contains("artifactProofBinding.count=6")); + assertTrue(drift.contains("artifactProofBinding.ready.count=0")); + assertTrue(drift.contains("artifactProofBinding.blocked.count=6")); + assertTrue(drift.contains("artifactProofBinding.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("artifactProofBinding.bindingPreflightImplemented=true")); + assertTrue(drift.contains("artifactProofBinding.proofBound=false")); + assertTrue(drift.contains("artifactProofBinding.rollbackBound=false")); + assertTrue(drift.contains("artifactProofBinding.approvalBound=false")); + assertTrue(drift.contains("artifactProofBinding.optimizedArtifactBuilt=false")); + assertTrue(drift.contains("artifactProofBinding.selectedIrReplacement=false")); + assertTrue(drift.contains("artifactSelection.count=6")); + assertTrue(drift.contains("artifactSelection.ready.count=0")); + assertTrue(drift.contains("artifactSelection.blocked.count=6")); + assertTrue(drift.contains("artifactSelection.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("artifactSelection.selectionPreflightImplemented=true")); + assertTrue(drift.contains("artifactSelection.productionGateRequired=true")); + assertTrue(drift.contains("artifactSelection.productionGateAccepted=false")); + assertTrue(drift.contains("artifactSelection.selectionApplied=false")); + assertTrue(drift.contains("artifactSelection.optimizedArtifactSelected=false")); + assertTrue(drift.contains("artifactSelection.selectedIrReplacement=false")); assertTrue(drift.contains("rewriteSketch.count=6")); assertTrue(drift.contains("rewriteSketch.ready.count=5")); assertTrue(drift.contains("rewriteSketch.blocked.count=1")); @@ -2387,6 +2431,18 @@ void optimizerDriftArtifactCapturesReadOnlyReplacementPlanTelemetry() { assertTrue(drift.contains("optimizerRule.0.irArtifactEnvelope.artifactEnvelopeBuilt=false")); assertTrue(drift.contains("optimizerRule.0.irArtifactEnvelope.optimizedArtifactBuilt=false")); assertTrue(drift.contains("optimizerRule.0.irArtifactEnvelope.selectedIrReplacement=false")); + assertTrue(drift.contains("optimizerRule.0.artifactProofBinding.count=3")); + assertTrue(drift.contains("optimizerRule.0.artifactProofBinding.ready.count=0")); + assertTrue(drift.contains("optimizerRule.0.artifactProofBinding.blocked.count=3")); + assertTrue(drift.contains("optimizerRule.0.artifactProofBinding.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("optimizerRule.0.artifactProofBinding.proofBound=false")); + assertTrue(drift.contains("optimizerRule.0.artifactProofBinding.selectedIrReplacement=false")); + assertTrue(drift.contains("optimizerRule.0.artifactSelection.count=3")); + assertTrue(drift.contains("optimizerRule.0.artifactSelection.ready.count=0")); + assertTrue(drift.contains("optimizerRule.0.artifactSelection.blocked.count=3")); + assertTrue(drift.contains("optimizerRule.0.artifactSelection.firstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("optimizerRule.0.artifactSelection.selectionApplied=false")); + assertTrue(drift.contains("optimizerRule.0.artifactSelection.selectedIrReplacement=false")); assertTrue(drift.contains("optimizerRule.0.rewriteSketch.count=3")); assertTrue(drift.contains("optimizerRule.0.rewriteSketch.ready.count=2")); assertTrue(drift.contains("optimizerRule.0.rewriteSketch.blocked.count=1")); @@ -2410,6 +2466,8 @@ void optimizerDriftArtifactCapturesReadOnlyReplacementPlanTelemetry() { assertTrue(drift.contains("graphPatches=3, readyGraphPatches=2, blockedGraphPatches=1, graphPatchFirstBlocker=replacement-plan-root-missing")); assertTrue(drift.contains("transformedGraphs=3, readyTransformedGraphs=2, blockedTransformedGraphs=1, transformedGraphFirstBlocker=replacement-plan-root-missing")); assertTrue(drift.contains("irArtifactEnvelopes=3, readyIrArtifactEnvelopes=2, blockedIrArtifactEnvelopes=1, irArtifactEnvelopeFirstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("artifactProofBindings=3, readyArtifactProofBindings=0, blockedArtifactProofBindings=3, artifactProofBindingFirstBlocker=replacement-plan-root-missing")); + assertTrue(drift.contains("artifactSelections=3, readyArtifactSelections=0, blockedArtifactSelections=3, artifactSelectionFirstBlocker=replacement-plan-root-missing")); assertTrue(drift.contains("rewriteSketches=3, readySketches=2, blockedSketches=1, rewriteSketchFirstBlocker=replacement-plan-root-missing, rewriteSelectionStatus=blocked, rewriteSelectionFirstBlocker=replacement-plan-root-missing, rewriteProofStatus=blocked, rewriteProofFirstBlocker=runtime-equivalence-payload-missing")); assertTrue(drift.contains("optimizerFamily.count=1")); assertTrue(drift.contains("optimizerFamily.summary=peephole[passes=1, acceptedProof=0, blockingProof=1, rolledBack=0, failed=0, promotionReady=false]")); diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistryTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistryTest.java index bd1db202..12029fae 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistryTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistryTest.java @@ -712,6 +712,69 @@ void diagnosticPeepholePassFindsTypedMadFmaCandidateWithoutMutatingIr() { ); assertEquals("false", passReport.proofArtifact().fields().get("rule.0.irArtifactEnvelope.0.artifactEnvelopeBuilt")); assertEquals("false", passReport.proofArtifact().fields().get("rule.0.irArtifactEnvelope.0.selectedIrReplacement")); + assertEquals("1", passReport.proofArtifact().fields().get("artifactProofBinding.count")); + assertEquals("0", passReport.proofArtifact().fields().get("artifactProofBinding.ready.count")); + assertEquals("1", passReport.proofArtifact().fields().get("artifactProofBinding.blocked.count")); + assertEquals("rewrite-builder-not-implemented", passReport.proofArtifact().fields().get("artifactProofBinding.firstBlocker")); + assertEquals("true", passReport.proofArtifact().fields().get("artifactProofBinding.bindingPreflightImplemented")); + assertEquals("false", passReport.proofArtifact().fields().get("artifactProofBinding.proofBound")); + assertEquals("false", passReport.proofArtifact().fields().get("artifactProofBinding.rollbackBound")); + assertEquals("false", passReport.proofArtifact().fields().get("artifactProofBinding.approvalBound")); + assertEquals("false", passReport.proofArtifact().fields().get("artifactProofBinding.optimizedArtifactBuilt")); + assertEquals("false", passReport.proofArtifact().fields().get("artifactProofBinding.selectedIrReplacement")); + assertEquals("blocked", passReport.proofArtifact().fields().get("rule.0.artifactProofBinding.0.status")); + assertEquals("rewrite-builder-not-implemented", passReport.proofArtifact().fields().get("rule.0.artifactProofBinding.0.firstBlocker")); + assertEquals(passReport.originalIrIdentity(), passReport.proofArtifact().fields().get("rule.0.artifactProofBinding.0.originalIrIdentity")); + assertEquals("not-built", passReport.proofArtifact().fields().get("rule.0.artifactProofBinding.0.optimizedArtifactIdentity")); + assertEquals("runtime-equivalence-required", passReport.proofArtifact().fields().get("rule.0.artifactProofBinding.0.proofAnchor")); + assertEquals("original-ir", passReport.proofArtifact().fields().get("rule.0.artifactProofBinding.0.rollbackAnchor")); + assertEquals("blocked", passReport.proofArtifact().fields().get("rule.0.artifactProofBinding.0.proofStatus")); + assertEquals("rewrite-builder-not-implemented", passReport.proofArtifact().fields().get("rule.0.artifactProofBinding.0.proofFirstBlocker")); + assertEquals("blocked", passReport.proofArtifact().fields().get("rule.0.artifactProofBinding.0.reviewPackageStatus")); + assertEquals("runtime-equivalence-payload-missing", passReport.proofArtifact().fields().get("rule.0.artifactProofBinding.0.reviewPackageFirstBlocker")); + assertEquals("true", passReport.proofArtifact().fields().get("rule.0.artifactProofBinding.0.artifactEnvelopeReady")); + assertEquals("true", passReport.proofArtifact().fields().get("rule.0.artifactProofBinding.0.proofRequired")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.artifactProofBinding.0.proofAccepted")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.artifactProofBinding.0.runtimeEquivalencePayload.complete")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.artifactProofBinding.0.rollbackEvidence.present")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.artifactProofBinding.0.rollbackClean")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.artifactProofBinding.0.approvalAccepted")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.artifactProofBinding.0.reviewPackageComplete")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.artifactProofBinding.0.bindingReady")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.artifactProofBinding.0.proofBound")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.artifactProofBinding.0.rollbackBound")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.artifactProofBinding.0.approvalBound")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.artifactProofBinding.0.optimizedArtifactBuilt")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.artifactProofBinding.0.transformedIrBuilt")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.artifactProofBinding.0.selectedIrReplacement")); + assertEquals("1", passReport.proofArtifact().fields().get("artifactSelection.count")); + assertEquals("0", passReport.proofArtifact().fields().get("artifactSelection.ready.count")); + assertEquals("1", passReport.proofArtifact().fields().get("artifactSelection.blocked.count")); + assertEquals("rewrite-builder-not-implemented", passReport.proofArtifact().fields().get("artifactSelection.firstBlocker")); + assertEquals("true", passReport.proofArtifact().fields().get("artifactSelection.selectionPreflightImplemented")); + assertEquals("true", passReport.proofArtifact().fields().get("artifactSelection.productionGateRequired")); + assertEquals("false", passReport.proofArtifact().fields().get("artifactSelection.productionGateAccepted")); + assertEquals("false", passReport.proofArtifact().fields().get("artifactSelection.selectionApplied")); + assertEquals("false", passReport.proofArtifact().fields().get("artifactSelection.optimizedArtifactSelected")); + assertEquals("false", passReport.proofArtifact().fields().get("artifactSelection.selectedIrReplacement")); + assertEquals("blocked", passReport.proofArtifact().fields().get("rule.0.artifactSelection.0.status")); + assertEquals("rewrite-builder-not-implemented", passReport.proofArtifact().fields().get("rule.0.artifactSelection.0.firstBlocker")); + assertEquals(passReport.originalIrIdentity(), passReport.proofArtifact().fields().get("rule.0.artifactSelection.0.originalIrIdentity")); + assertEquals("not-built", passReport.proofArtifact().fields().get("rule.0.artifactSelection.0.optimizedArtifactIdentity")); + assertEquals("blocked", passReport.proofArtifact().fields().get("rule.0.artifactSelection.0.proofBindingStatus")); + assertEquals("rewrite-builder-not-implemented", passReport.proofArtifact().fields().get("rule.0.artifactSelection.0.proofBindingFirstBlocker")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.artifactSelection.0.proofBindingReady")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.artifactSelection.0.proofBound")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.artifactSelection.0.rollbackBound")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.artifactSelection.0.approvalBound")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.artifactSelection.0.optimizedArtifactBuilt")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.artifactSelection.0.transformedIrBuilt")); + assertEquals("true", passReport.proofArtifact().fields().get("rule.0.artifactSelection.0.productionGateRequired")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.artifactSelection.0.productionGateAccepted")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.artifactSelection.0.mutationPolicyAllowed")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.artifactSelection.0.selectionReady")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.artifactSelection.0.selectionApplied")); + assertEquals("false", passReport.proofArtifact().fields().get("rule.0.artifactSelection.0.optimizedArtifactSelected")); assertEquals("1", passReport.proofArtifact().fields().get("rewriteSketch.count")); assertEquals("1", passReport.proofArtifact().fields().get("rewriteSketch.ready.count")); assertEquals("0", passReport.proofArtifact().fields().get("rewriteSketch.blocked.count")); @@ -797,6 +860,28 @@ void diagnosticPeepholePassFindsTypedMadFmaCandidateWithoutMutatingIr() { assertTrue(passReport.toLine().contains("structural rewrite and proof emission are not implemented")); } + @Test + void diagnosticPeepholePreflightsKeepProductionEnablingFieldsDisabled() { + GpuRuntimeIrOptimizerRegistry registry = GpuRuntimeIrOptimizerRegistry.ofPasses( + List.of(new GpuRuntimeIrPeepholePass()) + ); + for (IrGpuArtifact artifact : List.of( + fastMathTypedArtifact(), + fastMathPartialMadFmaTypedArtifact(), + fastMathClampTypedArtifact(), + fastMathStepTypedArtifact(), + fastMathDotTypedArtifact(), + fastMathMixTypedArtifact() + )) { + GpuRuntimeIrOptimizationReport report = registry.optimizeWithReport(request(artifact)); + GpuRuntimeIrOptimizationPassReport passReport = report.passReports().get(0); + + assertSame(artifact, report.artifact().orElseThrow()); + assertEquals(GpuRuntimeIrOptimizationOutcome.SKIPPED, passReport.outcome()); + assertNoProductionEnablingPreflightFields(passReport.proofArtifact().fields()); + } + } + @Test void diagnosticPeepholePassReportsPartialMadFmaPlanWithoutCandidate() { IrGpuArtifact artifact = fastMathPartialMadFmaTypedArtifact(); @@ -1190,6 +1275,42 @@ public String extensionId() { assertEquals("false", fields.get("rule.0.irArtifactEnvelope.0.artifactEnvelopeBuilt")); assertEquals("false", fields.get("irArtifactEnvelope.optimizedArtifactBuilt")); assertEquals("false", fields.get("irArtifactEnvelope.selectedIrReplacement")); + assertEquals("1", fields.get("artifactProofBinding.count")); + assertEquals("0", fields.get("artifactProofBinding.ready.count")); + assertEquals("1", fields.get("artifactProofBinding.blocked.count")); + assertEquals("replacement-plan-root-missing", fields.get("artifactProofBinding.firstBlocker")); + assertEquals("blocked", fields.get("rule.0.artifactProofBinding.0.status")); + assertEquals("replacement-plan-root-missing", fields.get("rule.0.artifactProofBinding.0.firstBlocker")); + assertEquals("false", fields.get("rule.0.artifactProofBinding.0.artifactEnvelopeReady")); + assertEquals("not-required", fields.get("rule.0.artifactProofBinding.0.proofStatus")); + assertEquals("replacement-plan-root-missing", fields.get("rule.0.artifactProofBinding.0.proofFirstBlocker")); + assertEquals("not-required", fields.get("rule.0.artifactProofBinding.0.reviewPackageStatus")); + assertEquals("replacement-plan-root-missing", fields.get("rule.0.artifactProofBinding.0.reviewPackageFirstBlocker")); + assertEquals("false", fields.get("rule.0.artifactProofBinding.0.proofRequired")); + assertEquals("false", fields.get("rule.0.artifactProofBinding.0.bindingReady")); + assertEquals("false", fields.get("rule.0.artifactProofBinding.0.proofBound")); + assertEquals("false", fields.get("rule.0.artifactProofBinding.0.optimizedArtifactBuilt")); + assertEquals("false", fields.get("rule.0.artifactProofBinding.0.transformedIrBuilt")); + assertEquals("false", fields.get("artifactProofBinding.optimizedArtifactBuilt")); + assertEquals("false", fields.get("artifactProofBinding.selectedIrReplacement")); + assertEquals("1", fields.get("artifactSelection.count")); + assertEquals("0", fields.get("artifactSelection.ready.count")); + assertEquals("1", fields.get("artifactSelection.blocked.count")); + assertEquals("replacement-plan-root-missing", fields.get("artifactSelection.firstBlocker")); + assertEquals("blocked", fields.get("rule.0.artifactSelection.0.status")); + assertEquals("replacement-plan-root-missing", fields.get("rule.0.artifactSelection.0.firstBlocker")); + assertEquals("blocked", fields.get("rule.0.artifactSelection.0.proofBindingStatus")); + assertEquals("replacement-plan-root-missing", fields.get("rule.0.artifactSelection.0.proofBindingFirstBlocker")); + assertEquals("false", fields.get("rule.0.artifactSelection.0.proofBindingReady")); + assertEquals("false", fields.get("rule.0.artifactSelection.0.optimizedArtifactBuilt")); + assertEquals("false", fields.get("rule.0.artifactSelection.0.transformedIrBuilt")); + assertEquals("true", fields.get("rule.0.artifactSelection.0.productionGateRequired")); + assertEquals("false", fields.get("rule.0.artifactSelection.0.productionGateAccepted")); + assertEquals("false", fields.get("rule.0.artifactSelection.0.mutationPolicyAllowed")); + assertEquals("false", fields.get("rule.0.artifactSelection.0.selectionReady")); + assertEquals("false", fields.get("artifactSelection.selectionApplied")); + assertEquals("false", fields.get("artifactSelection.optimizedArtifactSelected")); + assertEquals("false", fields.get("artifactSelection.selectedIrReplacement")); assertEquals("0", fields.get("rewriteSketch.ready.count")); assertEquals("1", fields.get("rewriteSketch.blocked.count")); assertEquals("replacement-plan-root-missing", fields.get("rewriteSketch.firstBlocker")); @@ -1536,6 +1657,47 @@ private static GpuRuntimeIrOptimizationRequest request(IrGpuArtifact artifact) { ); } + private static void assertNoProductionEnablingPreflightFields(Map fields) { + List forbiddenTrueSuffixes = List.of( + ".replacementBuilderImplemented", + ".nodeIdsReserved", + ".nodeIdAllocatorApplied", + ".replacementNodeBuilt", + ".graphPatchApplied", + ".graphRewriteImplemented", + ".transformedGraphBuilt", + ".artifactEnvelopeBuilt", + ".optimizedArtifactBuilt", + ".transformedIrBuilt", + ".rewriteBuilderImplemented", + ".conflictResolutionImplemented", + ".runtimeEquivalenceProven", + ".proofAccepted", + ".runtimeEquivalencePayload.present", + ".runtimeEquivalencePayload.complete", + ".rollbackEvidence.present", + ".rollbackClean", + ".approvalAccepted", + ".reviewAccepted", + ".proofBound", + ".rollbackBound", + ".approvalBound", + ".bindingReady", + ".productionGateAccepted", + ".mutationPolicyAllowed", + ".selectionApplied", + ".optimizedArtifactSelected", + ".selectionReady", + ".mutationAllowed", + ".selectedIrReplacement" + ); + for (Map.Entry entry : fields.entrySet()) { + boolean forbidden = forbiddenTrueSuffixes.stream().anyMatch(entry.getKey()::endsWith); + assertFalse(forbidden && "true".equals(entry.getValue()), + () -> "production-enabling preflight field unexpectedly true: " + entry.getKey()); + } + } + private static GpuKernelDescriptor descriptor() { return new GpuKernelDescriptor( "kernel", diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeArtifactBoundaryPreflightTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeArtifactBoundaryPreflightTest.java new file mode 100644 index 00000000..244da634 --- /dev/null +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholeArtifactBoundaryPreflightTest.java @@ -0,0 +1,209 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +class GpuRuntimeIrPeepholeArtifactBoundaryPreflightTest { + + @Test + void artifactProofBindingPreflightBlocksWhenEnvelopeIsMissing() { + GpuRuntimeIrPeepholeArtifactProofBindingPreflight binding = + GpuRuntimeIrPeepholeArtifactProofBindingPreflight.from(null, null, null); + Map fields = binding.fields("binding"); + + assertEquals("blocked", binding.status()); + assertEquals("envelope-missing", binding.firstBlocker()); + assertEquals("rule:unknown", fields.get("binding.ruleId")); + assertEquals("not-built", fields.get("binding.optimizedArtifactIdentity")); + assertEquals("not-built", fields.get("binding.envelopeKey")); + assertEquals("runtime-equivalence-required", fields.get("binding.proofAnchor")); + assertEquals("original-ir", fields.get("binding.rollbackAnchor")); + assertEquals("not-required", fields.get("binding.proofStatus")); + assertEquals("envelope-missing", fields.get("binding.proofFirstBlocker")); + assertEquals("not-required", fields.get("binding.reviewPackageStatus")); + assertEquals("envelope-missing", fields.get("binding.reviewPackageFirstBlocker")); + assertEquals("false", fields.get("binding.artifactEnvelopeReady")); + assertEquals("false", fields.get("binding.proofRequired")); + assertEquals("false", fields.get("binding.bindingReady")); + assertFailClosedBindingFields(fields, "binding"); + } + + @Test + void artifactProofBindingPreflightBlocksReadyEnvelopeWithoutRequiredProof() { + GpuRuntimeIrPeepholeArtifactProofBindingPreflight binding = + GpuRuntimeIrPeepholeArtifactProofBindingPreflight.from(readyEnvelope(), null, null); + Map fields = binding.fields("binding"); + + assertEquals("blocked", binding.status()); + assertEquals("proof-not-required-for-envelope", binding.firstBlocker()); + assertEquals("true", fields.get("binding.artifactEnvelopeReady")); + assertEquals("not-required", fields.get("binding.proofStatus")); + assertEquals("no-proof-candidates", fields.get("binding.proofFirstBlocker")); + assertEquals("not-required", fields.get("binding.reviewPackageStatus")); + assertEquals("no-review-candidates", fields.get("binding.reviewPackageFirstBlocker")); + assertEquals("false", fields.get("binding.proofRequired")); + assertEquals("false", fields.get("binding.bindingReady")); + assertFailClosedBindingFields(fields, "binding"); + } + + @Test + void artifactSelectionPreflightBlocksWhenProofBindingIsMissing() { + GpuRuntimeIrPeepholeOptimizedArtifactSelectionPreflight selection = + GpuRuntimeIrPeepholeOptimizedArtifactSelectionPreflight.from(null); + Map fields = selection.fields("selection"); + + assertEquals("blocked", selection.status()); + assertEquals("artifact-proof-binding-missing", selection.firstBlocker()); + assertEquals("rule:unknown", fields.get("selection.ruleId")); + assertEquals("not-built", fields.get("selection.optimizedArtifactIdentity")); + assertEquals("blocked", fields.get("selection.proofBindingStatus")); + assertEquals("artifact-proof-binding-missing", fields.get("selection.proofBindingFirstBlocker")); + assertEquals("false", fields.get("selection.proofBindingReady")); + assertEquals("true", fields.get("selection.productionGateRequired")); + assertEquals("false", fields.get("selection.selectionReady")); + assertFailClosedSelectionFields(fields, "selection"); + } + + @Test + void artifactSelectionPreflightStillBlocksReadyBindingUntilArtifactsAreBoundAndBuilt() { + GpuRuntimeIrPeepholeArtifactProofBindingPreflight binding = + GpuRuntimeIrPeepholeArtifactProofBindingPreflight.from( + readyEnvelope(), + acceptedProof(), + completeReviewPackage() + ); + GpuRuntimeIrPeepholeOptimizedArtifactSelectionPreflight selection = + GpuRuntimeIrPeepholeOptimizedArtifactSelectionPreflight.from(binding); + Map fields = selection.fields("selection"); + + assertEquals("binding-ready", binding.status()); + assertEquals("none", binding.firstBlocker()); + assertEquals("true", binding.fields("binding").get("binding.bindingReady")); + assertFalse(binding.proofBound()); + assertFalse(binding.optimizedArtifactBuilt()); + + assertEquals("blocked", selection.status()); + assertEquals("proof-not-bound", selection.firstBlocker()); + assertEquals("binding-ready", fields.get("selection.proofBindingStatus")); + assertEquals("none", fields.get("selection.proofBindingFirstBlocker")); + assertEquals("true", fields.get("selection.proofBindingReady")); + assertEquals("true", fields.get("selection.productionGateRequired")); + assertEquals("false", fields.get("selection.selectionReady")); + assertFailClosedSelectionFields(fields, "selection"); + } + + private static GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight readyEnvelope() { + return new GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight( + "madFma", + "kernel", + 1, + "mad-fma", + "irgpu:sha256:original", + "not-built", + "not-built", + "materialization-key", + "envelope-key", + "runtime-equivalence-required", + "original-ir", + "6", + List.of(1), + List.of(1, 2), + List.of(3, 4, 5), + List.of(6), + true, + true, + true, + true, + true, + "artifact-envelope-ready", + "none", + true, + false, + false, + false, + false, + false, + false, + false, + false + ); + } + + private static GpuRuntimeIrPeepholeRewriteProofReadiness acceptedProof() { + return new GpuRuntimeIrPeepholeRewriteProofReadiness( + "peephole", + 1, + "proof-ready", + "none", + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false, + false, + "irgpu:sha256:original", + "not-built" + ); + } + + private static GpuRuntimeIrPeepholeRewriteReviewPackage completeReviewPackage() { + return new GpuRuntimeIrPeepholeRewriteReviewPackage( + "peephole", + 1, + 0, + "ready-for-manual-review", + "none", + true, + true, + true, + true, + true, + true, + true, + true, + false, + false, + false, + true, + "irgpu:sha256:original", + "not-built" + ); + } + + private static void assertFailClosedBindingFields(Map fields, String prefix) { + assertEquals("true", fields.get(prefix + ".bindingPreflightImplemented")); + assertEquals("false", fields.get(prefix + ".proofBound")); + assertEquals("false", fields.get(prefix + ".rollbackBound")); + assertEquals("false", fields.get(prefix + ".approvalBound")); + assertEquals("false", fields.get(prefix + ".optimizedArtifactBuilt")); + assertEquals("false", fields.get(prefix + ".transformedIrBuilt")); + assertEquals("false", fields.get(prefix + ".mutationAllowed")); + assertEquals("false", fields.get(prefix + ".selectedIrReplacement")); + } + + private static void assertFailClosedSelectionFields(Map fields, String prefix) { + assertEquals("true", fields.get(prefix + ".selectionPreflightImplemented")); + assertEquals("false", fields.get(prefix + ".proofBound")); + assertEquals("false", fields.get(prefix + ".rollbackBound")); + assertEquals("false", fields.get(prefix + ".approvalBound")); + assertEquals("false", fields.get(prefix + ".optimizedArtifactBuilt")); + assertEquals("false", fields.get(prefix + ".transformedIrBuilt")); + assertEquals("false", fields.get(prefix + ".productionGateAccepted")); + assertEquals("false", fields.get(prefix + ".mutationPolicyAllowed")); + assertEquals("false", fields.get(prefix + ".selectionApplied")); + assertEquals("false", fields.get(prefix + ".optimizedArtifactSelected")); + assertEquals("false", fields.get(prefix + ".mutationAllowed")); + assertEquals("false", fields.get(prefix + ".selectedIrReplacement")); + } +} From e153fadad3648e013d8980e4f3663789e2f622af Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Mon, 13 Jul 2026 16:13:28 +0300 Subject: [PATCH 25/32] wip: IR-Optimizator entry point --- README.md | 8 + ROADMAP.md | 9 +- docs/IR-Optimizer.md | 14 +- docs/Runtime-Guide.md | 4 +- docs/Validation-and-Operations.md | 9 +- examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md | 64 +++++ examples-app/build.gradle | 11 + .../examples/OptimizationJournalExample.java | 168 +++++++++++ .../IrGpuGeneratedArtifactParityTest.java | 2 +- .../OptimizationJournalExampleTest.java | 47 +++ .../GpuIrOptimizationSandwichReport.java | 23 ++ .../GpuIrOptimizationSandwichRunner.java | 29 ++ .../GpuIrOptimizedArtifactCandidate.java | 132 +++++++++ ...puIrOptimizedArtifactCandidateBuilder.java | 81 ++++++ .../GpuIrProposalRuntimeBridgePass.java | 17 ++ .../GpuIrOptimizationSandwichRunnerTest.java | 105 +++++++ .../GpuIrProposalRuntimeBridgePassTest.java | 90 ++++++ .../GpuRuntimeCompileArtifactDumper.java | 162 +++++++++++ .../GpuRuntimeCompileArtifactSnapshot.java | 90 +++++- .../opencl/OpenClGpuRuntimeBackend.java | 74 ++++- ...enClRuntimeIrOptimizerEvidenceSummary.java | 267 +++++++++++++++++- ...untimeIrOptimizerEvidenceValidatorCli.java | 64 +++++ .../GpuRuntimeCompileArtifactDumperTest.java | 44 ++- .../opencl/OpenClGpuRuntimeBackendTest.java | 61 ++++ ...RuntimeIrOptimizerEvidenceSummaryTest.java | 47 ++- ...meIrOptimizerEvidenceValidatorCliTest.java | 40 +++ .../opencl/OpenClValidationReportTest.java | 8 +- 27 files changed, 1625 insertions(+), 45 deletions(-) create mode 100644 examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md create mode 100644 examples-app/src/main/java/net/sixik/ga_utils/examples/OptimizationJournalExample.java create mode 100644 examples-app/src/test/java/net/sixik/ga_utils/examples/OptimizationJournalExampleTest.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizedArtifactCandidate.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizedArtifactCandidateBuilder.java diff --git a/README.md b/README.md index 88df0d14..866c956e 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,14 @@ processor/build/reports/opencl/ Start with `validation-report.md` when checking a run. +Run the optional IR optimizer journal example: + +```powershell +.\gradlew.bat :examples-app:runOptimizationJournalExample --console=plain +``` + +The example is documented in [examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md](examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md) and shows how to opt into the optimizer module while dumping original and optimized IR artifacts for review. + ## Project Layout - `processor` - annotation processor, compiler, OpenCL emitter, runtime, launchers, tests, and validation buckets. diff --git a/ROADMAP.md b/ROADMAP.md index 216f50bc..73562e9e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -607,7 +607,8 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i - [x] Allow third-party optimizer and peephole passes without core forks. External libraries can contribute full `GpuRuntimeIrOptimizationPass` implementations or typed-body `GpuRuntimeIrPeepholeRule` implementations through ServiceLoader. `GpuRuntimeIrPeepholeRuleRegistry` provides deterministic ordering, duplicate rule-id and extension-id rejection, phase/capability/permission validation, advisory failure isolation, and production-profile fail-closed behavior. Rule reports preserve rule id/version, extension id/version, method, candidate/proposal counts, proof status, diagnostics, and custom fields. The built-in matcher set now shares `GpuRuntimeIrTypedNodeGraph` for read-only typed-node traversal, emits `GpuRuntimeIrPeepholeReplacementPlanValidation` evidence, and produces mutation-free `GpuRuntimeIrPeepholeRewriteSketch` records so future structural rewrites have a fail-closed graph/plan/sketch boundary before any rewrite builder exists. Drift artifacts, workload gates, OpenCL validation reports/history, and production-promotion explainability now surface plan-validation and rewrite-sketch counts/first blockers as evidence-only telemetry. The current pass remains diagnostic/proposal-only: rules cannot bypass fast-math policy, proof requirements, production authorization, rollback, runtime-equivalence, or final IR selection gates. - [x] Add optional approval-bound IR optimizer modules. - `ir-optimizer` and `ir-vendor-optimizer` now exist as optional modules that keep `GpuIrValidationProvider` strictly read-only and expose optimization only through separate `MUTATION_PROPOSAL` provider contracts. The backend-neutral runtime bridge records proposal/provider evidence without loading vendor providers by default, while vendor-specific providers must opt into `GpuIrVendorOptimizationProposalProvider` / `GpuIrVendorOptimizationProposalRegistry`. Optimizer proposal evidence now includes approval-template status: real immutable rewrites can emit pending templates under `META-INF/javatogpu/ir-optimization-approvals/`, while preview/no-change proposals record `not-applicable` blockers. Approval artifacts are bound to optimizer id/version, original and optimized `IrGpu` identities, backend target, optimization profile, proof verdict, and rollback requirement; they remain manual-review-only and do not authorize production mutation or selected-IR replacement by themselves. Runtime OpenCL validation now surfaces this evidence through `runtime-ir-optimizer-evidence.properties` and the `Runtime IR Optimizer Evidence` report section, including approval-template pending/not-applicable counts. + `ir-optimizer` and `ir-vendor-optimizer` now exist as optional modules that keep `GpuIrValidationProvider` strictly read-only and expose optimization only through separate `MUTATION_PROPOSAL` provider contracts. The backend-neutral runtime bridge records proposal/provider evidence without loading vendor providers by default, while vendor-specific providers must opt into `GpuIrVendorOptimizationProposalProvider` / `GpuIrVendorOptimizationProposalRegistry`. Optimizer proposal evidence now includes approval-template status: real immutable rewrites can emit pending templates under `META-INF/javatogpu/ir-optimization-approvals/`, while preview/no-change proposals record `not-applicable` blockers. Approval artifacts are bound to optimizer id/version, original and optimized `IrGpu` identities, backend target, optimization profile, proof verdict, and rollback requirement; they remain manual-review-only and do not authorize production mutation or selected-IR replacement by themselves. Runtime OpenCL validation now surfaces this evidence through `runtime-ir-optimizer-evidence.properties` and the `Runtime IR Optimizer Evidence` report section, including approval-template pending/not-applicable counts and optimized-artifact candidate status/blockers. + `GpuIrOptimizedArtifactCandidateBuilder` now adds the first fail-closed optimized-artifact candidate envelope above the validation sandwich. It records original/optimized IR identities, validation verdict, proof presence, rollback requirement, mutation policy, and selection blockers, while keeping `selectionReady=false`, `selectionApplied=false`, and `selectedIrReplacement=false`; the default runtime bridge still keeps the original IR selected unless an explicit future selection gate accepts mutation. Runtime artifact dumping now lifts those fields into top-level `optimizedArtifactCandidate.*` evidence, OpenCL Markdown reports show candidate status and selection blockers per kernel, and `validateOpenClRuntimeIrOptimizerEvidence` rejects candidate selection or selected-IR replacement regressions. A bridge-to-dumper regression now proves the real `GpuIrProposalRuntimeBridgePass` candidate envelope reaches `runtime-ir-optimizer-evidence.properties` and passes the fail-closed validator. - [x] Add the third-party backend/device policy foundation. `GpuRuntimeDevicePolicy` extensions can contribute deterministic ranking adjustments, candidate rejection, capability facts, vendor quirks, compile-option validation, and diagnostics without taking ownership of final device selection. `GpuRuntimeDevicePolicyRegistry` loads the built-in backend-compatibility policy plus ServiceLoader extensions, rejects duplicate policy/extension ids, validates the device-selection phase/capability/read-only permission contract, isolates advisory failures, fails closed in production profiles, and keeps final scoring, rejection precedence, tie-breaking, and blocker selection in core. `GpuBackendLowerer` now participates in the shared extension metadata contract through stable backend-lowerer id/version/capability/phase/permission fields. - [x] Add third-party backend compiler feedback providers. @@ -646,7 +647,7 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i - [x] Preserve debug/source maps through runtime compilation. `IrGpu` method bodies now carry source-location metadata, manifests serialize/parse it with backward-compatible defaults, and runtime compile snapshots preserve the source anchors alongside original/optimized IR and lowered backend artifacts. - [x] Add tooling to dump `IrGpu`, optimized `IrGpu`, and backend output. - `GpuRuntimeCompileArtifactDumper` now turns runtime compile snapshots into comparable text artifacts for original `IrGpu`, optimized `IrGpu`, selected runtime `IrGpu`, lowered backend source, compile logs, runtime validation evidence, source anchors, invalidation stamps, and the explicit `runtime-ir-handoff.properties` contract that explains which IR artifact is handed to backend lowering and why production IR source selection remains accepted, review-only, or blocked. + `GpuRuntimeCompileArtifactDumper` now turns runtime compile snapshots into comparable text artifacts for original `IrGpu`, optimized `IrGpu`, selected runtime `IrGpu`, before/after lowered backend source, selected lowered backend source, compile logs, runtime validation evidence, source anchors, invalidation stamps, and the explicit `runtime-ir-handoff.properties` contract that explains which IR artifact is handed to backend lowering and why production IR source selection remains accepted, review-only, or blocked. `javatogpu.opencl.runtimeCompileArtifactDirectory` now enables the same per-kernel artifact bundle outside the operational workload-gate report path, making `original.irgpu.properties`, `optimized.irgpu.properties`, `original.backend.opencl-c`, `optimized.backend.opencl-c`, and the selected generated OpenCL backend source `backend.opencl-c` easy to inspect locally without enabling production mutation or selected-IR replacement. #### I3.8 Migration plan from current OpenCL build output @@ -678,7 +679,7 @@ Current rule: `ir-validation` remains strictly read-only. Optimizer modules may - [x] Add an immutable IR rewrite contract. `GpuIrOptimizationProposal` now enforces the immutable rewrite boundary directly: `PROPOSED` decisions must carry a distinct optimized artifact instance with a different stable `IrGpu` identity, while unchanged or preview-only work must use `NO_CHANGE` / proposal evidence instead. Proposal requests carry the original artifact plus policy/context, and proposals preserve optimizer id/version, diagnostics, proof metadata, rollback reason, original identity, and optimized identity. This keeps validation, diffing, cache identity, approval templates, and production gates comparing original versus optimized artifacts instead of trusting in-place mutation. - [x] Add the validation sandwich as the default optimizer safety model. - `GpuIrOptimizationSandwichRunner` now enforces the default optimizer safety flow: validate the original artifact, run one immutable proposal provider, validate the optimized artifact when present, then either keep the original, emit proposal-only evidence, select the optimized artifact only when mutation is explicitly allowed, or roll back on invalid optimized IR. The report records original/optimized validation results, final status, selected artifact, diagnostics, and rollback requirement. Existing tests cover no-change proposals, proposal-only mode, explicit mutation selection, invalid optimized rollback, invalid original short-circuiting, malformed proposal rejection, and ServiceLoader discovery. + `GpuIrOptimizationSandwichRunner` now enforces the default optimizer safety flow: validate the original artifact, run one immutable proposal provider, validate the optimized artifact when present, build a fail-closed optimized-artifact candidate envelope, then either keep the original, emit proposal-only evidence, select the optimized artifact only when mutation is explicitly allowed, or roll back on invalid optimized IR. The report records original/optimized validation results, optional optimized-artifact candidate evidence, final status, selected artifact, diagnostics, and rollback requirement. Existing tests cover no-change proposals, proposal-only mode, candidate-envelope guardrails, explicit mutation selection, invalid optimized rollback, invalid original short-circuiting, malformed proposal rejection, and ServiceLoader discovery. - [x] Define backend-neutral optimizer policy controls. `GpuIrOptimizationPolicy` now provides the backend-neutral optimizer control surface for optimizer profile, optimization level, proposal-only mode, mutation permission, fast-math permission, vendor adaptation, register-pressure splitting, rollback requirement, and proof requirement. `GpuIrOptimizationProposalRequest` preserves its existing constructors while exposing the normalized policy view and serializing policy fields into context metadata, so proposal providers and runtime adapters can consume one shared policy contract without depending on OpenCL-only flags or runtime-local prototype details. - [x] Add first backend-neutral optimizer families only after the contract is stable. @@ -692,7 +693,7 @@ Current rule: `ir-validation` remains strictly read-only. Optimizer modules may The same runtime evidence now includes `previewReadiness.*` aggregate fields over constant folding, safe-local CSE, and typed dead-code cleanup. The aggregate records overall status, recorded/candidate/blocked family counts, and a per-family status summary (`not-recorded`, `no-candidates`, `candidates-recorded`, `blocked-by-proof`, or `ready-for-runtime-equivalence-review`) so CI can track optimizer-family readiness without enabling production mutation or selected-IR replacement. `runtimeEquivalenceReview.*` now sits above `previewReadiness.*` as a fail-closed review gate. It records whether preview families are eligible for runtime-equivalence review, whether review evidence is required, the first blocker, the family summary, and explicitly disabled production mutation / selected-IR replacement so review readiness cannot be confused with production optimization enablement. `reviewPackage.*` now sits above runtime-equivalence review as the manual-review bundle boundary. It records package status, requirement/completeness, first blocker, proposal-pass count, pending approval count, original/optimized IR and proof-summary requirements, and repeats manual-review-only plus disabled production mutation / selected-IR replacement guardrails so review packaging remains evidence-only. - `validateOpenClRuntimeIrOptimizerEvidence` now validates those raw runtime evidence files in the OpenCL operational/NVIDIA routines, failing CI if review-package guardrails disappear, if production mutation or selected-IR replacement becomes enabled, or if a required package is marked complete/missing a blocker before an activation design exists. + `validateOpenClRuntimeIrOptimizerEvidence` now validates those raw runtime evidence files in the OpenCL operational/NVIDIA routines, failing CI if optimized-artifact candidate or review-package guardrails disappear, if production mutation or selected-IR replacement becomes enabled, if candidate selection is applied, or if a required package is marked complete/missing a blocker before an activation design exists. - [x] Keep vendor optimization as a separate optional provider layer. `ir-vendor-optimizer` now exists as a separate optional module skeleton. It contributes vendor proposals only through `GpuIrVendorOptimizationProposalProvider` and `GpuIrVendorOptimizationProposalRegistry`, while the default runtime bridge keeps loading only backend-neutral proposal providers. Future vendor modules may consume target device profile, backend compiler feedback, resource metrics, driver quirks, launch advisories, and promotion evidence, but missing modules, missing matching device evidence, or disabled vendor adaptation must leave backend-neutral IR selection unchanged. - [x] Gate production optimizer selection through approval-bound artifacts. diff --git a/docs/IR-Optimizer.md b/docs/IR-Optimizer.md index 52f672e9..5fa4ebf7 100644 --- a/docs/IR-Optimizer.md +++ b/docs/IR-Optimizer.md @@ -2,7 +2,13 @@ The optional IR optimizer module is the future backend-neutral transform layer for `IrGpu` artifacts. -The current module is intentionally a skeleton. It contributes one ServiceLoader-discovered no-op pass that records optimizer participation without changing the selected IR. This keeps the default runtime behavior safe while establishing the module, packaging, and report boundary for future optimization families. +The current module is intentionally proposal-first. It contributes ServiceLoader-discovered providers for no-op participation, conservative backend-neutral proposals, and preview-only proof surfaces without changing the selected IR. This keeps the default runtime behavior safe while establishing the module, packaging, and report boundary for future optimization families. + +## First Alpha Scope + +The first alpha is an evidence and contract boundary, not a production rewrite release. It includes the optional `ir-optimizer` / `ir-vendor-optimizer` module split, immutable proposal contracts, the validation sandwich, preview-only optimizer families, approval-template metadata, optimized-artifact candidate envelopes, and OpenCL evidence/report/validator guardrails. + +It intentionally excludes runtime IR selection, selected-IR replacement, production mutation, real vendor rewrites, and any automatic choice of optimized artifacts. Those remain future gates above runtime-equivalence, rollback, approval, and production-promotion evidence. ## Contract @@ -42,6 +48,8 @@ The optimizer contract is intentionally split into proposal and selection: If mutation is disabled, a valid optimized artifact is reported as `PROPOSAL_ONLY` and the original IR remains selected. If post-validation fails, the runner reports `OPTIMIZED_INVALID_ROLLED_BACK` and keeps the original IR. +`GpuIrOptimizedArtifactCandidateBuilder` now materializes a separate optimized-artifact candidate envelope after optimized-artifact validation. The envelope records original/optimized IR identities, the deterministic candidate key, validation verdict, proof presence, rollback requirement, mutation policy, and selection blockers. It deliberately keeps `selectionReady=false`, `selectionApplied=false`, and `selectedIrReplacement=false`; final runtime IR selection remains a separate gate even when a candidate artifact exists. + ## Policy Controls `GpuIrOptimizationPolicy` is the backend-neutral control surface for optimizer providers and runtime adapters. It currently covers: @@ -68,6 +76,10 @@ The default bridge is intentionally proposal-only. It can surface canonicalizati Runtime artifact dumping records bridge/provider evidence in `runtime-ir-optimizer-evidence.properties`. The artifact is filtered by the stable `javatogpu.ir-optimizer` provider prefix and includes pass counts, outcomes, proposal-only/selected counts, original/transformed IR identities, proof artifact fields, and diagnostics. This is an evidence surface only; it does not authorize optimized IR selection or production mutation by itself. +When a provider proposes an optimized artifact, bridge proof fields include `optimizedArtifactCandidate.*` metadata from the fail-closed candidate envelope. These fields make candidate identity, validation result, rollback requirement, mutation policy, and selection blockers visible without marking runtime selection as applied. + +The runtime artifact also lifts those proof fields into top-level `optimizedArtifactCandidate.*` aggregate evidence: candidate status/counts, ready/blocked counts, first blockers, selection-ready/applied counts, selected-IR replacement counts, mutation-policy count, and explicit disabled selection/replacement guardrails. The generated OpenCL Markdown report shows candidate status and selection blockers per kernel, and `validateOpenClRuntimeIrOptimizerEvidence` fails if candidate selection or selected-IR replacement becomes enabled before the future selection gate exists. + Runtime peephole/InstCombine proof is also kept read-only. `GpuRuntimeIrPeepholeReplacementPlan` records future structural replacement intent, including root node, covered node ids, input node ids, replacement kind, completeness, and first blocker. `GpuRuntimeIrTypedNodeGraph` is the shared read-only traversal view for typed peephole rules, and `GpuRuntimeIrPeepholeReplacementPlanValidation` checks each emitted plan against that graph before it is surfaced. `GpuRuntimeIrPeepholeTypedRewriteVisitor` is the shared traversal preflight above validated plans: it records deterministic visit order, visitor-ready/blocked counts, first visitor blocker, `visitorImplemented=true`, and disabled replacement-builder / transformed-IR / mutation / selected-IR flags. `GpuRuntimeIrPeepholeReplacementBlueprint` sits above visitor-ready plans as a read-only target-node contract: it records intended intrinsic-call node kind, target operation, argument node ids/roles, blueprint-ready/blocked counts, first blueprint blocker, and disabled replacement-builder / transformed-IR / mutation / selected-IR flags. `GpuRuntimeIrPeepholeRewriteTransactionPreflight` then describes the future graph transaction shape: replaced root ids, removed covered ids, retained input ids, planned added-node count, `plannedAddedNodeIds=not-allocated`, and disabled node-id allocator / graph-rewrite / transformed-IR / mutation / selected-IR flags. `GpuRuntimeIrPeepholeNodeIdAllocationPreflight` sits above transaction-ready plans as a diagnostic-only allocation preview: it records current graph max node id, deterministic candidate node ids, allocation-ready/blocked counts, first blocker, and disabled id reservation / allocator application / graph-rewrite / transformed-IR / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeReplacementNodePreflight` then pairs that candidate id with the intrinsic-call blueprint as a diagnostic-only replacement-node construction preview, keeping `replacementNodeBuilt=false`, `replacementBuilderImplemented=false`, and graph-rewrite / transformed-IR / mutation / selected-IR disabled. `GpuRuntimeIrPeepholeGraphPatchPreflight` records the next read-only patch preview: replacement node id, replaced/removed/retained/inserted node sets, graph-patch ready/blocked counts, first blocker, and disabled graph-patch application / graph-rewrite / transformed-IR / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeTransformedGraphPreflight` adds the materialization boundary above that patch: original IR identity, deterministic materialization key, `transformedGraphIdentity=not-built`, materialization-ready/blocked counts, first blocker, and disabled transformed-graph build / transformed-IR / graph-patch application / graph-rewrite / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeRewriteSketch` remains the mutation-free rewrite skeleton: a complete, valid replacement plan can become `sketch-ready`, but the sketch still records `rewriteBuilderImplemented=false`, `mutationAllowed=false`, and `selectedIrReplacement=false`; runtime equivalence and approval remain required. `GpuRuntimeIrPeepholeRewriteSelectionReadiness` now sits above ready/blocked sketches as a read-only selection preflight: it reports sketch counts, conflict counts, status, first blocker, missing rewrite-builder/conflict-resolution/proof/approval gates, and disabled mutation/selection flags without choosing any optimized IR. `GpuRuntimeIrPeepholeRewriteProofReadiness` then records the next fail-closed proof boundary: proof required/accepted state, runtime-equivalence payload presence/completeness, rollback evidence, approval acceptance, original IR identity, absent transformed IR identity, and disabled mutation / selected-IR replacement. `GpuRuntimeIrPeepholeRewriteReviewPackage` sits above proof readiness as the manual-review package boundary: it records required/complete state, conflict count, proof acceptance, runtime-equivalence payload, rollback, approval, manual-review-only state, and disabled mutation / selection / selected-IR replacement. The pass emits aggregate `rewriteVisitor.*`, `replacementBlueprint.*`, `rewriteTransaction.*`, `nodeIdAllocation.*`, `replacementNode.*`, `graphPatch.*`, `transformedGraph.*`, `rewriteSelection.*`, `rewriteProof.*`, and `rewriteReviewPackage.*` plus matching per-rule fields, so CI can identify which matcher family is blocked by graph validation, traversal, blueprint construction, transaction shaping, id-allocation preview, replacement-node preview, graph-patch preview, transformed-graph materialization, conflict resolution, missing runtime-equivalence payloads, missing rollback evidence, missing approval, or incomplete review packaging. Validation fields fail closed on missing roots, uncovered roots, or missing covered/input nodes; visitor fields prove only original-node traversal; blueprint fields describe only a future intrinsic-call shape; transaction fields describe only planned replace/remove/retain/add effects; allocation fields preview candidate ids only and keep `nodeIdsReserved=false` and `nodeIdAllocatorApplied=false`; replacement-node fields preview the synthetic node shape only and keep `replacementNodeBuilt=false`; graph-patch fields preview the future diff only and keep `graphPatchApplied=false`; transformed-graph fields preview materialization only and keep `transformedGraphBuilt=false`. They still do not reserve node ids, build replacement nodes, apply graph patches, build transformed graphs, rewrite graphs, or authorize mutation. The current matcher set covers `mad/fma` for `a*b+c` / `c+a*b`, `clamp` for `min(max(x, lo), hi)`, `step` for simple `x < edge ? 0 : 1` / `x >= edge ? 1 : 0` ternaries, `dot` for additive multiply trees such as `(a0*b0)+(a1*b1)`, and `mix` for `a + t * (b - a)` / `t * (b - a) + a`. `runtime-optimizer-drift.properties` aggregates complete/partial replacement-plan counts, structural plan-validation total/valid/invalid counts, rewrite-visitor ready/blocked counts, replacement-blueprint ready/blocked counts, rewrite-transaction ready/blocked counts, node-id allocation ready/blocked counts, replacement-node preview ready/blocked counts, graph-patch preview ready/blocked counts, transformed-graph materialization ready/blocked counts, rewrite-sketch ready/blocked counts, overlap/conflict counters, aggregate and per-rule `rewriteSelection.*` status/first-blocker guardrails, aggregate and per-rule `rewriteProof.*` proof/runtime-equivalence/rollback blockers, aggregate and per-rule `rewriteReviewPackage.*` package-completeness blockers, and compact `optimizerRule.*` summaries for CI drift detection. Workload gates carry those visitor/blueprint/transaction/allocation/replacement-node/graph-patch/transformed-graph fields forward with the rest of the drift payload, while validation reports/history, I3 summaries, and production-promotion explainability treat the optimizer state as evidence-only telemetry. The pass still reports `rewrite-engine-not-implemented` and never mutates the selected IR. `GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight` now sits above transformed-graph materialization as the future optimized-artifact boundary. It records the original IR identity, absent transformed graph and optimized artifact identities, deterministic envelope key, proof anchor, rollback anchor, ready/blocked counts, first blocker, and disabled artifact build / optimized-artifact build / transformed-IR / graph-patch / graph-rewrite / mutation / selected-IR flags. The pass emits aggregate and `rule.N.irArtifactEnvelope.*` fields, `runtime-optimizer-drift.properties` aggregates them, and workload gates carry them forward as evidence-only telemetry; no optimized artifact is constructed or selected. diff --git a/docs/Runtime-Guide.md b/docs/Runtime-Guide.md index 3e2da1c2..33c1bf83 100644 --- a/docs/Runtime-Guide.md +++ b/docs/Runtime-Guide.md @@ -229,11 +229,13 @@ High and critical estimates add optimizer diagnostics with the hottest method, e Artifact dumps store the complete result in `runtime-ir-analysis.properties`. Per-method fields include total parameter/local/private-array storage, `peakLiveRegisters`, expression-temporary peak, scoped-variable count, shadowed-variable count, unresolved-reference count, budget, utilization, level, and typed-node counts. Per-call fields include resolution state, inline/recursive flags, caller-live values, argument/result pressure, callee pressure, additional frame pressure, and combined estimate. +Set `-Djavatogpu.opencl.runtimeCompileArtifactDirectory=` to dump the full runtime compile artifact bundle for each OpenCL kernel invocation without enabling the operational validation report path. Each kernel gets a sanitized subdirectory under that root. When IR artifacts are available, the bundle includes `original.irgpu.properties` and `optimized.irgpu.properties` so the pre/post optimizer IR can be compared directly, plus `original.backend.opencl-c` and `optimized.backend.opencl-c` for before/after generated OpenCL backend source. `backend.opencl-c` remains the selected OpenCL source that the backend actually compiles. The bundle also includes `runtime-ir-handoff.properties`, `optimizer-report.txt` when reports exist, backend source artifacts, provenance, and diagnostics. This dump is diagnostic-only and does not select optimized IR, enable production mutation, or change backend source switching. + OpenCL isolated runtime-equivalence checks write raw pipeline comparison cases into `runtime-equivalence.properties` when invocation arguments use supported array shapes. Each case records the comparison mode, original invocation inputs, descriptor-source reference outputs, reconstructed-source candidate outputs, exact tolerance metadata, per-output equivalence flags, and diagnostics. Primitive arrays are written as readable vectors, vector and struct arrays as deterministic packed Base64, scalar values as literals, and opaque image/sampler/runtime objects as stable type tags without process-specific handles. This evidence validates source reconstruction and remains separate from per-family optimizer proof. Non-analysis optimizer passes with runtime-equivalence payload evidence also produce `runtime-optimizer-family-equivalence-payload.properties` plus durable files under `runtime-optimizer-family-equivalence-payload/family--/pass-/`. Each pass directory contains `manifest.properties`, CPU/reference, pre-optimization, post-optimization, tolerance, failure-fixture, and diagnostics properties files. When proof fields contain structured case evidence, the component files include deterministic indexed fields for case names, inputs, raw outputs, tolerances, equivalence flags, and diagnostics. Missing components are materialized with `status=not-recorded`; they are never inferred from another pass. Artifact paths are normalized and must remain inside the kernel's `runtime-compile-artifacts` directory. These files are diagnostic evidence only and cannot enable source switching or production mutation. -When the optional IR optimizer bridge participates, each kernel directory may also receive `runtime-ir-optimizer-evidence.properties`. The artifact records only proposal/provider evidence from stable `javatogpu.ir-optimizer` sources: pass counts, proposal-only versus selected optimized counts, rollback counts, IR identities, proof fields, approval-template applicability, and diagnostics. Constant-folding preview proof is also aggregated into `constantFoldingPreview.*` fields for pass count, candidate count, skipped blockers, required runtime-equivalence/approval gates, and unresolved integer-overflow / floating-point-rounding safety flags. Safe-local-CSE preview proof is aggregated into `safeLocalCsePreview.*` fields for pass count, expression/candidate/duplicate/equivalence-class counts, unsupported-operator / impure-operand / control-flow-boundary blockers, required runtime-equivalence/approval gates, and unresolved dominance / side-effect-freedom proof flags. Typed dead-code preview proof is aggregated into `typedDeadCodePreview.*` fields for pass count, typed/reachable/unreachable node counts, missing-root / missing-child-reference / side-effecting-unreachable blockers, required runtime-equivalence/approval gates, and unresolved side-effect-freedom proof flags. The artifact also records `previewReadiness.*` fields with aggregate status, recorded/candidate/blocked family counts, and a per-family status summary so CI can distinguish no-candidate previews from proof-blocked previews and review-ready runtime-equivalence candidates. `runtimeEquivalenceReview.*` records the next fail-closed review boundary: status, eligibility, requirement, first blocker, family summary, disabled production mutation, disabled selected-IR replacement, and manual-review-only state. `reviewPackage.*` records the manual-review package boundary above that review gate, including package status, requirement/completeness, first blocker, proposal-pass count, pending approval count, original/optimized IR and proof-summary requirements, manual-review-only state, and disabled production mutation / selected-IR replacement. `openClValidationReport` aggregates this into `Runtime IR Optimizer Evidence` for CI visibility, including constant-folding, safe-local-CSE, typed dead-code preview totals, preview-readiness status, runtime-equivalence review eligibility, and review-package status. `validateOpenClRuntimeIrOptimizerEvidence` then validates the raw runtime evidence files and fails CI if those fail-closed guardrails are missing, completed early, or production-enabling. The section and validator are evidence-only and do not participate in history, source switching, selected-IR replacement, or production mutation gates. +When the optional IR optimizer bridge participates, each kernel directory may also receive `runtime-ir-optimizer-evidence.properties`. The artifact records only proposal/provider evidence from stable `javatogpu.ir-optimizer` sources: pass counts, proposal-only versus selected optimized counts, rollback counts, IR identities, proof fields, approval-template applicability, and diagnostics. The validation sandwich now also has an internal optimized-artifact candidate envelope for proposed artifacts; it records candidate identity and selection blockers as report-only evidence while final runtime IR selection remains separate and fail-closed. Candidate envelope proof fields are also lifted into top-level `optimizedArtifactCandidate.*` summary fields for status/counts, ready/blocked counts, first blockers, selection-ready/applied counts, selected-IR replacement counts, mutation-policy count, and disabled selection/replacement guardrails. Constant-folding preview proof is also aggregated into `constantFoldingPreview.*` fields for pass count, candidate count, skipped blockers, required runtime-equivalence/approval gates, and unresolved integer-overflow / floating-point-rounding safety flags. Safe-local-CSE preview proof is aggregated into `safeLocalCsePreview.*` fields for pass count, expression/candidate/duplicate/equivalence-class counts, unsupported-operator / impure-operand / control-flow-boundary blockers, required runtime-equivalence/approval gates, and unresolved dominance / side-effect-freedom proof flags. Typed dead-code preview proof is aggregated into `typedDeadCodePreview.*` fields for pass count, typed/reachable/unreachable node counts, missing-root / missing-child-reference / side-effecting-unreachable blockers, required runtime-equivalence/approval gates, and unresolved side-effect-freedom proof flags. The artifact also records `previewReadiness.*` fields with aggregate status, recorded/candidate/blocked family counts, and a per-family status summary so CI can distinguish no-candidate previews from proof-blocked previews and review-ready runtime-equivalence candidates. `runtimeEquivalenceReview.*` records the next fail-closed review boundary: status, eligibility, requirement, first blocker, family summary, disabled production mutation, disabled selected-IR replacement, and manual-review-only state. `reviewPackage.*` records the manual-review package boundary above that review gate, including package status, requirement/completeness, first blocker, proposal-pass count, pending approval count, original/optimized IR and proof-summary requirements, manual-review-only state, and disabled production mutation / selected-IR replacement. `openClValidationReport` aggregates this into `Runtime IR Optimizer Evidence` for CI visibility, including optimized-artifact candidate status/blockers, constant-folding, safe-local-CSE, typed dead-code preview totals, preview-readiness status, runtime-equivalence review eligibility, and review-package status. `validateOpenClRuntimeIrOptimizerEvidence` then validates the raw runtime evidence files and fails CI if those fail-closed guardrails are missing, completed early, or production-enabling. The section and validator are evidence-only and do not participate in history, source switching, selected-IR replacement, or production mutation gates. Runtime peephole/InstCombine remains diagnostic-only. `GpuRuntimeIrPeepholeReplacementPlan` records read-only typed replacement plans for current rule families: `mad/fma`, `clamp`, `step`, `dot`, and `mix`. Built-in rules now use `GpuRuntimeIrTypedNodeGraph` as the shared read-only typed-node traversal surface, and each emitted plan is checked by `GpuRuntimeIrPeepholeReplacementPlanValidation` before proof fields are exported. `GpuRuntimeIrPeepholeTypedRewriteVisitor` adds a shared traversal preflight above validated plans: it records deterministic visit order, visitor-ready/blocked counts, first visitor blocker, and disabled replacement-builder / transformed-IR / mutation / selected-IR flags. `GpuRuntimeIrPeepholeReplacementBlueprint` adds a read-only target intrinsic-call blueprint above visitor-ready plans: it records target node kind/operation, argument node ids/roles, blueprint-ready/blocked counts, first blueprint blocker, and the same disabled builder / transformed-IR / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeRewriteTransactionPreflight` adds the graph-transaction shape above blueprint-ready plans: replaced root ids, removed covered ids, retained input ids, planned added-node count, `plannedAddedNodeIds=not-allocated`, and disabled node-id allocation / graph rewrite / transformed-IR / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeNodeIdAllocationPreflight` adds a diagnostic allocation preview above transaction-ready plans: it records current graph max node id, deterministic candidate node ids, allocation-ready/blocked counts, first blocker, and disabled id reservation / allocator application / graph rewrite / transformed-IR / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeReplacementNodePreflight` adds the next diagnostic preview: candidate replacement-node id, target intrinsic-call kind/operation, argument ids/roles, ready/blocked counts, first blocker, and disabled replacement-node build / replacement-builder / graph rewrite / transformed-IR / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeGraphPatchPreflight` adds a diagnostic graph-patch preview: replacement node id, replaced/removed/retained/inserted node sets, ready/blocked counts, first blocker, and disabled patch application / graph rewrite / transformed-IR / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeTransformedGraphPreflight` adds the transformed-graph materialization preview above that patch: original IR identity, deterministic materialization key, `transformedGraphIdentity=not-built`, ready/blocked counts, first blocker, and disabled transformed-graph build / transformed-IR / patch application / graph rewrite / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeRewriteSketch` records the mutation-free structural rewrite skeleton around the validated replacement plan: ready sketches prove only that the future rewrite shape is describable, while `rewriteBuilderImplemented`, `mutationAllowed`, and `selectedIrReplacement` remain `false`. `rewriteSelection.*` fields add a read-only selection preflight over those sketches, including sketch/conflict counts, selection status, first blocker, proof/approval requirements, and disabled mutation/selection guardrails. `rewriteProof.*` fields add the matching proof preflight: proof status, first blocker, runtime-equivalence payload presence/completeness, rollback evidence, approval acceptance, original IR identity, absent transformed IR identity, and disabled selected-IR replacement. `rewriteReviewPackage.*` fields add the manual-review package boundary above proof readiness: package required/complete state, conflict count, proof acceptance, runtime-equivalence payload, rollback, approval, manual-review-only state, and disabled mutation / selection / selected-IR replacement. The same visitor/blueprint/transaction/allocation/replacement-node/graph-patch/transformed-graph/selection/proof/package preflight fields are also emitted under `rule.N.*`, making rule-family blockers visible without enabling any rewrite. Proof fields expose the candidate root node, covered nodes, input nodes, completeness, first blocker, structural validation counters, visitor counters, blueprint counters, transaction counters, allocation counters, replacement-node counters, graph-patch counters, transformed-graph counters, sketch counters, selection preflight fields, proof preflight fields, and review-package fields. `runtime-optimizer-drift.properties` aggregates complete/partial replacement-plan counts, plan-validation total/valid/invalid counts, rewrite-visitor ready/blocked counts, replacement-blueprint ready/blocked counts, rewrite-transaction ready/blocked counts, node-id allocation ready/blocked counts, replacement-node preview ready/blocked counts, graph-patch preview ready/blocked counts, transformed-graph materialization ready/blocked counts, rewrite-sketch ready/blocked counts, conflict counts, selection-readiness guardrails, proof/runtime-equivalence/rollback blockers, package-completeness blockers, first incomplete/invalid/visitor/blueprint/transaction/allocation/replacement-node/graph-patch/transformed-graph/sketch/selection/proof/package blockers, and compact `optimizerRule.*` summaries from existing `rule.N.*` proof fields so CI can detect planning drift while the rewrite engine remains absent. Workload gates carry the same visitor/blueprint/transaction/allocation/replacement-node/graph-patch/transformed-graph fields forward as evidence-only telemetry. These plans, visitors, blueprints, transactions, allocation previews, replacement-node previews, graph-patch previews, transformed-graph previews, and sketches describe what a future structural rewrite would need to replace; they do not edit typed bodies, reserve node ids, build replacement nodes, apply graph patches, build transformed graphs, apply allocators, rewrite graphs, select optimized IR, or enable production mutation. diff --git a/docs/Validation-and-Operations.md b/docs/Validation-and-Operations.md index 3fe9a07a..7173c5c8 100644 --- a/docs/Validation-and-Operations.md +++ b/docs/Validation-and-Operations.md @@ -84,6 +84,11 @@ processor/build/reports/opencl/backend-source-promotion-activation-gate.properti processor/build/reports/opencl/backend-source-promotion-activation-gate.properties.sha256 processor/build/reports/opencl/production-activation-token-smoke.properties processor/build/reports/opencl/production-activation-token-negative.properties +/**/original.irgpu.properties +/**/optimized.irgpu.properties +/**/original.backend.opencl-c +/**/optimized.backend.opencl-c +/**/backend.opencl-c processor/build/reports/opencl/runtime-compile-artifacts/**/runtime-ir-optimizer-evidence.properties processor/build/reports/opencl/runtime-compile-artifacts/**/runtime-optimizer-family-equivalence-payload/ processor/build/reports/opencl/optimizer-family-payload-fixture/ @@ -92,6 +97,8 @@ processor/build/test-results/ These files are more useful than a screenshot because they preserve bucket status, device details, and machine-readable failure state. +For local pre/post optimizer inspection, set `-Djavatogpu.opencl.runtimeCompileArtifactDirectory=` on the runtime process. This writes the same per-kernel artifact bundle independently of the validation report path, including `original.irgpu.properties`, `optimized.irgpu.properties`, `original.backend.opencl-c`, `optimized.backend.opencl-c`, selected `backend.opencl-c`, `runtime-ir-handoff.properties`, and optimizer evidence when present. + The candidate gate combines the real-workload gate with controlled source-switching acceptance for the same kernel resources and device identity. `review-ready` means the candidate evidence is complete; default production source switching and production mutation remain disabled. Manual `workflow_dispatch` runs expose `production_promotion_manifest_mode=skip|template|validate|activate`. Use `template` to archive a pending device-specific manifest bound to that run's `github.sha`. After approving and committing the manifest, use `validate` with `production_promotion_manifest_file`, the original SHA in `production_promotion_candidate_git_sha`, and a single matching `validation_lane`. Candidate SHA-256 and identity bindings prevent reuse for another GPU, driver, candidate artifact, or source state. @@ -102,7 +109,7 @@ Use `activate` only after manifest validation succeeds. The workflow then writes The vendor workflow also runs `openClOptimizerFamilyPayloadFixtureTest` after the main validation bucket. It must produce `fixture-summary.properties` with two complete families and fourteen durable files. This fixture proves the nested artifact contract is uploadable and path-safe; it does not alter real-workload optimizer-family counts or production readiness. -`validateOpenClRuntimeIrOptimizerEvidence` checks `runtime-ir-optimizer-evidence.properties` under `runtime-compile-artifacts`. It fails if runtime-equivalence review or review-package guardrails stop being fail-closed, if production mutation or selected-IR replacement becomes enabled, if a required review package has no blocker, or if a package is marked complete before manual-review activation exists. The operational and NVIDIA routines run this validator after `openClValidationReport`. +`validateOpenClRuntimeIrOptimizerEvidence` checks `runtime-ir-optimizer-evidence.properties` under `runtime-compile-artifacts`. It fails if runtime-equivalence review, optimized-artifact candidate, or review-package guardrails stop being fail-closed, if production mutation or selected-IR replacement becomes enabled, if candidate selection is applied, if a required review package has no blocker, or if a package is marked complete before manual-review activation exists. The operational and NVIDIA routines run this validator after `openClValidationReport`. ## Optional IR Validation diff --git a/examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md b/examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md new file mode 100644 index 00000000..d3c99894 --- /dev/null +++ b/examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md @@ -0,0 +1,64 @@ +# IR Optimizer Journal Example + +This example shows how to opt into the optional IR optimizer module and write runtime artifacts for local inspection. + +The important source files are: + +```text +examples-app/build.gradle +examples-app/src/main/java/net/sixik/ga_utils/examples/OptimizationJournalExample.java +``` + +`examples-app/build.gradle` adds the optional optimizer module and registers a dedicated runnable task: + +```groovy +implementation project(':ir-optimizer') +tasks.register('runOptimizationJournalExample', JavaExec) { ... } +``` + +`OptimizationJournalExample` enables a diagnostic optimization profile for one generated launcher invocation: + +```java +GpuRuntimeCompileOptions.openCl(List.of(), "diagnostic") +``` + +It also enables the runtime artifact journal by setting: + +```text +javatogpu.opencl.runtimeCompileArtifactDirectory= +``` + +## Run The Example + +Run it from the repository root: + +```powershell +.\gradlew.bat :examples-app:runOptimizationJournalExample --console=plain +``` + +Use a custom artifact directory: + +```powershell +.\gradlew.bat :examples-app:runOptimizationJournalExample -Pjavatogpu.optimizerJournalDir=build\reports\my-ir-journal --console=plain +``` + +## Journal Files + +Look for these files under the generated per-kernel subdirectory: + +```text +backend.opencl-c +original.backend.opencl-c +optimized.backend.opencl-c +original.irgpu.properties +optimized.irgpu.properties +runtime-ir-handoff.properties +optimizer-report.txt +runtime-ir-optimizer-evidence.properties +``` + +`original.backend.opencl-c` and `optimized.backend.opencl-c` are the before/after backend source files for comparing generated OpenCL around the optimizer boundary. `backend.opencl-c` is the selected backend source code that the OpenCL backend actually compiles. `original.irgpu.properties` and `optimized.irgpu.properties` are the matching before/after IR files. `runtime-ir-optimizer-evidence.properties` is the machine-readable guardrail summary that keeps the current alpha fail-closed. + +To tell whether `backend.opencl-c` came from optimized IR or from the original/pass-through IR, open `runtime-ir-handoff.properties` and check `selectedStage` plus `optimizedDiffersFromOriginal`. + +Current alpha behavior is intentionally fail-closed: optimizer evidence and proposed artifacts can be dumped for review, but production mutation and selected-IR replacement remain disabled unless future proof, approval, and production gates explicitly allow them. diff --git a/examples-app/build.gradle b/examples-app/build.gradle index 1217a7d4..ed51e8e8 100644 --- a/examples-app/build.gradle +++ b/examples-app/build.gradle @@ -8,6 +8,7 @@ version = '1.0-SNAPSHOT' dependencies { implementation project(':processor') + implementation project(':ir-optimizer') annotationProcessor project(':processor') if (providers.gradleProperty('javatogpu.enableIrValidationExamples').map { it.toBoolean() }.getOrElse(false)) { annotationProcessor project(':ir-validation') @@ -33,6 +34,16 @@ tasks.register('rewriteGpuMethods', JavaExec) { args layout.buildDirectory.dir('classes/java/main').get().asFile.absolutePath } +tasks.register('runOptimizationJournalExample', JavaExec) { + dependsOn tasks.named('classes') + classpath = sourceSets.main.runtimeClasspath + mainClass = 'net.sixik.ga_utils.examples.OptimizationJournalExample' + def journalDir = providers.gradleProperty('javatogpu.optimizerJournalDir') + if (journalDir.isPresent()) { + args journalDir.get() + } +} + tasks.named('classes') { dependsOn tasks.named('rewriteGpuMethods') } diff --git a/examples-app/src/main/java/net/sixik/ga_utils/examples/OptimizationJournalExample.java b/examples-app/src/main/java/net/sixik/ga_utils/examples/OptimizationJournalExample.java new file mode 100644 index 00000000..4b834bba --- /dev/null +++ b/examples-app/src/main/java/net/sixik/ga_utils/examples/OptimizationJournalExample.java @@ -0,0 +1,168 @@ +package net.sixik.ga_utils.examples; + +import net.sixik.ga_utils.javatogpu.api.GPU; +import net.sixik.ga_utils.javatogpu.api.annotations.GPUGlobal; +import net.sixik.ga_utils.javatogpu.api.annotations.GPUOptimize; +import net.sixik.ga_utils.javatogpu.runtime.GpuExecutionConfig; +import net.sixik.ga_utils.javatogpu.runtime.GpuGeneratedLauncherInvoker; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntime; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileOptions; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeScope; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +/** + * Small runnable example for enabling the optional IR optimizer bridge and writing inspection artifacts. + */ +public final class OptimizationJournalExample { + + private static final String ARTIFACT_DIRECTORY_PROPERTY = "javatogpu.opencl.runtimeCompileArtifactDirectory"; + private static final Path DEFAULT_JOURNAL_ROOT = Path.of( + "build", + "reports", + "ir-optimizer-journal" + ); + private static final Set INTERESTING_ARTIFACTS = Set.of( + "backend.opencl-c", + "original.backend.opencl-c", + "optimized.backend.opencl-c", + "original.irgpu.properties", + "optimized.irgpu.properties", + "runtime-ir-handoff.properties", + "optimizer-report.txt", + "runtime-ir-optimizer-evidence.properties" + ); + + private OptimizationJournalExample() { + } + + public static void main(String[] args) { + Path journalRoot = resolveJournalRoot(args); + String previousArtifactDirectory = System.getProperty(ARTIFACT_DIRECTORY_PROPERTY); + System.setProperty(ARTIFACT_DIRECTORY_PROPERTY, journalRoot.toString()); + + float[] input = new float[]{1.0f, 2.0f, 3.0f, 4.0f}; + float[] output = new float[input.length]; + GpuRuntimeCompileOptions compileOptions = GpuRuntimeCompileOptions.openCl( + List.of(), + "diagnostic" + ); + + System.out.println("IR optimizer example"); + System.out.println("Optimization profile: " + compileOptions.optimizationProfile()); + System.out.println("Artifact journal root: " + journalRoot.toAbsolutePath().normalize()); + System.out.println("Runtime artifact property: -D" + ARTIFACT_DIRECTORY_PROPERTY + "=" + journalRoot); + + try (GpuRuntimeScope ignored = GpuRuntime.useOpenClSharedCache()) { + GpuGeneratedLauncherInvoker.invokeWithConfigAndCompileOptions( + OptimizationJournalExample.class, + "optimizerJournalKernel", + GpuExecutionConfig.oneDimensional(input.length), + compileOptions, + input, + output, + 0f + ); + System.out.println("output[0] = " + output[0]); + printJournal(journalRoot); + } catch (RuntimeException exception) { + System.out.println("OpenCL execution failed: " + exception.getMessage()); + System.out.println("The example still shows the switches to use; run it on a machine with OpenCL to write artifacts."); + } finally { + restoreProperty(previousArtifactDirectory); + GpuRuntime.shutdownOpenClSharedCache(); + } + } + + @net.sixik.ga_utils.javatogpu.api.annotations.GPU + @GPUOptimize(fastMath = true) // Разрешаем оптимизатору применять fma/mad/mix и менять округление + public static void optimizerJournalKernel( + @GPUGlobal float[] input, + @GPUGlobal float[] output, + float t + ) { + int id = GPU.get_global_id(0); + float value = input[id]; + + // 1. Мишень для CSE (Common Subexpression Elimination) + // Выражение (value * 3.1415f) повторяется три раза подряд без мутации 'value'. + float a = value * 3.1415f; + float b = (value * 3.1415f) + 10.0f; + float c = (value * 3.1415f) - 5.0f; + + // 2. Мишень для InstCombine (mad / fma) + // Паттерн a * b + c + float fmaTarget = a * b + c; + + // 3. Мишень для InstCombine (clamp) + // Паттерн min(max(x, lo), hi) + float clampTarget = GPU.min(GPU.max(fmaTarget, 0.0f), 1.0f); + + // 4. Мишень для InstCombine (mix / lerp) + // Паттерн a + t * (b - a) + float mixTarget = a + t * (b - a); + + // 5. Мишень для Auto-vectorization + // Последовательный доступ к памяти внутри цикла с фиксированным размером + float sum = 0.0f; + for (int i = 0; i < 4; i++) { + sum = sum + input[id * 4 + i]; + } + + // 6. Давление на регистры (Register Pressure) + // Множество промежуточных переменных сходятся в одном тяжелом математическом выражении, + // что должно отразиться в GpuRuntimeRegisterPressureAnalyzer + float heavyMath = GPU.sin(clampTarget) + GPU.cos(mixTarget) + sum; + + output[id] = heavyMath; + } + + static Path resolveJournalRoot(String[] args) { + if (args != null && args.length > 0 && args[0] != null && !args[0].isBlank()) { + return Path.of(args[0]); + } + return DEFAULT_JOURNAL_ROOT; + } + + static List interestingJournalFiles(Path journalRoot) throws IOException { + if (journalRoot == null || !Files.isDirectory(journalRoot)) { + return List.of(); + } + try (java.util.stream.Stream paths = Files.walk(journalRoot)) { + return paths + .filter(Files::isRegularFile) + .filter(path -> INTERESTING_ARTIFACTS.contains(path.getFileName().toString())) + .sorted(Comparator.comparing(path -> journalRoot.relativize(path).toString())) + .toList(); + } + } + + private static void printJournal(Path journalRoot) { + try { + List files = interestingJournalFiles(journalRoot); + if (files.isEmpty()) { + System.out.println("No journal artifacts were written yet."); + return; + } + System.out.println("Journal artifacts:"); + for (Path file : files) { + System.out.println(" - " + journalRoot.relativize(file)); + } + } catch (IOException exception) { + System.out.println("Failed to list journal artifacts: " + exception.getMessage()); + } + } + + private static void restoreProperty(String previousArtifactDirectory) { + if (previousArtifactDirectory == null) { + System.clearProperty(ARTIFACT_DIRECTORY_PROPERTY); + } else { + System.setProperty(ARTIFACT_DIRECTORY_PROPERTY, previousArtifactDirectory); + } + } +} diff --git a/examples-app/src/test/java/net/sixik/ga_utils/examples/IrGpuGeneratedArtifactParityTest.java b/examples-app/src/test/java/net/sixik/ga_utils/examples/IrGpuGeneratedArtifactParityTest.java index 689f2214..4f68cd83 100644 --- a/examples-app/src/test/java/net/sixik/ga_utils/examples/IrGpuGeneratedArtifactParityTest.java +++ b/examples-app/src/test/java/net/sixik/ga_utils/examples/IrGpuGeneratedArtifactParityTest.java @@ -26,7 +26,7 @@ void generatedIrGpuArtifactsReconstructToGeneratedOpenClSources() throws Excepti List classpathRoots = javatogpuClasspathRoots(); List manifests = generatedIrGpuManifests(classpathRoots); - assertEquals(28, manifests.size(), "examples-app should keep all showcase IrGpu artifacts under parity coverage"); + assertEquals(29, manifests.size(), "examples-app should keep all showcase IrGpu artifacts under parity coverage"); ArrayList failures = new ArrayList<>(); for (Path manifestPath : manifests) { diff --git a/examples-app/src/test/java/net/sixik/ga_utils/examples/OptimizationJournalExampleTest.java b/examples-app/src/test/java/net/sixik/ga_utils/examples/OptimizationJournalExampleTest.java new file mode 100644 index 00000000..9237e3fc --- /dev/null +++ b/examples-app/src/test/java/net/sixik/ga_utils/examples/OptimizationJournalExampleTest.java @@ -0,0 +1,47 @@ +package net.sixik.ga_utils.examples; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class OptimizationJournalExampleTest { + + @TempDir + Path temporaryDirectory; + + @Test + void resolvesCustomJournalRootFromFirstArgument() { + Path custom = temporaryDirectory.resolve("custom-journal"); + + assertEquals(custom, OptimizationJournalExample.resolveJournalRoot(new String[]{custom.toString()})); + } + + @Test + void listsOnlyRelevantJournalArtifacts() throws Exception { + Path kernelDirectory = temporaryDirectory.resolve("kernel-a"); + Files.createDirectories(kernelDirectory); + Files.writeString(kernelDirectory.resolve("backend.opencl-c"), "__kernel void kernel() {}\n"); + Files.writeString(kernelDirectory.resolve("original.backend.opencl-c"), "__kernel void kernel_original() {}\n"); + Files.writeString(kernelDirectory.resolve("optimized.backend.opencl-c"), "__kernel void kernel_optimized() {}\n"); + Files.writeString(kernelDirectory.resolve("original.irgpu.properties"), "original\n"); + Files.writeString(kernelDirectory.resolve("optimized.irgpu.properties"), "optimized\n"); + Files.writeString(kernelDirectory.resolve("runtime-ir-handoff.properties"), "handoff\n"); + Files.writeString(kernelDirectory.resolve("unrelated.properties"), "ignored\n"); + + List files = OptimizationJournalExample.interestingJournalFiles(temporaryDirectory); + + assertEquals(6, files.size()); + assertTrue(files.stream().anyMatch(path -> path.getFileName().toString().equals("backend.opencl-c"))); + assertTrue(files.stream().anyMatch(path -> path.getFileName().toString().equals("original.backend.opencl-c"))); + assertTrue(files.stream().anyMatch(path -> path.getFileName().toString().equals("optimized.backend.opencl-c"))); + assertTrue(files.stream().anyMatch(path -> path.getFileName().toString().equals("original.irgpu.properties"))); + assertTrue(files.stream().anyMatch(path -> path.getFileName().toString().equals("optimized.irgpu.properties"))); + assertTrue(files.stream().anyMatch(path -> path.getFileName().toString().equals("runtime-ir-handoff.properties"))); + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichReport.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichReport.java index 256e0984..2263f7be 100644 --- a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichReport.java +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichReport.java @@ -14,16 +14,39 @@ public record GpuIrOptimizationSandwichReport( IrGpuArtifact originalArtifact, IrGpuArtifact selectedArtifact, Optional proposal, + Optional optimizedArtifactCandidate, GpuIrOptimizationValidationResult originalValidation, Optional optimizedValidation, GpuIrOptimizationSandwichStatus status, List diagnostics ) { + public GpuIrOptimizationSandwichReport( + IrGpuArtifact originalArtifact, + IrGpuArtifact selectedArtifact, + Optional proposal, + GpuIrOptimizationValidationResult originalValidation, + Optional optimizedValidation, + GpuIrOptimizationSandwichStatus status, + List diagnostics + ) { + this( + originalArtifact, + selectedArtifact, + proposal, + Optional.empty(), + originalValidation, + optimizedValidation, + status, + diagnostics + ); + } + public GpuIrOptimizationSandwichReport { originalArtifact = Objects.requireNonNull(originalArtifact, "originalArtifact"); selectedArtifact = Objects.requireNonNull(selectedArtifact, "selectedArtifact"); proposal = proposal == null ? Optional.empty() : proposal; + optimizedArtifactCandidate = optimizedArtifactCandidate == null ? Optional.empty() : optimizedArtifactCandidate; originalValidation = Objects.requireNonNull(originalValidation, "originalValidation"); optimizedValidation = optimizedValidation == null ? Optional.empty() : optimizedValidation; status = status == null ? GpuIrOptimizationSandwichStatus.NO_CHANGE : status; diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichRunner.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichRunner.java index 1ce0c7cb..06bdebe2 100644 --- a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichRunner.java +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichRunner.java @@ -13,9 +13,11 @@ public final class GpuIrOptimizationSandwichRunner { private final GpuIrOptimizationValidationGate validationGate; + private final GpuIrOptimizedArtifactCandidateBuilder candidateBuilder; public GpuIrOptimizationSandwichRunner(GpuIrOptimizationValidationGate validationGate) { this.validationGate = validationGate == null ? GpuIrOptimizationValidationGate.alwaysValid() : validationGate; + this.candidateBuilder = GpuIrOptimizedArtifactCandidateBuilder.failClosed(); } public static GpuIrOptimizationSandwichRunner alwaysValid() { @@ -99,12 +101,14 @@ public GpuIrOptimizationSandwichReport run( Optional.of(proposal) ) ); + GpuIrOptimizedArtifactCandidate candidate = candidateBuilder.build(request, proposal, optimizedValidation); diagnostics.addAll(optimizedValidation.diagnostics()); if (!optimizedValidation.valid()) { return report( originalArtifact, originalArtifact, Optional.of(proposal), + Optional.of(candidate), originalValidation, Optional.of(optimizedValidation), GpuIrOptimizationSandwichStatus.OPTIMIZED_INVALID_ROLLED_BACK, @@ -117,6 +121,7 @@ public GpuIrOptimizationSandwichReport run( originalArtifact, originalArtifact, Optional.of(proposal), + Optional.of(candidate), originalValidation, Optional.of(optimizedValidation), GpuIrOptimizationSandwichStatus.PROPOSAL_ONLY, @@ -128,6 +133,7 @@ public GpuIrOptimizationSandwichReport run( originalArtifact, optimizedArtifact, Optional.of(proposal), + Optional.of(candidate), originalValidation, Optional.of(optimizedValidation), GpuIrOptimizationSandwichStatus.OPTIMIZED_SELECTED, @@ -143,11 +149,34 @@ private static GpuIrOptimizationSandwichReport report( Optional optimizedValidation, GpuIrOptimizationSandwichStatus status, List diagnostics + ) { + return report( + originalArtifact, + selectedArtifact, + proposal, + Optional.empty(), + originalValidation, + optimizedValidation, + status, + diagnostics + ); + } + + private static GpuIrOptimizationSandwichReport report( + IrGpuArtifact originalArtifact, + IrGpuArtifact selectedArtifact, + Optional proposal, + Optional candidate, + GpuIrOptimizationValidationResult originalValidation, + Optional optimizedValidation, + GpuIrOptimizationSandwichStatus status, + List diagnostics ) { return new GpuIrOptimizationSandwichReport( originalArtifact, selectedArtifact, proposal, + candidate, originalValidation, optimizedValidation, status, diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizedArtifactCandidate.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizedArtifactCandidate.java new file mode 100644 index 00000000..c78e2606 --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizedArtifactCandidate.java @@ -0,0 +1,132 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactIdentity; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Immutable optimized-artifact candidate envelope produced before runtime IR selection. + * + *

The candidate records the optimized artifact and validation/proof metadata, but intentionally does not apply + * selection. Runtime-side selection remains a separate fail-closed gate.

+ */ +public record GpuIrOptimizedArtifactCandidate( + IrGpuArtifact originalArtifact, + IrGpuArtifact optimizedArtifact, + GpuIrOptimizationProposal proposal, + GpuIrOptimizationValidationResult optimizedValidation, + String originalIrIdentity, + String optimizedIrIdentity, + String candidateEnvelopeKey, + boolean candidateBuilt, + boolean optimizedValidationPassed, + boolean proofPresent, + boolean rollbackRequired, + boolean mutationAllowed, + boolean selectionReady, + boolean selectionApplied, + boolean selectedIrReplacement, + String status, + String firstBlocker, + String selectionFirstBlocker +) { + + public static final String DEFAULT_FIELD_PREFIX = "optimizedArtifactCandidate"; + public static final String STATUS_CANDIDATE_READY = "candidate-ready"; + public static final String STATUS_BLOCKED = "blocked"; + public static final String BLOCKER_NONE = "none"; + public static final String BLOCKER_MUTATION_DISABLED = "mutation-disabled"; + public static final String BLOCKER_SELECTION_GATE_NOT_BOUND = "selection-gate-not-bound"; + public static final String BLOCKER_OPTIMIZED_VALIDATION_FAILED = "optimized-validation-failed"; + + public GpuIrOptimizedArtifactCandidate { + originalArtifact = Objects.requireNonNull(originalArtifact, "originalArtifact"); + optimizedArtifact = Objects.requireNonNull(optimizedArtifact, "optimizedArtifact"); + proposal = Objects.requireNonNull(proposal, "proposal"); + optimizedValidation = optimizedValidation == null + ? GpuIrOptimizationValidationResult.invalid( + GpuIrOptimizationValidationStage.OPTIMIZED_AFTER, + "optimized-validation-missing", + "optimized validation result was not recorded" + ) + : optimizedValidation; + originalIrIdentity = IrGpuArtifactIdentity.stableIdentity(originalArtifact); + optimizedIrIdentity = IrGpuArtifactIdentity.stableIdentity(optimizedArtifact); + candidateEnvelopeKey = normalize(candidateEnvelopeKey, envelopeKey( + proposal.optimizerId(), + proposal.optimizerVersion(), + originalIrIdentity, + optimizedIrIdentity, + optimizedValidation.verdict() + )); + candidateBuilt = proposal.hasOptimizedArtifact(); + optimizedValidationPassed = optimizedValidation.valid(); + proofPresent = proposal.proofArtifact() != null; + status = optimizedValidationPassed ? STATUS_CANDIDATE_READY : STATUS_BLOCKED; + firstBlocker = optimizedValidationPassed + ? BLOCKER_NONE + : normalize(optimizedValidation.verdict(), BLOCKER_OPTIMIZED_VALIDATION_FAILED); + selectionFirstBlocker = defaultSelectionFirstBlocker(optimizedValidationPassed, mutationAllowed); + selectionReady = false; + selectionApplied = false; + selectedIrReplacement = false; + } + + public Map fields(String prefix) { + String normalizedPrefix = normalize(prefix, DEFAULT_FIELD_PREFIX); + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put(normalizedPrefix + ".optimizerId", proposal.optimizerId()); + fields.put(normalizedPrefix + ".optimizerVersion", proposal.optimizerVersion()); + fields.put(normalizedPrefix + ".originalIrIdentity", originalIrIdentity); + fields.put(normalizedPrefix + ".optimizedIrIdentity", optimizedIrIdentity); + fields.put(normalizedPrefix + ".candidateEnvelopeKey", candidateEnvelopeKey); + fields.put(normalizedPrefix + ".candidateBuilt", Boolean.toString(candidateBuilt)); + fields.put(normalizedPrefix + ".optimizedValidationPassed", Boolean.toString(optimizedValidationPassed)); + fields.put(normalizedPrefix + ".optimizedValidationVerdict", optimizedValidation.verdict()); + fields.put(normalizedPrefix + ".proofPresent", Boolean.toString(proofPresent)); + fields.put(normalizedPrefix + ".rollbackRequired", Boolean.toString(rollbackRequired)); + fields.put(normalizedPrefix + ".mutationAllowed", Boolean.toString(mutationAllowed)); + fields.put(normalizedPrefix + ".selectionReady", Boolean.toString(selectionReady)); + fields.put(normalizedPrefix + ".selectionApplied", Boolean.toString(selectionApplied)); + fields.put(normalizedPrefix + ".selectedIrReplacement", Boolean.toString(selectedIrReplacement)); + fields.put(normalizedPrefix + ".status", status); + fields.put(normalizedPrefix + ".firstBlocker", firstBlocker); + fields.put(normalizedPrefix + ".selectionFirstBlocker", selectionFirstBlocker); + return Collections.unmodifiableMap(fields); + } + + private static String envelopeKey( + String optimizerId, + String optimizerVersion, + String originalIrIdentity, + String optimizedIrIdentity, + String validationVerdict + ) { + return String.join( + "|", + normalize(optimizerId, "optimizer:unknown"), + normalize(optimizerVersion, "optimizer:unknown:version"), + "original=" + normalize(originalIrIdentity, "irgpu:unknown"), + "optimized=" + normalize(optimizedIrIdentity, "irgpu:unknown"), + "validation=" + normalize(validationVerdict, "unknown") + ); + } + + private static String defaultSelectionFirstBlocker(boolean optimizedValidationPassed, boolean mutationAllowed) { + if (!optimizedValidationPassed) { + return BLOCKER_OPTIMIZED_VALIDATION_FAILED; + } + if (!mutationAllowed) { + return BLOCKER_MUTATION_DISABLED; + } + return BLOCKER_SELECTION_GATE_NOT_BOUND; + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value.trim(); + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizedArtifactCandidateBuilder.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizedArtifactCandidateBuilder.java new file mode 100644 index 00000000..195faa87 --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizedArtifactCandidateBuilder.java @@ -0,0 +1,81 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactIdentity; + +import java.util.Objects; + +/** + * Builds optimized-artifact candidate envelopes without applying runtime IR selection. + */ +public final class GpuIrOptimizedArtifactCandidateBuilder { + + private static final GpuIrOptimizedArtifactCandidateBuilder FAIL_CLOSED = + new GpuIrOptimizedArtifactCandidateBuilder(); + + private GpuIrOptimizedArtifactCandidateBuilder() { + } + + public static GpuIrOptimizedArtifactCandidateBuilder failClosed() { + return FAIL_CLOSED; + } + + public GpuIrOptimizedArtifactCandidate build( + GpuIrOptimizationProposalRequest request, + GpuIrOptimizationProposal proposal, + GpuIrOptimizationValidationResult optimizedValidation + ) { + Objects.requireNonNull(request, "request"); + Objects.requireNonNull(proposal, "proposal"); + if (!proposal.hasOptimizedArtifact()) { + throw new IllegalArgumentException("optimized artifact candidates require a proposed optimized artifact"); + } + IrGpuArtifact originalArtifact = proposal.originalArtifact(); + IrGpuArtifact optimizedArtifact = proposal.optimizedArtifact().orElseThrow(); + GpuIrOptimizationValidationResult validation = optimizedValidation == null + ? GpuIrOptimizationValidationResult.invalid( + GpuIrOptimizationValidationStage.OPTIMIZED_AFTER, + "optimized-validation-missing", + "optimized validation result was not recorded" + ) + : optimizedValidation; + boolean validationPassed = validation.valid(); + boolean mutationAllowed = request.policy().mutationAllowed(); + String requestOriginalIdentity = IrGpuArtifactIdentity.stableIdentity(request.originalArtifact()); + String originalIdentity = IrGpuArtifactIdentity.stableIdentity(originalArtifact); + if (!requestOriginalIdentity.equals(originalIdentity)) { + throw new IllegalArgumentException("optimized artifact candidate proposal original does not match request original artifact"); + } + String optimizedIdentity = IrGpuArtifactIdentity.stableIdentity(optimizedArtifact); + return new GpuIrOptimizedArtifactCandidate( + originalArtifact, + optimizedArtifact, + proposal, + validation, + originalIdentity, + optimizedIdentity, + "", + true, + validationPassed, + proposal.proofArtifact() != null, + request.policy().rollbackRequired(), + mutationAllowed, + false, + false, + false, + validationPassed ? "candidate-ready" : "blocked", + validationPassed ? "none" : validation.verdict(), + selectionFirstBlocker(validationPassed, mutationAllowed) + ); + } + + private static String selectionFirstBlocker(boolean validationPassed, boolean mutationAllowed) { + if (!validationPassed) { + return GpuIrOptimizedArtifactCandidate.BLOCKER_OPTIMIZED_VALIDATION_FAILED; + } + if (!mutationAllowed) { + return GpuIrOptimizedArtifactCandidate.BLOCKER_MUTATION_DISABLED; + } + return GpuIrOptimizedArtifactCandidate.BLOCKER_SELECTION_GATE_NOT_BOUND; + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePass.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePass.java index a0c96514..b66ec51f 100644 --- a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePass.java +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePass.java @@ -117,6 +117,7 @@ private static GpuRuntimeIrOptimizationPassReport toPassReport( GpuRuntimeIrOptimizationProofArtifact proofArtifact = proposal .map(GpuIrOptimizationProposal::proofArtifact) .orElseGet(() -> proof(report.status().name().toLowerCase(java.util.Locale.ROOT), Map.of())); + proofArtifact = withCandidateFields(proofArtifact, report); return switch (report.status()) { case OPTIMIZED_SELECTED -> new GpuRuntimeIrOptimizationPassReport( @@ -184,6 +185,22 @@ private static GpuRuntimeIrOptimizationProofArtifact proof(String verdict, Map fields = new LinkedHashMap<>(proofArtifact.fields()); + fields.putAll(report.optimizedArtifactCandidate().orElseThrow().fields("optimizedArtifactCandidate")); + return GpuRuntimeIrOptimizationProofArtifact.fromFields( + proofArtifact.source(), + proofArtifact.verdict(), + fields + ); + } + private static Map contextFields(GpuRuntimeIrOptimizationRequest request, boolean mutationAllowed) { LinkedHashMap fields = new LinkedHashMap<>(); fields.put("backendTarget", request.compileRequest().options().backendTarget().name()); diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichRunnerTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichRunnerTest.java index 4704b29a..baffa0d8 100644 --- a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichRunnerTest.java +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationSandwichRunnerTest.java @@ -2,6 +2,7 @@ import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactIdentity; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; @@ -31,6 +32,7 @@ void noOpProposalKeepsOriginalSelected() { assertSame(original, report.selectedArtifact()); assertTrue(report.selectedOriginal()); assertFalse(report.selectedOptimized()); + assertTrue(report.optimizedArtifactCandidate().isEmpty()); assertFalse(report.requiresRollback()); assertTrue(report.proposal().orElseThrow().diagnostics().get(0).contains("keeps original IR")); } @@ -48,6 +50,17 @@ void optimizedArtifactStaysProposalOnlyWhenMutationDisabled() { assertSame(original, report.selectedArtifact()); assertTrue(report.selectedOriginal()); assertFalse(report.selectedOptimized()); + GpuIrOptimizedArtifactCandidate candidate = report.optimizedArtifactCandidate().orElseThrow(); + assertSame(original, candidate.originalArtifact()); + assertSame(optimized, candidate.optimizedArtifact()); + assertEquals("candidate-ready", candidate.status()); + assertEquals("none", candidate.firstBlocker()); + assertEquals("mutation-disabled", candidate.selectionFirstBlocker()); + assertFalse(candidate.selectionReady()); + assertFalse(candidate.selectionApplied()); + assertFalse(candidate.selectedIrReplacement()); + assertEquals("false", candidate.fields("candidate").get("candidate.selectionApplied")); + assertEquals("mutation-disabled", candidate.fields("candidate").get("candidate.selectionFirstBlocker")); assertTrue(report.diagnostics().contains( "optimized artifact validated but mutation is disabled; original IR remains selected" )); @@ -66,6 +79,13 @@ void optimizedArtifactCanBeSelectedWhenMutationAllowedAndValidationPasses() { assertSame(optimized, report.selectedArtifact()); assertFalse(report.selectedOriginal()); assertTrue(report.selectedOptimized()); + GpuIrOptimizedArtifactCandidate candidate = report.optimizedArtifactCandidate().orElseThrow(); + assertEquals("candidate-ready", candidate.status()); + assertEquals("selection-gate-not-bound", candidate.selectionFirstBlocker()); + assertTrue(candidate.mutationAllowed()); + assertFalse(candidate.selectionReady()); + assertFalse(candidate.selectionApplied()); + assertFalse(candidate.selectedIrReplacement()); assertFalse(report.requiresRollback()); } @@ -92,6 +112,12 @@ void invalidOptimizedArtifactRollsBackToOriginal() { assertSame(original, report.selectedArtifact()); assertTrue(report.selectedOriginal()); assertTrue(report.requiresRollback()); + GpuIrOptimizedArtifactCandidate candidate = report.optimizedArtifactCandidate().orElseThrow(); + assertEquals("blocked", candidate.status()); + assertEquals("post-validation-failed", candidate.firstBlocker()); + assertEquals("optimized-validation-failed", candidate.selectionFirstBlocker()); + assertFalse(candidate.optimizedValidationPassed()); + assertFalse(candidate.selectionApplied()); assertEquals("post-validation-failed", report.optimizedValidation().orElseThrow().verdict()); assertTrue(report.diagnostics().contains("optimized artifact rejected")); } @@ -132,6 +158,85 @@ void malformedProposedDecisionWithoutArtifactIsRejected() { )); } + @Test + void candidateBuilderRejectsNoChangeProposals() { + IrGpuArtifact original = artifact("body\n return original\n"); + GpuIrOptimizationProposal proposal = GpuIrOptimizationProposal.noChange( + "optimizer:test", + "optimizer:test:1", + original, + "no change" + ); + + assertThrows(IllegalArgumentException.class, () -> GpuIrOptimizedArtifactCandidateBuilder.failClosed().build( + new GpuIrOptimizationProposalRequest(original), + proposal, + GpuIrOptimizationValidationResult.valid(GpuIrOptimizationValidationStage.OPTIMIZED_AFTER) + )); + } + + @Test + void candidateBuilderRejectsProposalForDifferentOriginalArtifact() { + IrGpuArtifact requestOriginal = artifact("body\n return requestOriginal\n"); + IrGpuArtifact proposalOriginal = artifact("body\n return proposalOriginal\n"); + IrGpuArtifact optimized = artifact("body\n return optimized\n"); + + assertThrows(IllegalArgumentException.class, () -> GpuIrOptimizedArtifactCandidateBuilder.failClosed().build( + new GpuIrOptimizationProposalRequest(requestOriginal), + proposed(proposalOriginal, optimized), + GpuIrOptimizationValidationResult.valid(GpuIrOptimizationValidationStage.OPTIMIZED_AFTER) + )); + } + + @Test + void candidateEnvelopePinsDerivedFailClosedFields() { + IrGpuArtifact original = artifact("body\n return original\n"); + IrGpuArtifact optimized = artifact("body\n return optimized\n"); + GpuIrOptimizationProposal proposal = proposed(original, optimized); + GpuIrOptimizationValidationResult invalidValidation = GpuIrOptimizationValidationResult.invalid( + GpuIrOptimizationValidationStage.OPTIMIZED_AFTER, + "post-validation-failed", + "optimized artifact rejected" + ); + + GpuIrOptimizedArtifactCandidate candidate = new GpuIrOptimizedArtifactCandidate( + original, + optimized, + proposal, + invalidValidation, + "caller-supplied-original-id", + "caller-supplied-optimized-id", + "", + false, + true, + false, + false, + true, + true, + true, + true, + "candidate-ready", + "none", + "none" + ); + + assertTrue(candidate.candidateBuilt()); + assertFalse(candidate.optimizedValidationPassed()); + assertTrue(candidate.proofPresent()); + assertTrue(candidate.mutationAllowed()); + assertFalse(candidate.selectionReady()); + assertFalse(candidate.selectionApplied()); + assertFalse(candidate.selectedIrReplacement()); + assertEquals("blocked", candidate.status()); + assertEquals("post-validation-failed", candidate.firstBlocker()); + assertEquals("optimized-validation-failed", candidate.selectionFirstBlocker()); + assertEquals(IrGpuArtifactIdentity.stableIdentity(original), candidate.originalIrIdentity()); + assertEquals(IrGpuArtifactIdentity.stableIdentity(optimized), candidate.optimizedIrIdentity()); + assertTrue(candidate.candidateEnvelopeKey().contains("validation=post-validation-failed")); + assertEquals("false", candidate.fields("candidate").get("candidate.selectionApplied")); + assertEquals("optimized-validation-failed", candidate.fields("candidate").get("candidate.selectionFirstBlocker")); + } + @Test void noOpProposalProviderIsServiceLoaded() { List providers = ServiceLoader diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePassTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePassTest.java index 21992092..4ddf9854 100644 --- a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePassTest.java +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePassTest.java @@ -7,7 +7,13 @@ import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; import net.sixik.ga_utils.javatogpu.runtime.GpuKernelDescriptor; +import net.sixik.ga_utils.javatogpu.runtime.GpuBackendModuleArtifact; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileArtifactDump; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileArtifactDumper; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileArtifactSnapshot; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileInvalidationStamp; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileOptions; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileProvenance; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileRequest; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeDeviceProfile; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationOutcome; @@ -16,10 +22,14 @@ import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationRequest; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationStage; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizerRegistry; +import net.sixik.ga_utils.javatogpu.runtime.opencl.OpenClRuntimeIrOptimizerEvidenceValidatorCli; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import java.io.BufferedReader; import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; @@ -27,6 +37,7 @@ import java.util.ServiceLoader; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertSame; @@ -34,6 +45,9 @@ class GpuIrProposalRuntimeBridgePassTest { + @TempDir + Path temporaryDirectory; + @Test void runtimeBridgeIsTheOnlyServiceLoadedRuntimePassFromModule() { List passes = ServiceLoader @@ -89,11 +103,79 @@ void bridgeKeepsCanonicalizationProposalOnlyByDefault() { assertEquals(GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, report.passReports().get(1).stage()); assertEquals(GpuRuntimeIrOptimizationOutcome.SKIPPED, report.passReports().get(1).outcome()); assertEquals("proposal-only", report.passReports().get(1).proofStatus()); + assertEquals( + "candidate-ready", + report.passReports().get(1).proofArtifact().fields().get("optimizedArtifactCandidate.status") + ); + assertEquals( + "mutation-disabled", + report.passReports().get(1).proofArtifact().fields().get("optimizedArtifactCandidate.selectionFirstBlocker") + ); + assertEquals( + "false", + report.passReports().get(1).proofArtifact().fields().get("optimizedArtifactCandidate.selectionApplied") + ); + assertEquals( + "false", + report.passReports().get(1).proofArtifact().fields().get("optimizedArtifactCandidate.selectedIrReplacement") + ); assertTrue(report.passReports().get(1).diagnostics().contains( "optimized artifact validated but mutation is disabled; original IR remains selected" )); } + @Test + void bridgeCandidateEnvelopeReachesRuntimeEvidenceArtifact() throws Exception { + IrGpuArtifact original = artifact("body \r\n return original\t\r\n"); + GpuRuntimeCompileRequest compileRequest = request(original); + GpuRuntimeIrOptimizationReport optimizationReport = new GpuIrProposalRuntimeBridgePass( + List.of(new GpuIrTextCanonicalizationProposalProvider()), + GpuIrOptimizationSandwichRunner.alwaysValid(), + false + ).run(new GpuRuntimeIrOptimizationRequest(compileRequest, Optional.of(original))); + GpuBackendModuleArtifact backendArtifact = GpuBackendModuleArtifact.openClSource( + "__kernel void run(__global int* out) { out[0] = 1; }", + "runtime/lowered/run.cl", + "test-lowerer-v1" + ); + GpuRuntimeCompileArtifactSnapshot snapshot = GpuRuntimeCompileArtifactSnapshot.from( + compileRequest, + compileRequest, + backendArtifact, + GpuRuntimeCompileInvalidationStamp.from(compileRequest, backendArtifact, "ir-optimizer:e2e"), + GpuRuntimeCompileProvenance.from(compileRequest), + optimizationReport + ); + + GpuRuntimeCompileArtifactDump dump = GpuRuntimeCompileArtifactDumper.dump(snapshot); + + assertTrue(dump.hasArtifact(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT)); + String evidence = dump.artifact(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + assertTrue(evidence.contains("status=recorded")); + assertTrue(evidence.contains("pass.count=1")); + assertTrue(evidence.contains("proposalOnly.count=1")); + assertTrue(evidence.contains("selectedOptimized.count=0")); + assertTrue(evidence.contains("optimizedArtifactCandidate.status=candidate-ready")); + assertTrue(evidence.contains("optimizedArtifactCandidate.count=1")); + assertTrue(evidence.contains("optimizedArtifactCandidate.ready.count=1")); + assertTrue(evidence.contains("optimizedArtifactCandidate.selectionApplied.count=0")); + assertTrue(evidence.contains("optimizedArtifactCandidate.selectedIrReplacement.count=0")); + assertTrue(evidence.contains("optimizedArtifactCandidate.selectionFirstBlocker=mutation-disabled")); + assertTrue(evidence.contains("optimizedArtifactCandidate.selectionApplied=false")); + assertTrue(evidence.contains("optimizedArtifactCandidate.selectedIrReplacement=false")); + assertTrue(evidence.contains("pass.0.proofArtifact.field.optimizedArtifactCandidate.status=candidate-ready")); + assertTrue(evidence.contains("pass.0.proofArtifact.field.optimizedArtifactCandidate.selectionApplied=false")); + assertTrue(evidence.contains("pass.0.proofArtifact.field.optimizedArtifactCandidate.selectedIrReplacement=false")); + + Path evidenceFile = temporaryDirectory.resolve( + GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT + ); + Files.writeString(evidenceFile, evidence, StandardCharsets.UTF_8); + assertDoesNotThrow(() -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main( + new String[]{evidenceFile.toString()} + )); + } + @Test void bridgeCanSelectOptimizedArtifactWhenExplicitlyAllowed() { IrGpuArtifact original = artifact("body \r\n return original\t\r\n"); @@ -110,6 +192,14 @@ void bridgeCanSelectOptimizedArtifactWhenExplicitlyAllowed() { assertEquals(1, report.passReports().size()); assertEquals(GpuRuntimeIrOptimizationOutcome.APPLIED, report.passReports().get(0).outcome()); assertEquals("optimized-selected", report.passReports().get(0).proofStatus()); + assertEquals( + "selection-gate-not-bound", + report.passReports().get(0).proofArtifact().fields().get("optimizedArtifactCandidate.selectionFirstBlocker") + ); + assertEquals( + "false", + report.passReports().get(0).proofArtifact().fields().get("optimizedArtifactCandidate.selectionApplied") + ); assertFalse(report.requiresRollback()); } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java index f66355c7..0408941d 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java @@ -65,6 +65,16 @@ public static GpuRuntimeCompileArtifactDump dump( if (snapshot.originalIrGpuArtifact().isPresent() || snapshot.optimizedIrGpuArtifact().isPresent()) { artifacts.put("irgpu-regeneration.properties", formatRegenerationMetadata(snapshot)); } + snapshot.originalBackendModuleArtifact().ifPresent(artifact -> putBackendStageArtifact( + artifacts, + "original", + artifact + )); + snapshot.optimizedBackendModuleArtifact().ifPresent(artifact -> putBackendStageArtifact( + artifacts, + "optimized", + artifact + )); artifacts.put("backend." + snapshot.backendModuleArtifact().format(), snapshot.backendModuleArtifact().source()); artifacts.put("compile-provenance.properties", snapshot.compileProvenance().toPropertiesText()); snapshot.deviceSelection().ifPresent(selection -> artifacts.put( @@ -144,6 +154,17 @@ private static String formatSourceLocation(IrGpuSourceLocation location) { + location.endColumn(); } + private static void putBackendStageArtifact( + LinkedHashMap artifacts, + String stage, + GpuBackendModuleArtifact artifact + ) { + if (artifact == null || !artifact.sourceAvailable()) { + return; + } + artifacts.put(stage + ".backend." + artifact.format(), artifact.source()); + } + private static String formatProperties(Map fields) { StringBuilder builder = new StringBuilder(); fields.forEach((key, value) -> builder @@ -729,6 +750,29 @@ private static String formatRuntimeIrOptimizerEvidence(GpuRuntimeIrOptimizationR builder.append("approvalTemplate.notApplicable.count=") .append(countApprovalTemplateStatus(irOptimizerReports, "not-applicable")) .append('\n'); + OptimizedArtifactCandidateEvidence optimizedArtifactCandidate = optimizedArtifactCandidateEvidence(irOptimizerReports); + builder.append("optimizedArtifactCandidate.status=").append(optimizedArtifactCandidate.status()).append('\n'); + builder.append("optimizedArtifactCandidate.count=").append(optimizedArtifactCandidate.count()).append('\n'); + builder.append("optimizedArtifactCandidate.ready.count=") + .append(optimizedArtifactCandidate.readyCount()).append('\n'); + builder.append("optimizedArtifactCandidate.blocked.count=") + .append(optimizedArtifactCandidate.blockedCount()).append('\n'); + builder.append("optimizedArtifactCandidate.selectionReady.count=") + .append(optimizedArtifactCandidate.selectionReadyCount()).append('\n'); + builder.append("optimizedArtifactCandidate.selectionApplied.count=") + .append(optimizedArtifactCandidate.selectionAppliedCount()).append('\n'); + builder.append("optimizedArtifactCandidate.selectedIrReplacement.count=") + .append(optimizedArtifactCandidate.selectedIrReplacementCount()).append('\n'); + builder.append("optimizedArtifactCandidate.mutationAllowed.count=") + .append(optimizedArtifactCandidate.mutationAllowedCount()).append('\n'); + builder.append("optimizedArtifactCandidate.firstBlocker=") + .append(safePropertyValue(optimizedArtifactCandidate.firstBlocker())).append('\n'); + builder.append("optimizedArtifactCandidate.selectionFirstBlocker=") + .append(safePropertyValue(optimizedArtifactCandidate.selectionFirstBlocker())).append('\n'); + builder.append("optimizedArtifactCandidate.selectionApplied=") + .append(optimizedArtifactCandidate.selectionApplied()).append('\n'); + builder.append("optimizedArtifactCandidate.selectedIrReplacement=") + .append(optimizedArtifactCandidate.selectedIrReplacement()).append('\n'); ConstantFoldingPreviewEvidence constantFoldingPreview = constantFoldingPreviewEvidence(irOptimizerReports); builder.append("constantFoldingPreview.pass.count=").append(constantFoldingPreview.passCount()).append('\n'); builder.append("constantFoldingPreview.candidate.count=").append(constantFoldingPreview.candidateCount()).append('\n'); @@ -914,6 +958,103 @@ private static long countApprovalTemplateStatus( .count(); } + private static OptimizedArtifactCandidateEvidence optimizedArtifactCandidateEvidence( + List passReports + ) { + int count = 0; + int readyCount = 0; + int blockedCount = 0; + int selectionReadyCount = 0; + int selectionAppliedCount = 0; + int selectedIrReplacementCount = 0; + int mutationAllowedCount = 0; + String firstBlocker = "no-candidates"; + String selectionFirstBlocker = "no-candidates"; + for (GpuRuntimeIrOptimizationPassReport passReport : passReports) { + if (passReport == null || passReport.proofArtifact() == null) { + continue; + } + Map fields = passReport.proofArtifact().fields(); + if (!hasOptimizedArtifactCandidate(fields)) { + continue; + } + count++; + String status = fields.getOrDefault("optimizedArtifactCandidate.status", "unknown"); + if ("candidate-ready".equals(status)) { + readyCount++; + } else { + blockedCount++; + } + if (parseBoolean(fields.get("optimizedArtifactCandidate.selectionReady"))) { + selectionReadyCount++; + } + if (parseBoolean(fields.get("optimizedArtifactCandidate.selectionApplied"))) { + selectionAppliedCount++; + } + if (parseBoolean(fields.get("optimizedArtifactCandidate.selectedIrReplacement"))) { + selectedIrReplacementCount++; + } + if (parseBoolean(fields.get("optimizedArtifactCandidate.mutationAllowed"))) { + mutationAllowedCount++; + } + String candidateBlocker = fields.getOrDefault("optimizedArtifactCandidate.firstBlocker", "none"); + if (isPreferredCandidateBlocker(firstBlocker, candidateBlocker)) { + firstBlocker = candidateBlocker; + } + String candidateSelectionBlocker = fields.getOrDefault( + "optimizedArtifactCandidate.selectionFirstBlocker", + "selection-gate-not-bound" + ); + if (isPreferredCandidateBlocker(selectionFirstBlocker, candidateSelectionBlocker)) { + selectionFirstBlocker = candidateSelectionBlocker; + } + } + String status = optimizedArtifactCandidateStatus(count, readyCount, blockedCount); + if (count > 0 && "no-candidates".equals(firstBlocker)) { + firstBlocker = "none"; + } + if (count > 0 && "no-candidates".equals(selectionFirstBlocker)) { + selectionFirstBlocker = "selection-gate-not-bound"; + } + return new OptimizedArtifactCandidateEvidence( + status, + count, + readyCount, + blockedCount, + selectionReadyCount, + selectionAppliedCount, + selectedIrReplacementCount, + mutationAllowedCount, + firstBlocker, + selectionFirstBlocker + ); + } + + private static boolean hasOptimizedArtifactCandidate(Map fields) { + return fields != null && fields.keySet().stream() + .anyMatch(key -> key.startsWith("optimizedArtifactCandidate.")); + } + + private static boolean isPreferredCandidateBlocker(String currentBlocker, String candidateBlocker) { + if (candidateBlocker == null || candidateBlocker.isBlank()) { + return false; + } + if ("no-candidates".equals(currentBlocker)) { + return true; + } + return "none".equals(currentBlocker) && !"none".equals(candidateBlocker); + } + + private static String optimizedArtifactCandidateStatus(int count, int readyCount, int blockedCount) { + if (count <= 0) { + return "not-recorded"; + } + if (readyCount > 0 && blockedCount > 0) { + return "mixed"; + } + return blockedCount > 0 ? "blocked" : "candidate-ready"; + } + private static ConstantFoldingPreviewEvidence constantFoldingPreviewEvidence( List passReports ) { @@ -1364,6 +1505,27 @@ private record ReviewPackageEvidence( ) { } + private record OptimizedArtifactCandidateEvidence( + String status, + int count, + int readyCount, + int blockedCount, + int selectionReadyCount, + int selectionAppliedCount, + int selectedIrReplacementCount, + int mutationAllowedCount, + String firstBlocker, + String selectionFirstBlocker + ) { + private boolean selectionApplied() { + return selectionAppliedCount > 0; + } + + private boolean selectedIrReplacement() { + return selectedIrReplacementCount > 0; + } + } + private static ApprovalTemplateEvidence approvalTemplateEvidence(GpuRuntimeIrOptimizationPassReport passReport) { if (passReport == null) { return ApprovalTemplateEvidence.notApplicable("pass-report-missing"); diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactSnapshot.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactSnapshot.java index 1869466b..cd877b75 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactSnapshot.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactSnapshot.java @@ -29,7 +29,9 @@ public record GpuRuntimeCompileArtifactSnapshot( String compileLog, List runtimeValidationEvidence, Optional deviceSelection, - List binaryArtifacts + List binaryArtifacts, + Optional originalBackendModuleArtifact, + Optional optimizedBackendModuleArtifact ) { public GpuRuntimeCompileArtifactSnapshot { @@ -77,6 +79,12 @@ public record GpuRuntimeCompileArtifactSnapshot( : List.copyOf(runtimeValidationEvidence); deviceSelection = deviceSelection == null ? Optional.empty() : deviceSelection; binaryArtifacts = binaryArtifacts == null ? List.of() : List.copyOf(binaryArtifacts); + originalBackendModuleArtifact = originalBackendModuleArtifact == null + ? Optional.empty() + : originalBackendModuleArtifact; + optimizedBackendModuleArtifact = optimizedBackendModuleArtifact == null + ? Optional.empty() + : optimizedBackendModuleArtifact; } public GpuRuntimeCompileArtifactSnapshot( @@ -114,7 +122,9 @@ public GpuRuntimeCompileArtifactSnapshot( compileLog, runtimeValidationEvidence, deviceSelection, - List.of() + List.of(), + Optional.empty(), + Optional.empty() ); } @@ -445,7 +455,9 @@ public GpuRuntimeCompileArtifactSnapshot withCompileLog(String compileLog) { compileLog, runtimeValidationEvidence, deviceSelection, - binaryArtifacts + binaryArtifacts, + originalBackendModuleArtifact, + optimizedBackendModuleArtifact ); } @@ -467,7 +479,9 @@ public GpuRuntimeCompileArtifactSnapshot withBinaryArtifacts(List SHARED_COMPILED_KERNELS = new ConcurrentHashMap<>(); @@ -393,6 +394,9 @@ public final void invoke(GpuKernelInvocation invocation) { compileRequest = applyProductionPromotionDecision(compileRequest); GpuRuntimeIrOptimizationResult optimizationResult = optimizeRuntimeIrWithReport(compileRequest); GpuRuntimeCompileRequest optimizedCompileRequest = optimizationResult.compileRequest(); + GpuBackendModuleArtifact originalModuleArtifact = runtimeCompileArtifactsConfigured() + ? lowerBackendModule(compileRequest) + : null; GpuBackendModuleArtifact optimizedModuleArtifact = lowerBackendModule(optimizedCompileRequest); GpuRuntimeEquivalenceEvidence runtimeEquivalenceEvidence = executeRuntimeEquivalence(new GpuRuntimeEquivalenceRequest( compileRequest, @@ -421,6 +425,8 @@ public final void invoke(GpuKernelInvocation invocation) { ); GpuBackendModuleArtifact moduleArtifact = sameSelectedIr(optimizedCompileRequest, selectedCompileRequest) ? optimizedModuleArtifact + : sameSelectedIr(compileRequest, selectedCompileRequest) && originalModuleArtifact != null + ? originalModuleArtifact : lowerBackendModule(selectedCompileRequest); GpuRuntimeCompileInvalidationStamp invalidationStamp = GpuRuntimeCompileInvalidationStamp.from( selectedCompileRequest, @@ -435,7 +441,7 @@ public final void invoke(GpuKernelInvocation invocation) { GpuRuntimeCompileProvenance.from(selectedCompileRequest), optimizationResult.report(), runtimeEquivalenceEvidence - ); + ).withBackendStageModuleArtifacts(originalModuleArtifact, optimizedModuleArtifact); GpuBackendSourcePromotionGate sourcePromotionGate = backendSourcePromotionGate( selectedCompileRequest, moduleArtifact, @@ -3239,26 +3245,57 @@ private void clearRuntimeLaunchAdvisory(GpuRuntimeCompileArtifactSnapshot artifa } private void dumpRuntimeCompileArtifacts(GpuRuntimeCompileArtifactSnapshot artifactSnapshot) { - String outputPath = System.getProperty(BACKEND_SOURCE_PROMOTION_WORKLOAD_GATE_FILE_PROPERTY); - if (outputPath == null || outputPath.isBlank()) { + writeRuntimeCompileArtifactsIfConfigured(artifactSnapshot); + } + + private static boolean runtimeCompileArtifactsConfigured() { + return propertyConfigured(RUNTIME_COMPILE_ARTIFACT_DIRECTORY_PROPERTY) + || propertyConfigured(BACKEND_SOURCE_PROMOTION_WORKLOAD_GATE_FILE_PROPERTY); + } + + private static boolean propertyConfigured(String property) { + String value = System.getProperty(property); + return value != null && !value.isBlank(); + } + + static void writeRuntimeCompileArtifactsIfConfigured(GpuRuntimeCompileArtifactSnapshot artifactSnapshot) { + java.util.List artifactDirectories = runtimeCompileArtifactDirectories(artifactSnapshot); + if (artifactDirectories.isEmpty()) { return; } try { - java.nio.file.Path gatePath = java.nio.file.Paths.get(outputPath); - java.nio.file.Path reportDirectory = gatePath.getParent(); - if (reportDirectory == null) { - return; - } - java.nio.file.Path artifactDirectory = runtimeCompileArtifactDirectory(reportDirectory, artifactSnapshot); - java.nio.file.Files.createDirectories(artifactDirectory); - GpuRuntimeCompileArtifactDump dump = GpuRuntimeCompileArtifactDumper.dump(artifactSnapshot); - writeRuntimeCompileArtifactDump(artifactDirectory, dump); + for (java.nio.file.Path artifactDirectory : artifactDirectories) { + java.nio.file.Files.createDirectories(artifactDirectory); + writeRuntimeCompileArtifactDump(artifactDirectory, dump); + } } catch (RuntimeException | java.io.IOException exception) { throw new IllegalStateException("Failed to write OpenCL runtime compile artifacts", exception); } } + private static java.util.List runtimeCompileArtifactDirectories( + GpuRuntimeCompileArtifactSnapshot artifactSnapshot + ) { + java.util.LinkedHashSet directories = new java.util.LinkedHashSet<>(); + String explicitArtifactRoot = System.getProperty(RUNTIME_COMPILE_ARTIFACT_DIRECTORY_PROPERTY); + if (explicitArtifactRoot != null && !explicitArtifactRoot.isBlank()) { + directories.add(runtimeCompileArtifactDirectoryFromRoot( + java.nio.file.Paths.get(explicitArtifactRoot), + artifactSnapshot + )); + } + String outputPath = System.getProperty(BACKEND_SOURCE_PROMOTION_WORKLOAD_GATE_FILE_PROPERTY); + if (outputPath != null && !outputPath.isBlank()) { + java.nio.file.Path gatePath = java.nio.file.Paths.get(outputPath); + java.nio.file.Path reportDirectory = gatePath.getParent(); + if (reportDirectory != null) { + directories.add(runtimeCompileArtifactDirectory(reportDirectory, artifactSnapshot)); + } + } + return java.util.List.copyOf(directories); + } + static void writeRuntimeCompileArtifactDump( java.nio.file.Path artifactDirectory, GpuRuntimeCompileArtifactDump dump @@ -3310,7 +3347,20 @@ private static java.nio.file.Path runtimeCompileArtifactDirectory( GpuRuntimeCompileArtifactSnapshot artifactSnapshot ) { return reportDirectory + .toAbsolutePath() + .normalize() .resolve("runtime-compile-artifacts") + .resolve(runtimeCompileArtifactDirectoryName(artifactSnapshot)) + .normalize(); + } + + private static java.nio.file.Path runtimeCompileArtifactDirectoryFromRoot( + java.nio.file.Path artifactRootDirectory, + GpuRuntimeCompileArtifactSnapshot artifactSnapshot + ) { + return artifactRootDirectory + .toAbsolutePath() + .normalize() .resolve(runtimeCompileArtifactDirectoryName(artifactSnapshot)); } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java index bdfb2f3b..4429e1dd 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java @@ -96,6 +96,67 @@ int totalApprovalTemplateNotApplicableCount() { return entries.stream().mapToInt(Entry::approvalTemplateNotApplicableCount).sum(); } + int totalOptimizedArtifactCandidateCount() { + return entries.stream().mapToInt(Entry::optimizedArtifactCandidateCount).sum(); + } + + int totalOptimizedArtifactCandidateReadyCount() { + return entries.stream().mapToInt(Entry::optimizedArtifactCandidateReadyCount).sum(); + } + + int totalOptimizedArtifactCandidateBlockedCount() { + return entries.stream().mapToInt(Entry::optimizedArtifactCandidateBlockedCount).sum(); + } + + int totalOptimizedArtifactCandidateSelectionReadyCount() { + return entries.stream().mapToInt(Entry::optimizedArtifactCandidateSelectionReadyCount).sum(); + } + + int totalOptimizedArtifactCandidateSelectionAppliedCount() { + return entries.stream().mapToInt(Entry::optimizedArtifactCandidateSelectionAppliedCount).sum(); + } + + int totalOptimizedArtifactCandidateSelectedIrReplacementCount() { + return entries.stream().mapToInt(Entry::optimizedArtifactCandidateSelectedIrReplacementCount).sum(); + } + + int totalOptimizedArtifactCandidateMutationAllowedCount() { + return entries.stream().mapToInt(Entry::optimizedArtifactCandidateMutationAllowedCount).sum(); + } + + String optimizedArtifactCandidateStatus() { + int count = totalOptimizedArtifactCandidateCount(); + if (count <= 0) { + return "not-recorded"; + } + int readyCount = totalOptimizedArtifactCandidateReadyCount(); + int blockedCount = totalOptimizedArtifactCandidateBlockedCount(); + if (readyCount > 0 && blockedCount > 0) { + return "mixed"; + } + return blockedCount > 0 ? "blocked" : "candidate-ready"; + } + + String optimizedArtifactCandidateFirstBlocker() { + for (Entry entry : entries) { + if (entry.optimizedArtifactCandidateCount() > 0 + && !"none".equals(entry.optimizedArtifactCandidateFirstBlocker())) { + return entry.optimizedArtifactCandidateFirstBlocker(); + } + } + return totalOptimizedArtifactCandidateCount() > 0 ? "none" : "no-candidates"; + } + + String optimizedArtifactCandidateSelectionFirstBlocker() { + for (Entry entry : entries) { + if (entry.optimizedArtifactCandidateCount() > 0 + && !"none".equals(entry.optimizedArtifactCandidateSelectionFirstBlocker())) { + return entry.optimizedArtifactCandidateSelectionFirstBlocker(); + } + } + return totalOptimizedArtifactCandidateCount() > 0 ? "selection-gate-not-bound" : "no-candidates"; + } + int totalConstantFoldingPreviewPassCount() { return entries.stream().mapToInt(Entry::constantFoldingPreviewPassCount).sum(); } @@ -342,6 +403,30 @@ String toMarkdown() { markdown.append("- Rolled back count: `").append(totalRolledBackCount()).append("`\n"); markdown.append("- Approval templates pending: `").append(totalApprovalTemplatePendingCount()).append("`\n"); markdown.append("- Approval templates not applicable: `").append(totalApprovalTemplateNotApplicableCount()).append("`\n"); + markdown.append("- Optimized artifact candidate status: `") + .append(optimizedArtifactCandidateStatus()).append("`\n"); + markdown.append("- Optimized artifact candidates: `") + .append(totalOptimizedArtifactCandidateCount()).append("`\n"); + markdown.append("- Optimized artifact candidates ready: `") + .append(totalOptimizedArtifactCandidateReadyCount()).append("`\n"); + markdown.append("- Optimized artifact candidates blocked: `") + .append(totalOptimizedArtifactCandidateBlockedCount()).append("`\n"); + markdown.append("- Optimized artifact candidate first blocker: `") + .append(inline(optimizedArtifactCandidateFirstBlocker())).append("`\n"); + markdown.append("- Optimized artifact candidate selection first blocker: `") + .append(inline(optimizedArtifactCandidateSelectionFirstBlocker())).append("`\n"); + markdown.append("- Optimized artifact candidate selection ready count: `") + .append(totalOptimizedArtifactCandidateSelectionReadyCount()).append("`\n"); + markdown.append("- Optimized artifact candidate selection applied count: `") + .append(totalOptimizedArtifactCandidateSelectionAppliedCount()).append("`\n"); + markdown.append("- Optimized artifact candidate selected IR replacement count: `") + .append(totalOptimizedArtifactCandidateSelectedIrReplacementCount()).append("`\n"); + markdown.append("- Optimized artifact candidate mutation-allowed count: `") + .append(totalOptimizedArtifactCandidateMutationAllowedCount()).append("`\n"); + markdown.append("- Optimized artifact candidate selection applied: `") + .append(totalOptimizedArtifactCandidateSelectionAppliedCount() > 0).append("`\n"); + markdown.append("- Optimized artifact candidate selected IR replacement: `") + .append(totalOptimizedArtifactCandidateSelectedIrReplacementCount() > 0).append("`\n"); markdown.append("- Constant folding preview passes: `").append(totalConstantFoldingPreviewPassCount()).append("`\n"); markdown.append("- Constant folding preview candidates: `").append(totalConstantFoldingPreviewCandidateCount()).append("`\n"); markdown.append("- Constant folding preview skipped blockers: `").append(totalConstantFoldingPreviewSkippedCount()).append("`\n"); @@ -386,8 +471,8 @@ String toMarkdown() { if (entries.isEmpty()) { return markdown.toString(); } - markdown.append("| Kernel resource | Status | Passes | Proposal-only | Selected | Rolled back | Approval pending | Approval N/A | CF candidates | CF skipped | CSE candidates | CSE duplicates | CSE blocked | TDC unreachable | TDC blocked | Review package | Review blocker | Providers |\n"); - markdown.append("| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- | --- | --- |\n"); + markdown.append("| Kernel resource | Status | Passes | Proposal-only | Selected | Rolled back | Approval pending | Approval N/A | Candidate | Candidate blocker | Selection blocker | CF candidates | CF skipped | CSE candidates | CSE duplicates | CSE blocked | TDC unreachable | TDC blocked | Review package | Review blocker | Providers |\n"); + markdown.append("| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | --- | --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- | --- | --- |\n"); for (Entry entry : entries) { markdown.append("| `").append(table(entry.kernelResource())).append("` | `") .append(table(entry.status())).append("` | `") @@ -397,6 +482,9 @@ String toMarkdown() { .append(entry.rolledBackCount()).append("` | `") .append(entry.approvalTemplatePendingCount()).append("` | `") .append(entry.approvalTemplateNotApplicableCount()).append("` | `") + .append(table(entry.optimizedArtifactCandidateStatus())).append("` | `") + .append(table(entry.optimizedArtifactCandidateFirstBlocker())).append("` | `") + .append(table(entry.optimizedArtifactCandidateSelectionFirstBlocker())).append("` | `") .append(entry.constantFoldingPreviewCandidateCount()).append("` | `") .append(entry.constantFoldingPreviewSkippedCount()).append("` | `") .append(entry.safeLocalCsePreviewCandidateExpressionCount()).append("` | `") @@ -525,6 +613,16 @@ record Entry( int rolledBackCount, int approvalTemplatePendingCount, int approvalTemplateNotApplicableCount, + String optimizedArtifactCandidateStatus, + int optimizedArtifactCandidateCount, + int optimizedArtifactCandidateReadyCount, + int optimizedArtifactCandidateBlockedCount, + int optimizedArtifactCandidateSelectionReadyCount, + int optimizedArtifactCandidateSelectionAppliedCount, + int optimizedArtifactCandidateSelectedIrReplacementCount, + int optimizedArtifactCandidateMutationAllowedCount, + String optimizedArtifactCandidateFirstBlocker, + String optimizedArtifactCandidateSelectionFirstBlocker, int constantFoldingPreviewPassCount, int constantFoldingPreviewCandidateCount, int constantFoldingPreviewSkippedNonPlainLiteralCount, @@ -578,6 +676,19 @@ record Entry( rolledBackCount = Math.max(0, rolledBackCount); approvalTemplatePendingCount = Math.max(0, approvalTemplatePendingCount); approvalTemplateNotApplicableCount = Math.max(0, approvalTemplateNotApplicableCount); + optimizedArtifactCandidateStatus = normalize(optimizedArtifactCandidateStatus, "not-recorded"); + optimizedArtifactCandidateCount = Math.max(0, optimizedArtifactCandidateCount); + optimizedArtifactCandidateReadyCount = Math.max(0, optimizedArtifactCandidateReadyCount); + optimizedArtifactCandidateBlockedCount = Math.max(0, optimizedArtifactCandidateBlockedCount); + optimizedArtifactCandidateSelectionReadyCount = Math.max(0, optimizedArtifactCandidateSelectionReadyCount); + optimizedArtifactCandidateSelectionAppliedCount = Math.max(0, optimizedArtifactCandidateSelectionAppliedCount); + optimizedArtifactCandidateSelectedIrReplacementCount = Math.max(0, optimizedArtifactCandidateSelectedIrReplacementCount); + optimizedArtifactCandidateMutationAllowedCount = Math.max(0, optimizedArtifactCandidateMutationAllowedCount); + optimizedArtifactCandidateFirstBlocker = normalize(optimizedArtifactCandidateFirstBlocker, "no-candidates"); + optimizedArtifactCandidateSelectionFirstBlocker = normalize( + optimizedArtifactCandidateSelectionFirstBlocker, + "no-candidates" + ); constantFoldingPreviewPassCount = Math.max(0, constantFoldingPreviewPassCount); constantFoldingPreviewCandidateCount = Math.max(0, constantFoldingPreviewCandidateCount); constantFoldingPreviewSkippedNonPlainLiteralCount = Math.max(0, constantFoldingPreviewSkippedNonPlainLiteralCount); @@ -665,12 +776,15 @@ int typedDeadCodePreviewProofBlockerCount() { static Entry from(String kernelResource, Properties properties) { if (properties == null || properties.isEmpty()) { - return new Entry(kernelResource, "missing", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + return new Entry(kernelResource, "missing", 0, 0, 0, 0, 0, 0, + "not-recorded", 0, 0, 0, 0, 0, 0, 0, "no-candidates", "no-candidates", + 0, 0, 0, 0, 0, 0, 0, false, false, false, false, 0, 0, 0, 0, 0, 0, 0, 0, false, false, false, false, 0, 0, 0, 0, 0, 0, 0, false, false, false, "not-recorded", false, false, "review-package-not-recorded", 0, 0, "unknown", false, Map.of()); } + CandidateEvidence optimizedArtifactCandidate = optimizedArtifactCandidateEvidence(properties); return new Entry( kernelResource, properties.getProperty("status", "unknown"), @@ -680,6 +794,16 @@ static Entry from(String kernelResource, Properties properties) { parseInt(properties.getProperty("rolledBack.count"), 0), parseInt(properties.getProperty("approvalTemplate.pending.count"), 0), parseInt(properties.getProperty("approvalTemplate.notApplicable.count"), 0), + optimizedArtifactCandidate.status(), + optimizedArtifactCandidate.count(), + optimizedArtifactCandidate.readyCount(), + optimizedArtifactCandidate.blockedCount(), + optimizedArtifactCandidate.selectionReadyCount(), + optimizedArtifactCandidate.selectionAppliedCount(), + optimizedArtifactCandidate.selectedIrReplacementCount(), + optimizedArtifactCandidate.mutationAllowedCount(), + optimizedArtifactCandidate.firstBlocker(), + optimizedArtifactCandidate.selectionFirstBlocker(), parseInt(properties.getProperty("constantFoldingPreview.pass.count"), 0), parseInt(properties.getProperty("constantFoldingPreview.candidate.count"), 0), parseInt(properties.getProperty("constantFoldingPreview.skipped.nonPlainLiteral.count"), 0), @@ -736,6 +860,143 @@ private static String normalize(String value, String fallback) { return value == null || value.isBlank() ? fallback : value; } + private static CandidateEvidence optimizedArtifactCandidateEvidence(Properties properties) { + if (properties.containsKey("optimizedArtifactCandidate.count") + || properties.containsKey("optimizedArtifactCandidate.status")) { + return new CandidateEvidence( + properties.getProperty("optimizedArtifactCandidate.status", "not-recorded"), + parseInt(properties.getProperty("optimizedArtifactCandidate.count"), 0), + parseInt(properties.getProperty("optimizedArtifactCandidate.ready.count"), 0), + parseInt(properties.getProperty("optimizedArtifactCandidate.blocked.count"), 0), + parseInt(properties.getProperty("optimizedArtifactCandidate.selectionReady.count"), 0), + parseInt(properties.getProperty("optimizedArtifactCandidate.selectionApplied.count"), 0), + parseInt(properties.getProperty("optimizedArtifactCandidate.selectedIrReplacement.count"), 0), + parseInt(properties.getProperty("optimizedArtifactCandidate.mutationAllowed.count"), 0), + properties.getProperty("optimizedArtifactCandidate.firstBlocker", "no-candidates"), + properties.getProperty("optimizedArtifactCandidate.selectionFirstBlocker", "no-candidates") + ); + } + return optimizedArtifactCandidateEvidenceFromPassFields(properties); + } + + private static CandidateEvidence optimizedArtifactCandidateEvidenceFromPassFields(Properties properties) { + int count = 0; + int readyCount = 0; + int blockedCount = 0; + int selectionReadyCount = 0; + int selectionAppliedCount = 0; + int selectedIrReplacementCount = 0; + int mutationAllowedCount = 0; + String firstBlocker = "no-candidates"; + String selectionFirstBlocker = "no-candidates"; + int passCount = parseInt(properties.getProperty("pass.count"), 0); + for (int index = 0; index < passCount; index++) { + String prefix = "pass." + index + ".proofArtifact.field.optimizedArtifactCandidate."; + if (!hasCandidateField(properties, prefix)) { + continue; + } + count++; + String status = properties.getProperty(prefix + "status", "unknown"); + if ("candidate-ready".equals(status)) { + readyCount++; + } else { + blockedCount++; + } + if (parseBoolean(properties.getProperty(prefix + "selectionReady"))) { + selectionReadyCount++; + } + if (parseBoolean(properties.getProperty(prefix + "selectionApplied"))) { + selectionAppliedCount++; + } + if (parseBoolean(properties.getProperty(prefix + "selectedIrReplacement"))) { + selectedIrReplacementCount++; + } + if (parseBoolean(properties.getProperty(prefix + "mutationAllowed"))) { + mutationAllowedCount++; + } + String candidateBlocker = properties.getProperty(prefix + "firstBlocker", "none"); + if (isPreferredCandidateBlocker(firstBlocker, candidateBlocker)) { + firstBlocker = candidateBlocker; + } + String candidateSelectionBlocker = properties.getProperty( + prefix + "selectionFirstBlocker", + "selection-gate-not-bound" + ); + if (isPreferredCandidateBlocker(selectionFirstBlocker, candidateSelectionBlocker)) { + selectionFirstBlocker = candidateSelectionBlocker; + } + } + String status = optimizedArtifactCandidateStatus(count, readyCount, blockedCount); + if (count > 0 && "no-candidates".equals(firstBlocker)) { + firstBlocker = "none"; + } + if (count > 0 && "no-candidates".equals(selectionFirstBlocker)) { + selectionFirstBlocker = "selection-gate-not-bound"; + } + return new CandidateEvidence( + status, + count, + readyCount, + blockedCount, + selectionReadyCount, + selectionAppliedCount, + selectedIrReplacementCount, + mutationAllowedCount, + firstBlocker, + selectionFirstBlocker + ); + } + + private static boolean hasCandidateField(Properties properties, String prefix) { + return properties.stringPropertyNames().stream().anyMatch(key -> key.startsWith(prefix)); + } + + private static boolean isPreferredCandidateBlocker(String currentBlocker, String candidateBlocker) { + if (candidateBlocker == null || candidateBlocker.isBlank()) { + return false; + } + if ("no-candidates".equals(currentBlocker)) { + return true; + } + return "none".equals(currentBlocker) && !"none".equals(candidateBlocker); + } + + private static String optimizedArtifactCandidateStatus(int count, int readyCount, int blockedCount) { + if (count <= 0) { + return "not-recorded"; + } + if (readyCount > 0 && blockedCount > 0) { + return "mixed"; + } + return blockedCount > 0 ? "blocked" : "candidate-ready"; + } + + private record CandidateEvidence( + String status, + int count, + int readyCount, + int blockedCount, + int selectionReadyCount, + int selectionAppliedCount, + int selectedIrReplacementCount, + int mutationAllowedCount, + String firstBlocker, + String selectionFirstBlocker + ) { + private CandidateEvidence { + status = normalize(status, "not-recorded"); + count = Math.max(0, count); + readyCount = Math.max(0, readyCount); + blockedCount = Math.max(0, blockedCount); + selectionReadyCount = Math.max(0, selectionReadyCount); + selectionAppliedCount = Math.max(0, selectionAppliedCount); + selectedIrReplacementCount = Math.max(0, selectedIrReplacementCount); + mutationAllowedCount = Math.max(0, mutationAllowedCount); + firstBlocker = normalize(firstBlocker, "no-candidates"); + selectionFirstBlocker = normalize(selectionFirstBlocker, "no-candidates"); + } + } + private static Map parseProviderCounts(Properties properties) { LinkedHashMap counts = new LinkedHashMap<>(); int count = parseInt(properties.getProperty("pass.count"), 0); diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCli.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCli.java index 858573c8..415a8753 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCli.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCli.java @@ -87,6 +87,7 @@ private static void validateGuardrails(Path artifact, Properties properties) { requireValue(artifact, properties, "reviewPackage.originalIrRequired", "true"); requireValue(artifact, properties, "reviewPackage.optimizedIrRequired", "true"); requireValue(artifact, properties, "reviewPackage.proofSummaryRequired", "true"); + validateOptimizedArtifactCandidateGuardrails(artifact, properties); String reviewPackageStatus = requirePresent(artifact, properties, "reviewPackage.status"); boolean required = parseBoolean(requirePresent(artifact, properties, "reviewPackage.required")); @@ -124,6 +125,69 @@ private static void validateGuardrails(Path artifact, Properties properties) { } } + private static void validateOptimizedArtifactCandidateGuardrails(Path artifact, Properties properties) { + String status = requirePresent(artifact, properties, "optimizedArtifactCandidate.status"); + int count = parseNonNegativeInt(artifact, properties, "optimizedArtifactCandidate.count"); + int readyCount = parseNonNegativeInt(artifact, properties, "optimizedArtifactCandidate.ready.count"); + int blockedCount = parseNonNegativeInt(artifact, properties, "optimizedArtifactCandidate.blocked.count"); + int selectionReadyCount = parseNonNegativeInt( + artifact, + properties, + "optimizedArtifactCandidate.selectionReady.count" + ); + int selectionAppliedCount = parseNonNegativeInt( + artifact, + properties, + "optimizedArtifactCandidate.selectionApplied.count" + ); + int selectedIrReplacementCount = parseNonNegativeInt( + artifact, + properties, + "optimizedArtifactCandidate.selectedIrReplacement.count" + ); + parseNonNegativeInt(artifact, properties, "optimizedArtifactCandidate.mutationAllowed.count"); + String firstBlocker = requirePresent(artifact, properties, "optimizedArtifactCandidate.firstBlocker"); + String selectionFirstBlocker = requirePresent( + artifact, + properties, + "optimizedArtifactCandidate.selectionFirstBlocker" + ); + requireValue(artifact, properties, "optimizedArtifactCandidate.selectionApplied", "false"); + requireValue(artifact, properties, "optimizedArtifactCandidate.selectedIrReplacement", "false"); + + if (!List.of("not-recorded", "candidate-ready", "blocked", "mixed").contains(status)) { + throw new IllegalStateException("Unsupported optimized artifact candidate status for " + artifact + + ": " + status); + } + if (readyCount + blockedCount > count) { + throw new IllegalStateException("Optimized artifact candidate ready/blocked counts exceed total for " + + artifact + ": ready=" + readyCount + ", blocked=" + blockedCount + ", total=" + count); + } + if (count == 0 && !"not-recorded".equals(status)) { + throw new IllegalStateException("Missing optimized artifact candidates must use not-recorded status for " + + artifact + ": status=" + status); + } + if (count == 0 && (!"no-candidates".equals(firstBlocker) + || !"no-candidates".equals(selectionFirstBlocker))) { + throw new IllegalStateException("Missing optimized artifact candidates must expose no-candidates blockers for " + + artifact); + } + if (count > 0 && "not-recorded".equals(status)) { + throw new IllegalStateException("Recorded optimized artifact candidates cannot use not-recorded status for " + + artifact); + } + if (count > 0 && ("none".equals(selectionFirstBlocker) || "no-candidates".equals(selectionFirstBlocker))) { + throw new IllegalStateException("Optimized artifact candidates must keep selection blocked for " + + artifact + ": selectionFirstBlocker=" + selectionFirstBlocker); + } + if (selectionReadyCount > 0 || selectionAppliedCount > 0 || selectedIrReplacementCount > 0) { + throw new IllegalStateException("Optimized artifact candidates must not become runtime selection for " + + artifact + ": selectionReady=" + selectionReadyCount + + ", selectionApplied=" + selectionAppliedCount + + ", selectedIrReplacement=" + selectedIrReplacementCount); + } + } + private static String requirePresent(Path artifact, Properties properties, String key) { String value = properties.getProperty(key); if (value == null || value.isBlank()) { diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java index 6e7cd961..24775c0e 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java @@ -321,6 +321,11 @@ void preservesBinaryRuntimeCompileArtifacts() { void dumpsOriginalOptimizedAndBackendArtifacts() { IrGpuArtifact original = artifact("body\n return original\n"); IrGpuArtifact optimized = artifact("body\n return optimized\n"); + GpuBackendModuleArtifact originalBackendArtifact = GpuBackendModuleArtifact.openClSource( + "__kernel void kernel(__global int* out) { out[0] = 1; }", + "runtime/original/kernel.cl", + "test-lowerer-v1" + ); GpuBackendModuleArtifact backendArtifact = GpuBackendModuleArtifact.openClSource( "__kernel void kernel(__global int* out) { out[0] = 2; }", "runtime/lowered/kernel.cl", @@ -412,7 +417,8 @@ void dumpsOriginalOptimizedAndBackendArtifacts() { List.of(location()), "build ok", List.of("equivalence:skipped") - ).withDeviceSelection(deviceSelection()); + ).withBackendStageModuleArtifacts(originalBackendArtifact, backendArtifact) + .withDeviceSelection(deviceSelection()); GpuRuntimeCompileArtifactDump dump = GpuRuntimeCompileArtifactDumper.dump(snapshot); @@ -510,6 +516,8 @@ void dumpsOriginalOptimizedAndBackendArtifacts() { assertTrue(dump.artifact("backend-source-map.properties").contains("methodBody.0.emittedName=jtg_kernel")); assertTrue(dump.artifact("backend-source-map.properties").contains("methodBody.0.format=ir-text-v1")); assertTrue(dump.artifact("backend-source-map.properties").contains("methodBody.0.sourceKind=java-source")); + assertEquals(originalBackendArtifact.source(), dump.artifact("original.backend.opencl-c")); + assertEquals(backendArtifact.source(), dump.artifact("optimized.backend.opencl-c")); assertEquals(backendArtifact.source(), dump.artifact("backend.opencl-c")); assertTrue(dump.artifact("compile-provenance.properties").contains("backendTarget=OPENCL")); assertTrue(dump.artifact("compile-provenance.properties").contains("deviceLabel=Mock GPU")); @@ -2510,10 +2518,21 @@ void dumpsRuntimeIrOptimizerEvidenceArtifactForProposalBridgeReports() { GpuRuntimeIrOptimizationProofArtifact.fromFields( "ir-optimizer.text-canonicalization", "semantics-neutral-text-normalization", - Map.of( - "changedMethodBodies", "1", - "mutationRequired", "false", - "normalizations", "crlf-to-lf,trailing-whitespace" + Map.ofEntries( + Map.entry("changedMethodBodies", "1"), + Map.entry("mutationRequired", "false"), + Map.entry("normalizations", "crlf-to-lf,trailing-whitespace"), + Map.entry("optimizedArtifactCandidate.status", "candidate-ready"), + Map.entry("optimizedArtifactCandidate.candidateBuilt", "true"), + Map.entry("optimizedArtifactCandidate.optimizedValidationPassed", "true"), + Map.entry("optimizedArtifactCandidate.proofPresent", "true"), + Map.entry("optimizedArtifactCandidate.rollbackRequired", "true"), + Map.entry("optimizedArtifactCandidate.mutationAllowed", "false"), + Map.entry("optimizedArtifactCandidate.selectionReady", "false"), + Map.entry("optimizedArtifactCandidate.selectionApplied", "false"), + Map.entry("optimizedArtifactCandidate.selectedIrReplacement", "false"), + Map.entry("optimizedArtifactCandidate.firstBlocker", "none"), + Map.entry("optimizedArtifactCandidate.selectionFirstBlocker", "mutation-disabled") ) ), List.of("optimized artifact validated but mutation is disabled; original IR remains selected") @@ -2638,6 +2657,18 @@ void dumpsRuntimeIrOptimizerEvidenceArtifactForProposalBridgeReports() { assertTrue(evidence.contains("selectedOptimized.count=0")); assertTrue(evidence.contains("approvalTemplate.pending.count=1")); assertTrue(evidence.contains("approvalTemplate.notApplicable.count=4")); + assertTrue(evidence.contains("optimizedArtifactCandidate.status=candidate-ready")); + assertTrue(evidence.contains("optimizedArtifactCandidate.count=1")); + assertTrue(evidence.contains("optimizedArtifactCandidate.ready.count=1")); + assertTrue(evidence.contains("optimizedArtifactCandidate.blocked.count=0")); + assertTrue(evidence.contains("optimizedArtifactCandidate.selectionReady.count=0")); + assertTrue(evidence.contains("optimizedArtifactCandidate.selectionApplied.count=0")); + assertTrue(evidence.contains("optimizedArtifactCandidate.selectedIrReplacement.count=0")); + assertTrue(evidence.contains("optimizedArtifactCandidate.mutationAllowed.count=0")); + assertTrue(evidence.contains("optimizedArtifactCandidate.firstBlocker=none")); + assertTrue(evidence.contains("optimizedArtifactCandidate.selectionFirstBlocker=mutation-disabled")); + assertTrue(evidence.contains("optimizedArtifactCandidate.selectionApplied=false")); + assertTrue(evidence.contains("optimizedArtifactCandidate.selectedIrReplacement=false")); assertTrue(evidence.contains("constantFoldingPreview.pass.count=1")); assertTrue(evidence.contains("constantFoldingPreview.candidate.count=1")); assertTrue(evidence.contains("constantFoldingPreview.skipped.nonPlainLiteral.count=2")); @@ -2707,6 +2738,9 @@ void dumpsRuntimeIrOptimizerEvidenceArtifactForProposalBridgeReports() { assertTrue(evidence.contains("pass.1.proofArtifact.source=ir-optimizer.text-canonicalization")); assertTrue(evidence.contains("pass.1.proofArtifact.field.changedMethodBodies=1")); assertTrue(evidence.contains("pass.1.proofArtifact.field.mutationRequired=false")); + assertTrue(evidence.contains("pass.1.proofArtifact.field.optimizedArtifactCandidate.status=candidate-ready")); + assertTrue(evidence.contains("pass.1.proofArtifact.field.optimizedArtifactCandidate.selectionApplied=false")); + assertTrue(evidence.contains("pass.1.proofArtifact.field.optimizedArtifactCandidate.selectedIrReplacement=false")); assertTrue(evidence.contains("pass.0.approvalTemplate.status=not-applicable")); assertTrue(evidence.contains("pass.0.approvalTemplate.firstBlocker=proposal-decision-not-proposed")); assertTrue(evidence.contains("pass.1.approvalTemplate.status=pending")); diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackendTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackendTest.java index bc04fa6e..bf51eb0a 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackendTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackendTest.java @@ -153,6 +153,67 @@ void runtimeCompileArtifactWriterCreatesNestedPayloadDirectories() throws Except ); } + @Test + void runtimeCompileArtifactDirectoryPropertyDumpsOriginalAndOptimizedIrArtifacts() throws Exception { + Path artifactRoot = Files.createTempDirectory("javatogpu-runtime-ir-dump"); + String property = "javatogpu.opencl.runtimeCompileArtifactDirectory"; + String previousArtifactRoot = System.getProperty(property); + try { + System.setProperty(property, artifactRoot.toString()); + IrGpuArtifact original = testIrGpuArtifact("body\n return original\n"); + IrGpuArtifact optimized = testIrGpuArtifact("body\n return optimized\n"); + GpuRuntimeCompileRequest originalRequest = new GpuRuntimeCompileRequest( + intOutputDescriptor(), + GpuRuntimeCompileOptions.defaults(GpuBackendTarget.OPENCL), + GpuRuntimeDeviceProfile.generic(GpuBackendTarget.OPENCL, "OpenCL"), + Optional.of(original) + ); + GpuRuntimeCompileRequest optimizedRequest = originalRequest.withIrGpuArtifact(Optional.of(optimized)); + GpuBackendModuleArtifact originalBackendArtifact = GpuBackendModuleArtifact.openClSource( + "__kernel void kernel(__global int* output) { output[0] = 0; }", + "javatogpu/sample/Demo/kernel-original.cl", + "test-lowerer-v1" + ); + GpuBackendModuleArtifact backendArtifact = GpuBackendModuleArtifact.openClSource( + "__kernel void kernel(__global int* output) { output[0] = 1; }", + "javatogpu/sample/Demo/kernel.cl", + "test-lowerer-v1" + ); + GpuRuntimeCompileArtifactSnapshot snapshot = GpuRuntimeCompileArtifactSnapshot.from( + originalRequest, + optimizedRequest, + backendArtifact, + net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileInvalidationStamp.from( + optimizedRequest, + backendArtifact, + "optimizer:manual-dump-test" + ), + net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileProvenance.from(optimizedRequest), + new GpuRuntimeIrOptimizationReport(Optional.of(optimized), List.of()) + ).withBackendStageModuleArtifacts(originalBackendArtifact, backendArtifact); + + OpenClGpuRuntimeBackend.writeRuntimeCompileArtifactsIfConfigured(snapshot); + + Path artifactDirectory; + try (java.util.stream.Stream directories = Files.list(artifactRoot)) { + artifactDirectory = directories.filter(Files::isDirectory).findFirst().orElseThrow(); + } + assertTrue(Files.readString(artifactDirectory.resolve("original.irgpu.properties")).contains("return original")); + assertTrue(Files.readString(artifactDirectory.resolve("optimized.irgpu.properties")).contains("return optimized")); + assertTrue(Files.readString(artifactDirectory.resolve("original.backend.opencl-c")).contains("output[0] = 0")); + assertTrue(Files.readString(artifactDirectory.resolve("optimized.backend.opencl-c")).contains("output[0] = 1")); + assertTrue(Files.readString(artifactDirectory.resolve("backend.opencl-c")).contains("__kernel void kernel")); + assertTrue(Files.exists(artifactDirectory.resolve("runtime-ir-handoff.properties"))); + assertTrue(Files.exists(artifactDirectory.resolve("compile-provenance.properties"))); + } finally { + if (previousArtifactRoot == null) { + System.clearProperty(property); + } else { + System.setProperty(property, previousArtifactRoot); + } + } + } + @Test void writesOptimizerFamilyPayloadFixtureArtifacts() throws Exception { String outputDirectory = System.getProperty(OPTIMIZER_FAMILY_PAYLOAD_FIXTURE_DIRECTORY_PROPERTY); diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java index 16940295..48aa3ddb 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java @@ -42,6 +42,18 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { rolledBack.count=1 approvalTemplate.pending.count=1 approvalTemplate.notApplicable.count=2 + optimizedArtifactCandidate.status=candidate-ready + optimizedArtifactCandidate.count=1 + optimizedArtifactCandidate.ready.count=1 + optimizedArtifactCandidate.blocked.count=0 + optimizedArtifactCandidate.selectionReady.count=0 + optimizedArtifactCandidate.selectionApplied.count=0 + optimizedArtifactCandidate.selectedIrReplacement.count=0 + optimizedArtifactCandidate.mutationAllowed.count=0 + optimizedArtifactCandidate.firstBlocker=none + optimizedArtifactCandidate.selectionFirstBlocker=mutation-disabled + optimizedArtifactCandidate.selectionApplied=false + optimizedArtifactCandidate.selectedIrReplacement=false constantFoldingPreview.pass.count=1 constantFoldingPreview.candidate.count=3 constantFoldingPreview.skipped.nonPlainLiteral.count=1 @@ -100,6 +112,15 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { assertEquals(1, summary.entries().get(0).rolledBackCount()); assertEquals(1, summary.entries().get(0).approvalTemplatePendingCount()); assertEquals(2, summary.entries().get(0).approvalTemplateNotApplicableCount()); + assertEquals("candidate-ready", summary.entries().get(0).optimizedArtifactCandidateStatus()); + assertEquals(1, summary.entries().get(0).optimizedArtifactCandidateCount()); + assertEquals(1, summary.entries().get(0).optimizedArtifactCandidateReadyCount()); + assertEquals(0, summary.entries().get(0).optimizedArtifactCandidateBlockedCount()); + assertEquals(0, summary.entries().get(0).optimizedArtifactCandidateSelectionReadyCount()); + assertEquals(0, summary.entries().get(0).optimizedArtifactCandidateSelectionAppliedCount()); + assertEquals(0, summary.entries().get(0).optimizedArtifactCandidateSelectedIrReplacementCount()); + assertEquals("none", summary.entries().get(0).optimizedArtifactCandidateFirstBlocker()); + assertEquals("mutation-disabled", summary.entries().get(0).optimizedArtifactCandidateSelectionFirstBlocker()); assertEquals(1, summary.entries().get(0).constantFoldingPreviewPassCount()); assertEquals(3, summary.entries().get(0).constantFoldingPreviewCandidateCount()); assertEquals(5, summary.entries().get(0).constantFoldingPreviewSkippedCount()); @@ -126,6 +147,16 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { assertEquals(1, summary.totalRolledBackCount()); assertEquals(1, summary.totalApprovalTemplatePendingCount()); assertEquals(2, summary.totalApprovalTemplateNotApplicableCount()); + assertEquals("candidate-ready", summary.optimizedArtifactCandidateStatus()); + assertEquals(1, summary.totalOptimizedArtifactCandidateCount()); + assertEquals(1, summary.totalOptimizedArtifactCandidateReadyCount()); + assertEquals(0, summary.totalOptimizedArtifactCandidateBlockedCount()); + assertEquals(0, summary.totalOptimizedArtifactCandidateSelectionReadyCount()); + assertEquals(0, summary.totalOptimizedArtifactCandidateSelectionAppliedCount()); + assertEquals(0, summary.totalOptimizedArtifactCandidateSelectedIrReplacementCount()); + assertEquals(0, summary.totalOptimizedArtifactCandidateMutationAllowedCount()); + assertEquals("none", summary.optimizedArtifactCandidateFirstBlocker()); + assertEquals("mutation-disabled", summary.optimizedArtifactCandidateSelectionFirstBlocker()); assertEquals(1, summary.totalConstantFoldingPreviewPassCount()); assertEquals(3, summary.totalConstantFoldingPreviewCandidateCount()); assertEquals(5, summary.totalConstantFoldingPreviewSkippedCount()); @@ -161,6 +192,18 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { assertTrue(summary.toMarkdown().contains("- Proposal-only count: `2`")); assertTrue(summary.toMarkdown().contains("- Approval templates pending: `1`")); assertTrue(summary.toMarkdown().contains("- Approval templates not applicable: `2`")); + assertTrue(summary.toMarkdown().contains("- Optimized artifact candidate status: `candidate-ready`")); + assertTrue(summary.toMarkdown().contains("- Optimized artifact candidates: `1`")); + assertTrue(summary.toMarkdown().contains("- Optimized artifact candidates ready: `1`")); + assertTrue(summary.toMarkdown().contains("- Optimized artifact candidates blocked: `0`")); + assertTrue(summary.toMarkdown().contains("- Optimized artifact candidate first blocker: `none`")); + assertTrue(summary.toMarkdown().contains("- Optimized artifact candidate selection first blocker: `mutation-disabled`")); + assertTrue(summary.toMarkdown().contains("- Optimized artifact candidate selection ready count: `0`")); + assertTrue(summary.toMarkdown().contains("- Optimized artifact candidate selection applied count: `0`")); + assertTrue(summary.toMarkdown().contains("- Optimized artifact candidate selected IR replacement count: `0`")); + assertTrue(summary.toMarkdown().contains("- Optimized artifact candidate mutation-allowed count: `0`")); + assertTrue(summary.toMarkdown().contains("- Optimized artifact candidate selection applied: `false`")); + assertTrue(summary.toMarkdown().contains("- Optimized artifact candidate selected IR replacement: `false`")); assertTrue(summary.toMarkdown().contains("- Constant folding preview passes: `1`")); assertTrue(summary.toMarkdown().contains("- Constant folding preview candidates: `3`")); assertTrue(summary.toMarkdown().contains("- Constant folding preview skipped blockers: `5`")); @@ -192,8 +235,8 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { assertTrue(summary.toMarkdown().contains("- Review package production mutation: `disabled`")); assertTrue(summary.toMarkdown().contains("- Review package selected IR replacement: `disabled`")); assertTrue(summary.toMarkdown().contains("- Providers: `ir-optimizer:no-op:1=1, ir-optimizer:text-canonicalization:1=2`")); - assertTrue(summary.toMarkdown().contains("| `kernel-a.cl` | `recorded` | `3` | `2` | `1` | `1` | `1` | `2` | `3` | `5` | `4` | `2` | `6` | `3` | `4` | `pending-manual-review` | `preview-readiness-blocked-by-proof` | `ir-optimizer:no-op:1=1, ir-optimizer:text-canonicalization:1=2` |")); - assertTrue(summary.toMarkdown().contains("| `kernel-b.cl` | `missing` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `not-recorded` | `review-package-not-recorded` | `none` |")); + assertTrue(summary.toMarkdown().contains("| `kernel-a.cl` | `recorded` | `3` | `2` | `1` | `1` | `1` | `2` | `candidate-ready` | `none` | `mutation-disabled` | `3` | `5` | `4` | `2` | `6` | `3` | `4` | `pending-manual-review` | `preview-readiness-blocked-by-proof` | `ir-optimizer:no-op:1=1, ir-optimizer:text-canonicalization:1=2` |")); + assertTrue(summary.toMarkdown().contains("| `kernel-b.cl` | `missing` | `0` | `0` | `0` | `0` | `0` | `0` | `not-recorded` | `no-candidates` | `no-candidates` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `not-recorded` | `review-package-not-recorded` | `none` |")); } @Test diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCliTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCliTest.java index c3e0e7bd..09be07fb 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCliTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCliTest.java @@ -76,6 +76,34 @@ void rejectsCompletedReviewPackage() throws Exception { ); } + @Test + void rejectsOptimizedArtifactCandidateSelectionRegression() throws Exception { + Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + Files.writeString(artifact, failClosedEvidence("pending-manual-review", "true", + "manual-review-required", "1", "0") + .replace("optimizedArtifactCandidate.selectionApplied=false", + "optimizedArtifactCandidate.selectionApplied=" + Boolean.TRUE)); + + assertThrows( + IllegalStateException.class, + () -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main(new String[]{artifact.toString()}) + ); + } + + @Test + void rejectsOptimizedArtifactCandidateSelectedIrReplacementRegression() throws Exception { + Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + Files.writeString(artifact, failClosedEvidence("pending-manual-review", "true", + "manual-review-required", "1", "0") + .replace("optimizedArtifactCandidate.selectedIrReplacement.count=0", + "optimizedArtifactCandidate.selectedIrReplacement.count=1")); + + assertThrows( + IllegalStateException.class, + () -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main(new String[]{artifact.toString()}) + ); + } + @Test void rejectsRequiredPackageWithoutBlocker() throws Exception { Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); @@ -99,6 +127,18 @@ private static String failClosedEvidence( "runtimeEquivalenceReview.productionMutation=disabled", "runtimeEquivalenceReview.selectedIrReplacement=disabled", "runtimeEquivalenceReview.manualReviewOnly=true", + "optimizedArtifactCandidate.status=not-recorded", + "optimizedArtifactCandidate.count=0", + "optimizedArtifactCandidate.ready.count=0", + "optimizedArtifactCandidate.blocked.count=0", + "optimizedArtifactCandidate.selectionReady.count=0", + "optimizedArtifactCandidate.selectionApplied.count=0", + "optimizedArtifactCandidate.selectedIrReplacement.count=0", + "optimizedArtifactCandidate.mutationAllowed.count=0", + "optimizedArtifactCandidate.firstBlocker=no-candidates", + "optimizedArtifactCandidate.selectionFirstBlocker=no-candidates", + "optimizedArtifactCandidate.selectionApplied=false", + "optimizedArtifactCandidate.selectedIrReplacement=false", "reviewPackage.status=" + status, "reviewPackage.required=" + required, "reviewPackage.complete=false", diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java index f3182e19..d468ba43 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java @@ -1463,6 +1463,12 @@ void validationReporterWritesKernelLaunchAdvisoryReportAndWorkflowFragment() thr assertTrue(reportMarkdown.contains("`workload/perlin.cl` | `non-preferred-multiple`")); assertTrue(reportMarkdown.contains("## Runtime IR Optimizer Evidence")); assertTrue(reportMarkdown.contains("- Proposal-only count: `1`")); + assertTrue(reportMarkdown.contains("- Optimized artifact candidate status: `not-recorded`")); + assertTrue(reportMarkdown.contains("- Optimized artifact candidates: `0`")); + assertTrue(reportMarkdown.contains("- Optimized artifact candidate first blocker: `no-candidates`")); + assertTrue(reportMarkdown.contains("- Optimized artifact candidate selection first blocker: `no-candidates`")); + assertTrue(reportMarkdown.contains("- Optimized artifact candidate selection applied: `false`")); + assertTrue(reportMarkdown.contains("- Optimized artifact candidate selected IR replacement: `false`")); assertTrue(reportMarkdown.contains("- Constant folding preview passes: `1`")); assertTrue(reportMarkdown.contains("- Constant folding preview candidates: `2`")); assertTrue(reportMarkdown.contains("- Constant folding preview skipped blockers: `2`")); @@ -1490,7 +1496,7 @@ void validationReporterWritesKernelLaunchAdvisoryReportAndWorkflowFragment() thr assertTrue(reportMarkdown.contains("- Review package manual review only: `true`")); assertTrue(reportMarkdown.contains("- Review package production mutation: `disabled`")); assertTrue(reportMarkdown.contains("- Review package selected IR replacement: `disabled`")); - assertTrue(reportMarkdown.contains("| `workload/perlin.cl` | `recorded` | `1` | `1` | `0` | `0` | `0` | `0` | `2` | `2` | `3` | `1` | `2` | `2` | `2` | `not-recorded` | `none` | `ir-optimizer:text-canonicalization:1=1` |")); + assertTrue(reportMarkdown.contains("| `workload/perlin.cl` | `recorded` | `1` | `1` | `0` | `0` | `0` | `0` | `not-recorded` | `no-candidates` | `no-candidates` | `2` | `2` | `3` | `1` | `2` | `2` | `2` | `not-recorded` | `none` | `ir-optimizer:text-canonicalization:1=1` |")); assertTrue(reportMarkdown.contains("ir-optimizer:text-canonicalization:1=1")); assertTrue(summaryMarkdown.contains("## Kernel Launch Advisories")); assertTrue(summaryMarkdown.contains("- Blocking: `0`")); From 18b72fb144df041a9b13b3b22d3465e10ce67da4 Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Tue, 14 Jul 2026 02:20:20 +0300 Subject: [PATCH 26/32] wip: IR-Optimizer type 1 --- ROADMAP.md | 65 +- docs/IR-Optimizer.md | 36 +- docs/Runtime-Guide.md | 8 +- docs/Validation-and-Operations.md | 2 +- examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md | 10 +- .../examples/OptimizationJournalExample.java | 143 +- .../IrGpuGeneratedArtifactParityTest.java | 2 +- .../OptimizationJournalExampleTest.java | 88 +- ...SourceMaterializationProposalProvider.java | 127 ++ ...rClampMaterializationProposalProvider.java | 775 +++++++ ...oldingMaterializationProposalProvider.java | 1072 ++++++++++ ...zationMaterializationProposalProvider.java | 1546 ++++++++++++++ ...MadFmaMaterializationProposalProvider.java | 812 ++++++++ ...uIrMixMaterializationProposalProvider.java | 1131 ++++++++++ .../GpuIrOptimizationApprovalManifest.java | 254 +++ ...uIrOptimizationApprovalManifestLoader.java | 180 ++ ...OptimizationApprovalTemplateFormatter.java | 46 + ...uIrOptimizationApprovalTemplateResult.java | 4 + .../GpuIrProposalRuntimeBridgePass.java | 64 +- ...calCseMaterializationProposalProvider.java | 944 +++++++++ ...IrStepMaterializationProposalProvider.java | 902 ++++++++ ...adCodeMaterializationProposalProvider.java | 494 +++++ ...ptimizer.GpuIrOptimizationProposalProvider | 9 + ...ceMaterializationProposalProviderTest.java | 83 + ...mpMaterializationProposalProviderTest.java | 151 ++ ...ngMaterializationProposalProviderTest.java | 970 +++++++++ ...onMaterializationProposalProviderTest.java | 277 +++ ...maMaterializationProposalProviderTest.java | 164 ++ ...ixMaterializationProposalProviderTest.java | 278 +++ ...GpuIrOptimizationApprovalManifestTest.java | 244 ++- ...mizationApprovalTemplateFormatterTest.java | 61 + ...GpuIrOptimizationProposalRegistryTest.java | 8 + .../GpuIrProposalRuntimeBridgePassTest.java | 457 +++- ...seMaterializationProposalProviderTest.java | 269 +++ ...epMaterializationProposalProviderTest.java | 208 ++ ...deMaterializationProposalProviderTest.java | 157 ++ .../GpuRuntimeCompileArtifactDumper.java | 1832 ++++++++++++++++- .../GpuRuntimeIrOptimizationReport.java | 50 +- .../GpuRuntimeIrOptimizerRegistry.java | 10 +- .../runtime/GpuRuntimeIrSelection.java | 14 +- .../runtime/GpuRuntimeIrTypedNodeGraph.java | 2 +- .../opencl/OpenClGpuRuntimeBackend.java | 37 +- ...enClRuntimeIrOptimizerEvidenceSummary.java | 1513 +++++++++++++- ...untimeIrOptimizerEvidenceValidatorCli.java | 837 ++++++++ .../GpuRuntimeCompileArtifactDumperTest.java | 1111 +++++++++- .../runtime/GpuRuntimeIrSelectionTest.java | 36 + .../GpuRuntimeIrTypedNodeGraphTest.java | 37 + .../opencl/OpenClGpuRuntimeBackendTest.java | 110 + ...RuntimeIrOptimizerEvidenceSummaryTest.java | 619 +++++- ...meIrOptimizerEvidenceValidatorCliTest.java | 550 +++++ .../opencl/OpenClValidationReportTest.java | 13 +- 51 files changed, 18699 insertions(+), 113 deletions(-) create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrBackendNeutralSourceMaterializationProposalProvider.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrClampMaterializationProposalProvider.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingMaterializationProposalProvider.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrLoopVectorizationMaterializationProposalProvider.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMadFmaMaterializationProposalProvider.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMixMaterializationProposalProvider.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalManifestLoader.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrSafeLocalCseMaterializationProposalProvider.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrStepMaterializationProposalProvider.java create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedDeadCodeMaterializationProposalProvider.java create mode 100644 ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrBackendNeutralSourceMaterializationProposalProviderTest.java create mode 100644 ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrClampMaterializationProposalProviderTest.java create mode 100644 ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingMaterializationProposalProviderTest.java create mode 100644 ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrLoopVectorizationMaterializationProposalProviderTest.java create mode 100644 ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMadFmaMaterializationProposalProviderTest.java create mode 100644 ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMixMaterializationProposalProviderTest.java create mode 100644 ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrSafeLocalCseMaterializationProposalProviderTest.java create mode 100644 ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrStepMaterializationProposalProviderTest.java create mode 100644 ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedDeadCodeMaterializationProposalProviderTest.java create mode 100644 processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrTypedNodeGraphTest.java diff --git a/ROADMAP.md b/ROADMAP.md index 73562e9e..f56fb75e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -311,8 +311,8 @@ Post-I2 optimizer frontier, not blocking I2 closure: Add dominance, side-effect/evaluation-order, alias/memory-space, and backend/device proof checks; keep mutating paths blocked unless all proof layers are present and consistent. - [ ] Expand canonicalization and common-computation detection beyond the current conservative slices. Extend numeric canonicalization, helper/intrinsic candidate grouping, and proof-ready vs blocked candidate artifacts only when Java semantics and runtime evidence are clear. -- [ ] Grow auto-vectorization beyond current fixed-width loop detection. - Add broader coordinate/noise/math candidate families, richer layout blockers, backend-sensitive vector decisions, and prototype-only rewrite artifacts while production `apply(...)` remains fail-closed/no-op. +- [ ] Grow auto-vectorization beyond the first fixed-width loop materializer. + The first review-only materializer now recognizes fixed-width contiguous float reductions and emits a `vload4` plus ordered scalar reduction candidate for review artifacts only. Remaining work is broader coordinate/noise/math candidate families, richer layout blockers, backend-sensitive vector decisions, typed graph materialization, and prototype-only rewrite artifacts while production `apply(...)` remains fail-closed/no-op. - [x] Add runtime-equivalence proof readiness artifacts for optimizer families selected for promotion. Runtime optimizer drift artifacts now aggregate optimizer-family counts, promotion-ready family counts, and per-family proof/rollback/failure summaries across workload kernels. Production explainability consumes those fields through the workload gate and exposes an explicit optimizer-family A1/A2 preflight, so promotion-ready optimizer families stay blocked unless runtime-equivalence history baseline evidence exists. The current proof surface is still conservative: it records accepted/blocking proof artifacts and keeps production mutation disabled, while deeper per-family CPU/reference and pre/post result payloads remain future frontier work. - [x] Wire optimizer evidence into A1/A2 operational validation. @@ -442,9 +442,11 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i - [x] Add a diagnostic runtime IR peephole / InstCombine entrypoint. `GpuRuntimeIrPeepholePass` now runs through the explicit `GpuRuntimeIrOptimizationPass` SPI, requires `@GPUOptimize(fastMath = true)`, consumes typed `ir-tree-v1` nodes, refuses text/regex rewriting, and emits blocking proof fields with per-rule counters. The first structural matcher detects `a * b + c` / `c + a * b` candidates without mutating IR; strict methods are skipped before the pass runs. - [ ] Implement mutating runtime IR peephole / InstCombine rewrites. - Build proof-backed local expression-tree rewrites such as `a + t * (b - a) -> mix(a, b, t)`, `a * b + c -> mad/fma(a, b, c)`, `min(max(x, lo), hi) -> clamp(x, lo, hi)`, ternary step forms -> `step(edge, x)`, and small fixed-width dot-product patterns. Mutation must stay fail-closed until structural replacement, typed-graph validation, runtime-equivalence, rollback, and A1/A2 evidence are stable. -- [ ] Add a safe IR pattern-matching and rewrite visitor for peephole passes. - The current typed preflight safely detects the initial `mad/fma` candidate shape without editing nodes. Next, add reusable typed-node traversal/replacement APIs that can prove complete-pattern replacement before editing anything. Partial matches must produce diagnostics only. Rule implementations should remain isolated (`mix`, `mad/fma`, `clamp`, `step`, `dot`) and report applied/skipped/blocked counts per rule so optimizer drift artifacts can detect regressions. + The diagnostic registry already contains isolated read-only rules for `mad/fma`, `clamp`, `mix`, `step`, and `dot`, including structural replacement plans, graph-patch previews, artifact-envelope previews, proof-binding previews, and selection preflights. The remaining work is the real rewrite engine: reserve node ids, construct replacement intrinsic nodes, apply graph patches to a distinct optimized artifact, validate the transformed graph/artifact, emit accepted runtime-equivalence + rollback evidence, and only then let future selection gates consider the optimized artifact. Mutation must stay fail-closed until structural replacement, typed-graph validation, runtime-equivalence, rollback, and A1/A2 evidence are stable. +- [ ] Extend peephole matchers for workload-derived algebraic variants before materialization. + Basic diagnostic matchers are in place, but real generated kernels expose additional shapes. `clamp` should keep covering generated `min(max(wave, lo), hi)` call trees and add regression fixtures from actual generated OpenCL/typed IR. `step` should distinguish exact `<` / `>=` forms from strict `>` / `<=` ternaries such as `(blend > threshold) ? 1.0F : 0.0F`, because equality behavior must be proven before replacing with OpenCL `step(edge, x)`. `mix` should grow beyond `a + t * (b - a)` / `t * (b - a) + a` into expanded and previously-lowered forms such as `t * b + (1 - t) * a` and `mad(t, b, (1 - t) * a)`, without assuming unsafe reassociation. +- [x] Add a safe IR pattern-matching and rewrite visitor for peephole passes. + The read-only typed preflight layer now detects complete and partial peephole candidates without editing nodes. Partial matches produce diagnostics only. Rule implementations remain isolated (`mix`, `mad/fma`, `clamp`, `step`, `dot`) and report skipped/blocked candidate state per rule so optimizer drift artifacts can detect regressions. `GpuRuntimeIrPeepholeReplacementPlan` now adds the first read-only typed replacement-planning contract. The `mad/fma` rule records root, covered, and input node ids plus complete/partial blocker state for detected `a*b+c` / `c+a*b` shapes, `clamp` records read-only complete plans for `min(max(x, lo), hi)` typed call shapes, `step` records read-only complete plans for simple `x < edge ? 0 : 1` / `x >= edge ? 1 : 0` ternary shapes, `dot` records read-only plans for fixed-width additive multiply trees such as `(a0*b0)+(a1*b1)`, and `mix` records read-only complete plans for `a + t * (b - a)` / `t * (b - a) + a`. `GpuRuntimeIrPeepholeTypedRewriteVisitor` now adds shared read-only traversal preflight, `GpuRuntimeIrPeepholeReplacementBlueprint` adds a target intrinsic-call blueprint, `GpuRuntimeIrPeepholeRewriteTransactionPreflight` adds a transaction-shape preflight, `GpuRuntimeIrPeepholeNodeIdAllocationPreflight` adds deterministic candidate id diagnostics above transaction-ready plans, `GpuRuntimeIrPeepholeReplacementNodePreflight` adds a replacement-node construction preview that pairs the candidate id with the intrinsic-call blueprint without building an IR node, `GpuRuntimeIrPeepholeGraphPatchPreflight` adds a read-only replace/remove/retain/insert graph-patch preview without applying it, and `GpuRuntimeIrPeepholeTransformedGraphPreflight` adds a materialization-key/original-IR-identity preview while keeping `transformedGraphIdentity=not-built`. Together they record deterministic visit order, blueprint target node kind/operation, argument ids/roles, replaced/removed/retained/inserted node ids, planned added-node count, candidate ids from current graph max id, candidate replacement-node id, materialization key, ready/blocked counts, first blockers, and disabled id reservation / allocator application / replacement-node build / graph-patch application / transformed-graph build / graph-rewrite / replacement-builder / transformed-IR / mutation / selected-IR flags. Rewrite-sketch overlap/conflict detection, `rewriteSelection.*` selection-readiness preflight, `rewriteProof.*` proof/runtime-equivalence/rollback preflight, and `rewriteReviewPackage.*` manual-review package preflight now make conflict-resolution, rewrite-builder, runtime-equivalence payload, rollback evidence, approval, mutation, selected-IR, and package-completeness blockers explicit before any rewrite engine exists. The pass still reports `rewrite-engine-not-implemented` and never mutates selected IR. All current peephole families now have diagnostic matchers, shared traversal evidence, replacement blueprints, transaction-shape evidence, node-id allocation previews, replacement-node construction previews, graph-patch previews, and transformed-graph materialization previews; the remaining work is real id reservation, actual replacement-node construction, graph rewrite mechanics, transformed IR artifact construction, and proof gates. `GpuRuntimeIrPeepholeIrArtifactEnvelopePreflight` now adds the next read-only artifact-envelope boundary above transformed-graph materialization. It records original IR identity, absent transformed-graph / optimized-artifact identities, deterministic envelope key, proof anchor, rollback anchor, ready/blocked counts, first blocker, and disabled artifact build / optimized-artifact build / transformed-IR / graph-patch / graph-rewrite / mutation / selected-IR flags before any optimized artifact can exist. `GpuRuntimeIrPeepholeArtifactProofBindingPreflight` now adds the proof-binding boundary above that envelope. It records proof/review blockers, runtime-equivalence payload completeness, rollback evidence, approval state, ready/blocked counts, and disabled proof / rollback / approval binding plus optimized-artifact / transformed-IR / mutation / selected-IR flags, so the future artifact handoff cannot be mistaken for accepted proof. @@ -455,9 +457,11 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i Structured ASM ingestion now writes the same `IrGpuOptimizerPolicyMetadata` contract as Java source. Missing ASM policy resolves to `default-strict`, the serialized `kernel.irgpu.properties` manifest carries `optimizerPolicy.fastMath=false` and `optimizerPolicy.source=default-strict`, and parser round-trip coverage pins the storage/runtime boundary. Future ASM compile options may opt in only through the same shared metadata and evidence gates. - [x] Persist fast-math and optimizer-policy metadata in `IrGpu` storage. `kernel.irgpu.properties` now carries `optimizerPolicy.fastMath` and `optimizerPolicy.source`, with backward-compatible strict defaults in the parser. Runtime optimizer requests read the loaded `IrGpu` policy, and passes marked `requiresFastMath()` are fail-closed/skipped when fast math is false or absent. Future rule-family toggles remain open. -- [ ] Add proof, runtime-equivalence, and rollback gates for peephole rewrites. +- [x] Add read-only proof, runtime-equivalence, and rollback gates for peephole rewrites. `GpuRuntimeIrPeepholeRewriteProofReadiness` now exposes the read-only proof preflight above selection readiness: proof required/accepted state, runtime-equivalence payload presence/completeness, rollback evidence, approval acceptance, original IR identity, absent transformed IR identity, and disabled mutation / selected-IR replacement. `GpuRuntimeIrPeepholeRewriteReviewPackage` now sits above that as the package boundary: it records required/complete status, first blocker, conflict count, proof acceptance, runtime-equivalence payload, rollback, approval, manual-review-only state, and disabled mutation / selection / selected-IR replacement. The pass now emits these gates both at aggregate level and per rule through `rule.N.rewriteSelection.*`, `rule.N.rewriteProof.*`, and `rule.N.rewriteReviewPackage.*`, so drift artifacts can distinguish a rule blocked by structural validation from one blocked by missing runtime-equivalence payload, rollback evidence, approval, or incomplete review packaging. Real rewrites still must emit accepted proof artifacts with optimizer family (`peephole`, `instcombine`, or per-rule families), rule counters, fast-math policy, original/transformed IR identities, and compact/durable runtime-equivalence payload evidence before this item can close. Failed proofs, incomplete pattern rewrites, runtime-equivalence failures, rollback diagnostics, or incomplete review packages must keep the original IR selected. -- [ ] Surface peephole telemetry in optimizer drift and promotion artifacts. +- [ ] Bind accepted proof, runtime-equivalence, and rollback evidence to materialized peephole rewrites. + The preflight gates exist, but no mutating peephole rewrite currently produces a transformed optimized artifact with accepted proof payloads. Close this only when materialized `mad/fma`, `clamp`, `step`, `mix`, or `dot` rewrites attach compact and durable runtime-equivalence cases, rollback evidence, approval-template data, original/transformed identities, and fail-closed diagnostics that the production gates can evaluate without changing safe defaults. +- [x] Surface peephole telemetry in optimizer drift and promotion artifacts. `runtime-optimizer-drift.properties` now surfaces read-only replacement-plan evidence with aggregate complete/partial counts, structural validation counts, rewrite-visitor ready/blocked counts, replacement-blueprint ready/blocked counts, rewrite-transaction ready/blocked counts, node-id allocation ready/blocked counts, replacement-node preview ready/blocked counts, graph-patch preview ready/blocked counts, transformed-graph materialization ready/blocked counts, rewrite-sketch ready/blocked counts, overlap/conflict counters, aggregate and per-rule `rewriteSelection.*` status/first-blocker guardrails, aggregate and per-rule `rewriteProof.*` proof/runtime-equivalence/rollback blockers, aggregate and per-rule `rewriteReviewPackage.*` package-completeness blockers, and compact `optimizerRule.*` summaries from the existing `rule.N.*` proof fields, so CI can detect `mad/fma`, `clamp`, `step`, `dot`, and `mix` planning drift before any rewrite engine exists. Workload gates carry those visitor/blueprint/transaction/allocation/replacement-node/graph-patch/transformed-graph fields forward with the rest of the drift payload, and compact workload summaries, OpenCL validation Markdown/history, I3 summaries, and production-promotion explainability continue to treat them as evidence-only telemetry. Production promotion must remain disabled until these rule families have stable A1/A2 runtime evidence and backend/vendor confidence. Drift artifacts and workload gates now also carry `irArtifactEnvelope.*` aggregate/per-rule telemetry so CI can see the future optimized-artifact envelope boundary separately from transformed-graph materialization, while `artifactEnvelopeBuilt=false`, `optimizedArtifactBuilt=false`, `mutationAllowed=false`, and `selectedIrReplacement=false` stay pinned. Drift artifacts and workload gates now also carry `artifactProofBinding.*` aggregate/per-rule telemetry, keeping proof binding, rollback binding, approval binding, optimized artifact construction, mutation, and selected-IR replacement explicitly disabled until accepted proof and complete review-package evidence exist. @@ -683,25 +687,60 @@ Current rule: `ir-validation` remains strictly read-only. Optimizer modules may - [x] Define backend-neutral optimizer policy controls. `GpuIrOptimizationPolicy` now provides the backend-neutral optimizer control surface for optimizer profile, optimization level, proposal-only mode, mutation permission, fast-math permission, vendor adaptation, register-pressure splitting, rollback requirement, and proof requirement. `GpuIrOptimizationProposalRequest` preserves its existing constructors while exposing the normalized policy view and serializing policy fields into context metadata, so proposal providers and runtime adapters can consume one shared policy contract without depending on OpenCL-only flags or runtime-local prototype details. - [x] Add first backend-neutral optimizer families only after the contract is stable. - `GpuIrTextCanonicalizationProposalProvider` handles semantics-neutral transitional text normalization, and `GpuIrHelperDependencyDeduplicationProposalProvider` now performs the first backend-neutral metadata cleanup by removing duplicate helper dependencies while preserving first-occurrence order and leaving body/typed/backend output data unchanged. More aggressive families such as constant folding, dead-code cleanup, safe local CSE, private-array scalarization, and typed-body peephole rewrites remain future proposal-only candidates until pre/post validation plus runtime-equivalence evidence are available. + `GpuIrTextCanonicalizationProposalProvider` handles semantics-neutral transitional text normalization, and `GpuIrHelperDependencyDeduplicationProposalProvider` now performs the first backend-neutral metadata cleanup by removing duplicate helper dependencies while preserving first-occurrence order and leaving body/typed/backend output data unchanged. `GpuIrBackendNeutralSourceMaterializationProposalProvider` now adds the first review-only backend-neutral source materialization candidate: OpenCL runtime review requests must prove reconstructed source emission, then receive a distinct optimized artifact with `backendNeutralSourceReady` metadata while the selected runtime IR stays unchanged. Broader aggressive families such as private-array scalarization, cross-control-flow CSE, broader dead-code cleanup, and typed-body peephole rewrites remain future proposal-only candidates until pre/post validation plus runtime-equivalence evidence are available. `GpuIrConstantFoldingPreviewProposalProvider` now adds the first constant-folding proof surface by scanning typed IR for simple plain-decimal literal binary expressions and recording candidate, operator, numeric-kind, first-fold, skipped-blocker, policy, proof, and safety evidence without proposing a rewrite. It explicitly records divide-by-zero, non-even division, unsupported operator, non-literal operand, and non-plain literal blockers, plus `runtimeEquivalenceRequiredBeforeRewrite`, `approvalRequiredBeforeRewrite`, and unresolved integer-overflow / floating-rounding proof flags so the future rewrite path cannot graduate without stronger evidence. - OpenCL runtime validation now aggregates that preview evidence into `constantFoldingPreview.*` fields in `runtime-ir-optimizer-evidence.properties` and surfaces pass, candidate, and skipped-blocker counts in the `Runtime IR Optimizer Evidence` report section, making constant-folding readiness visible without enabling optimized IR selection. + `GpuIrConstantFoldingMaterializationProposalProvider` now adds the first real review-only transform candidate. It materializes plain 32-bit integer literal unary `-` plus binary `+`, `-`, `*`, and exact `/` folds when the divisor is non-zero, the division is even, and it can update both the typed node and matching `ir-text-v1` expression consistently; nested safe literal folds are rewritten to a fixed point, so a review candidate can collapse shapes such as `((-2) * 4)` into `-8` or `((2 + 3) * 4)` into `20` in one optimizer pass. It also materializes the first constrained pure-symbolic identity slice: `x + 0`, `0 + x`, `x - 0`, `x * 1`, `1 * x`, and `x / 1` can rewrite to `x`, where the retained side may be a deterministic pure expression made from named variables, plain int32 literal leaves, arithmetic binary nodes, and unary minus nodes; mixed identity chains such as `((x / 1) + 0)`, repeated identity expressions across multiple roots, and multiple method bodies are materialized one reachable rewrite per fixed-point pass so the optimized text body and typed roots stay aligned. `x * 0`, `1 / x`, and broader division algebra remain blocked until stronger side-effect / unused-operand and equivalence proof exists. OpenCL runtime review requests also require regenerated source emission before a proposal is returned. The default runtime bridge keeps the original IR selected, carries the transformed artifact as `candidateArtifact`, records a compact exact-int or symbolic-identity `runtimeEquivalencePayload.*` with CPU/reference, pre/post, tolerance, failure-fixture, indexed case fields, per-case `MethodName`, `NodeId`, and `RewriteKind`, `runtimeEquivalencePayload.CaseIdentity=method-name-and-node-id`, `methodBody.rewriteScope=all-method-bodies`, `literalRewrite.count`, `identityRewrite.count`, `safety.identityOperandKind=GpuIrPureExpression`, `safety.identityRetainedExpressionKinds`, `fixedPoint.pass.count`, `fixedPoint.reachableNodeScan`, `fixedPoint.rewriteGranularity`, and `bodyTextReplacement.scope`, and preserves `selectionApplied=false`, `optimizedArtifactSelected=false`, and `selectedIrReplacement=false` guardrails. + OpenCL runtime validation now aggregates preview evidence into `constantFoldingPreview.*` fields, source-ready materialization evidence into `backendNeutralSourceMaterialization.*`, and materialized review-candidate evidence into `constantFoldingMaterialization.*` fields in `runtime-ir-optimizer-evidence.properties`. The report surfaces pass, candidate, source-ready, skipped-blocker, materialized-node, literal-rewrite, identity-rewrite, runtime-equivalence payload, status, and first-blocker counts; backend-neutral source materialization can become approval-template pending without requiring runtime-equivalence review, materialized constant folding can reach runtime-equivalence `review-ready`, and `reviewPackage.*` remains pending manual approval while optimized IR selection stays disabled. `GpuIrSafeLocalCsePreviewProposalProvider` now adds the matching safe-local-CSE proof surface by scanning typed IR for repeated local expressions and recording expression, candidate-expression, duplicate-expression, equivalence-class, blocker, policy, proof, and safety evidence without proposing a rewrite. It explicitly records unsupported-operator, impure-operand, and control-flow-boundary blockers plus required runtime-equivalence/approval gates and unresolved dominance / side-effect-freedom proof flags, so CSE readiness can be measured without enabling optimized IR selection. OpenCL runtime validation now aggregates that preview evidence into `safeLocalCsePreview.*` fields in `runtime-ir-optimizer-evidence.properties` and surfaces pass, candidate-expression, duplicate-expression, and blocker counts in the `Runtime IR Optimizer Evidence` report section. + `GpuIrSafeLocalCseMaterializationProposalProvider` now adds the first review-only safe-local-CSE transform. It reuses an already materialized local binding in straight-line `ir-text-v1` bodies, rewrites repeated pure binary expressions to existing local references in the optimized review artifact, runs to a fixed point over reachable roots, updates typed nodes and body text together, emits compact runtime-equivalence payload cases with `RewriteKind=safe-local-cse-reuse-existing-local`, and blocks on control-flow boundaries, missing text patterns, missing existing bindings, unsupported/impure operands, or OpenCL review-source emission failures. It does not introduce new temporaries, mutate the original artifact, select optimized IR, or enable production mutation. + `GpuIrMadFmaMaterializationProposalProvider` now adds the first review-only typed peephole materializer. It requires fast-math permission, rewrites reachable `a * b + c` / `c + a * b` typed roots and matching `ir-text-v1` text to `intrinsic(mad template="" args=[a, b, c])`, emits per-case `RewriteKind=mad-fma-to-opencl-mad` runtime-equivalence payload fields, verifies OpenCL review-source emission, and stays proposal-only through the bridge so `optimized.backend.opencl-c` can show `mad(...)` while `backend.opencl-c` remains selected from the original runtime IR. Runtime evidence now lifts this into `madFmaMaterialization.*` with pass, candidate, transformed-node, body-text-replacement, fixed-point-pass, skipped fast-math/body-text-pattern, fast-math policy, payload, status, and first-blocker fields. + `GpuIrClampMaterializationProposalProvider` now adds the first review-only non-fast-math typed peephole materializer for generated clamp trees. It recognizes conservative reachable `min(max(x, lo), hi)` typed intrinsic-call shapes, rewrites matching `ir-text-v1` text to `intrinsic(clamp template="" args=[x, lo, hi])`, replaces the typed root with a `GpuIrIntrinsicCall`, emits per-case `RewriteKind=min-max-to-opencl-clamp` runtime-equivalence payload fields, verifies OpenCL review-source emission, and stays proposal-only through the bridge so `optimized.backend.opencl-c` can show `clamp(...)` while `backend.opencl-c` remains selected from the original runtime IR. Reordered min/max forms and broader algebraic variants remain open until edge-case semantics are proven. + `GpuIrStepMaterializationProposalProvider` now adds review-only non-fast-math materialization for typed ternary masks. Inclusive comparisons such as `a >= b ? 1.0F : 0.0F` become direct `step(b, a)` review intrinsics, while strict comparisons such as `a > b ? 1.0F : 0.0F` become `(1.0F - step(a, b))` so equality and NaN behavior stay equivalent to the original ternary. The proof records `directStep.count`, `invertedStep.count`, `RewriteKind=ternary-mask-to-opencl-step` / `ternary-mask-to-inverted-opencl-step`, strict-comparison/equality/NaN preservation, OpenCL review-source readiness, and keeps the selected runtime IR unchanged. + `GpuIrMixMaterializationProposalProvider` now adds review-only materialization for typed interpolation trees. Canonical `a + t * (b - a)` / `t * (b - a) + a` forms become `mix(a, b, t)` without fast-math permission, while expanded weighted forms and generated `mad(b, t, (1.0F - t) * a)` forms require fast-math permission before review materialization. The optimizer-journal shape `mad(blend, isSolid, ((1.0F - isSolid) * (clampedWave * 0.1F)))` can now become `mix(clampedWave * 0.1F, blend, isSolid)` in `optimized.backend.opencl-c` while the selected runtime IR remains unchanged. + `GpuIrLoopVectorizationMaterializationProposalProvider` now adds the first review-only loop vectorization materializer. It recognizes strict fixed-width contiguous float reductions such as `sum += input[id * 4 + i]`, equivalent `mad(id, 4, i)` / `intrinsic(mad ...)` index shapes, straight-line precomputed base locals such as `var int base = id * 4; sum += input[base + i]`, reversed `i + base` forms, and constant-offset bases such as `sum += input[((id * 4) + 8) + i]`. Local base aliases are deliberately conservative: only preceding straight-line `int` declarations are considered, assignments invalidate aliases, and control-flow boundaries clear the alias set. The pass rewrites the optimized review body to `vload4` plus ordered scalar lane reduction through `Float4`, records per-case `RewriteKind=fixed-width-contiguous-vload4-ordered-reduction`, intentionally avoids `dot` / reassociation, requires no fast-math permission, reconstructs a matching review typed body for the supported `ir-text-v1` subset, falls back to typed-body invalidation only when surrounding body text cannot be reconstructed, and leaves the original runtime IR selected. `GpuIrTypedDeadCodePreviewProposalProvider` now adds the first typed dead-code proof surface by walking typed-body roots and recording node, reachable-node, unreachable-node, unreachable-kind, blocker, policy, proof, and safety evidence without proposing a rewrite. It explicitly records missing-root, missing-child-reference, and side-effecting-unreachable-node blockers plus required runtime-equivalence/approval gates and unresolved side-effect-freedom proof flags, so dead-code cleanup readiness can be measured without enabling optimized IR selection. OpenCL runtime validation now aggregates that preview evidence into `typedDeadCodePreview.*` fields in `runtime-ir-optimizer-evidence.properties` and surfaces pass, unreachable-node, and blocker counts in the `Runtime IR Optimizer Evidence` report section. + `GpuIrTypedDeadCodeMaterializationProposalProvider` now adds the first non-constant review-only materialized transform. It removes only unreachable pure typed nodes from typed bodies, leaves method text bodies unchanged, blocks on missing roots, missing child references, or side-effecting unreachable nodes, and records per-case method/node/rewrite-kind runtime-equivalence payload evidence with `runtimeEquivalencePayload.CaseIdentity=method-name-and-node-id`. The default bridge still keeps the original runtime IR selected and treats the optimized artifact as a review candidate only. + OpenCL runtime validation now aggregates safe-local-CSE materialization into `safeLocalCseMaterialization.*` fields in `runtime-ir-optimizer-evidence.properties` and surfaces pass, local-binding, transformed-node, body-text-replacement, payload, status, and first-blocker counts in the `Runtime IR Optimizer Evidence` report section. It also aggregates mad/FMA materialization into `madFmaMaterialization.*` fields and surfaces pass, candidate, transformed-node, body-text-replacement, fixed-point-pass, skipped-blocker, fast-math policy, payload, status, and first-blocker counts. Clamp, step, and mix materialization are aggregated into `clampMaterialization.*`, `stepMaterialization.*`, and `mixMaterialization.*` fields and surface candidate/transformed-node/body-text-replacement counts, direct versus inverted step accounting, canonical/expanded/MAD-expanded mix accounting, fast-math or strict-float safety evidence, payload, status, and first-blocker counts. Loop-vectorization materialization is aggregated into `loopVectorizationMaterialization.*` fields and surfaces pass, candidate, transformed-loop, changed-method-body, body-text-replacement, typed-body-materialization/invalidation, skipped-blocker, payload, status, and first-blocker counts. Typed dead-code materialization is aggregated into `typedDeadCodeMaterialization.*` fields and surfaces pass, typed-node, unreachable-node, removed-node, payload, status, first-blocker, and side-effect-freedom counts. `runtimeEquivalenceReview.*` now includes `constant-folding-materialization`, `safe-local-cse-materialization`, `mad-fma-materialization`, `clamp-materialization`, `step-materialization`, `mix-materialization`, `loop-vectorization-materialization`, and `typed-dead-code-materialization` readiness in the family summary before review-package approval can progress. The same runtime evidence now includes `previewReadiness.*` aggregate fields over constant folding, safe-local CSE, and typed dead-code cleanup. The aggregate records overall status, recorded/candidate/blocked family counts, and a per-family status summary (`not-recorded`, `no-candidates`, `candidates-recorded`, `blocked-by-proof`, or `ready-for-runtime-equivalence-review`) so CI can track optimizer-family readiness without enabling production mutation or selected-IR replacement. - `runtimeEquivalenceReview.*` now sits above `previewReadiness.*` as a fail-closed review gate. It records whether preview families are eligible for runtime-equivalence review, whether review evidence is required, the first blocker, the family summary, and explicitly disabled production mutation / selected-IR replacement so review readiness cannot be confused with production optimization enablement. + `runtimeEquivalenceReview.*` now sits above `previewReadiness.*` as a fail-closed review gate. It records whether preview and materialized families are eligible for runtime-equivalence review, whether review evidence is required, the first blocker, the family summary, and explicitly disabled production mutation / selected-IR replacement so review readiness cannot be confused with production optimization enablement. `reviewPackage.*` now sits above runtime-equivalence review as the manual-review bundle boundary. It records package status, requirement/completeness, first blocker, proposal-pass count, pending approval count, original/optimized IR and proof-summary requirements, and repeats manual-review-only plus disabled production mutation / selected-IR replacement guardrails so review packaging remains evidence-only. - `validateOpenClRuntimeIrOptimizerEvidence` now validates those raw runtime evidence files in the OpenCL operational/NVIDIA routines, failing CI if optimized-artifact candidate or review-package guardrails disappear, if production mutation or selected-IR replacement becomes enabled, if candidate selection is applied, or if a required package is marked complete/missing a blocker before an activation design exists. + `GpuIrProposalRuntimeBridgePass` now composes proposal-only materialized candidates through a separate review-working artifact: later providers can see earlier review transforms such as constant folding before safe-local-CSE and `mad/fma` materialization run, while the runtime-selected `artifact` remains the original unless mutation is explicitly allowed. This makes `optimized.irgpu.properties` / `optimized.backend.opencl-c` useful as an accumulated before/after review file without changing `backend.opencl-c`. + `validateOpenClRuntimeIrOptimizerEvidence` now validates those raw runtime evidence files in the OpenCL operational/NVIDIA routines, failing CI if optimized-artifact candidate or review-package guardrails disappear, if production mutation or selected-IR replacement becomes enabled, if candidate selection is applied, or if a required package is marked complete/missing a blocker before an activation design exists. It now also validates present materialization-family evidence: `safeLocalCseMaterialization.*` cannot reach review-ready without existing-local reuse, dominance/side-effect proof, matching text replacement, runtime-equivalence/approval requirements, and passed payloads; `madFmaMaterialization.*` cannot lose matching text replacements, fast-math policy evidence, runtime-equivalence/approval requirements, passed payloads, or a consistent first blocker; `clampMaterialization.*` cannot lose matching text replacement, strict-float/no-fast-math safety, runtime-equivalence/approval requirements, passed payloads, or a consistent first blocker; `stepMaterialization.*` cannot lose direct/inverted step accounting, strict comparison/equality/NaN safety, matching text replacement, runtime-equivalence/approval requirements, passed payloads, or a consistent first blocker; `mixMaterialization.*` cannot lose canonical/expanded/MAD-expanded accounting, required fast-math/reassociation evidence, matching text replacement, runtime-equivalence/approval requirements, passed payloads, or a consistent first blocker; `loopVectorizationMaterialization.*` cannot reach review-ready without matching text replacements, fixed trip-count proof, contiguous-load proof, ordered-reduction preservation, typed-body materialization/invalidation accounting, runtime-equivalence/approval requirements, and passed payloads; and `typedDeadCodeMaterialization.*` cannot reach review-ready without consistent node counts, side-effect proof, runtime-equivalence/approval requirements, and passed payloads. - [x] Keep vendor optimization as a separate optional provider layer. `ir-vendor-optimizer` now exists as a separate optional module skeleton. It contributes vendor proposals only through `GpuIrVendorOptimizationProposalProvider` and `GpuIrVendorOptimizationProposalRegistry`, while the default runtime bridge keeps loading only backend-neutral proposal providers. Future vendor modules may consume target device profile, backend compiler feedback, resource metrics, driver quirks, launch advisories, and promotion evidence, but missing modules, missing matching device evidence, or disabled vendor adaptation must leave backend-neutral IR selection unchanged. - [x] Gate production optimizer selection through approval-bound artifacts. - `GpuIrOptimizationApprovalManifest` now writes and validates a review-only approval template for one immutable optimizer proposal. It is bound to optimizer id/version, original and optimized `IrGpu` identities, backend target, optimization profile, optional device vendor/label context, proof source/verdict, and rollback requirement, and is intended for `META-INF/javatogpu/ir-optimization-approvals/`. A valid manifest remains manual-review-only and does not select optimized IR or grant production mutation by itself; missing, stale, or mismatched approval keeps selection external and fail-closed. - `GpuIrOptimizationApprovalTemplateFormatter` now bridges proposal evidence to pending approval templates and explicitly returns `not-applicable` for preview/no-change proposals, making the approval boundary visible before applied rewrites exist. + `GpuIrOptimizationApprovalManifest` now writes and validates a review-only approval template for one immutable optimizer proposal. It is bound to optimizer id/version, original and optimized `IrGpu` identities, backend target, optimization profile, optional device vendor/label context, proof source/verdict, required runtime-equivalence payload resource/comparison/case metadata, and rollback requirement, and is intended for `META-INF/javatogpu/ir-optimization-approvals/`. Required payloads must be present, passed, component-complete, and carry at least one case before approval validation can pass. A valid manifest remains manual-review-only and does not select optimized IR or grant production mutation by itself; missing, stale, mismatched, or payload-incomplete approval keeps selection external and fail-closed. + `GpuIrOptimizationApprovalTemplateFormatter` now bridges proposal evidence to pending approval templates, emits deterministic `manifest.resourcePath` / `approvalTemplate.resourcePath` values under `META-INF/javatogpu/ir-optimization-approvals/`, exposes compact `runtimeEquivalencePayload.*` template fields, and explicitly returns `not-applicable` for preview/no-change proposals, making the approval boundary visible before applied rewrites exist. `GpuIrOptimizationApprovalManifestLoader` now loads matching approved manifests from the classpath and validates them against the proposal/request binding as review-only evidence. Runtime IR optimizer evidence now carries those template payload fields under `pass.N.approvalTemplate.field.*` plus aggregate `approvalTemplate.runtimeEquivalencePayloadRequired/Present/Passed/Complete.count` and `reviewPackage.approvalManifest.*` fields so review packages can show the exact payload binding, loaded/accepted manifest state, and manifest blocker without marking the package complete or selecting optimized IR. - [x] Publish docs for optimizer authors and users before enabling real rewrites. `docs/IR-Optimizer.md` now documents the validator/optimizer/vendor split, proposal-only runtime bridge behavior, backend-neutral and vendor ServiceLoader packaging, approval-template artifacts, approval manifest resource path, and safe default behavior. Runtime and validation docs now point operators to `runtime-ir-optimizer-evidence.properties` and explain that optimizer evidence is report-only: projects that do not add optimizer modules keep the current no-op runtime path, and approval templates do not enable production mutation or selected-IR replacement by themselves. +### I4.1 IR-Optimizer work split + +- [x] Close the first IR-Optimizer alpha evidence boundary. + The current alpha has optional module wiring, immutable proposal contracts, validation sandwich, backend-neutral and vendor-provider SPI, accumulated review artifacts, before/after runtime dumps, approval-template surfaces, review-package guardrails, and OpenCL evidence validators. This is intentionally a review/evidence release, not a production mutation release. +- [x] Register the first diagnostic peephole families. + Runtime typed peephole rules for `mad/fma`, `clamp`, `step`, `dot`, and `mix` are already built into `GpuRuntimeIrPeepholeRuleRegistry` as read-only matchers. They can produce replacement plans and drift telemetry, but they do not rewrite selected IR or generated backend source. +- [x] Materialize generated clamp trees as review-only optimized artifacts. + `GpuIrClampMaterializationProposalProvider` now rewrites conservative `min(max(x, lo), hi)` typed intrinsic trees to review `clamp(x, lo, hi)` candidates, updates typed body and `ir-text-v1` together, emits runtime-equivalence payload fields, and keeps selected runtime IR unchanged. +- [x] Materialize ternary masks as review-only `step` optimized artifacts. + `GpuIrStepMaterializationProposalProvider` now rewrites typed `1.0F` / `0.0F` ternary masks over `<`, `<=`, `>`, and `>=` comparisons into OpenCL `step` review candidates. Inclusive comparisons use direct `step(edge, x)` forms; strict `>` / `<` comparisons use `1.0F - step(...)` to preserve equality and NaN behavior. The provider updates typed body and `ir-text-v1` together, emits runtime-equivalence payload fields, and keeps selected runtime IR unchanged. +- [x] Materialize interpolation trees as review-only `mix` optimized artifacts. + `GpuIrMixMaterializationProposalProvider` now rewrites typed canonical interpolation trees to OpenCL `mix(a, b, t)` review candidates. It also recognizes expanded weighted and generated `mad(b, t, (1.0F - t) * a)` forms when fast-math is allowed, including the optimizer-journal density shape, and records whether algebraic reassociation was required. Typed body and `ir-text-v1` are updated together while selected runtime IR remains unchanged. +- [ ] Materialize typed peephole rewrites into optimized review artifacts. + Add the shared graph rewrite engine for the remaining rule families and broader generalized materialization: allocate deterministic node ids, build intrinsic replacement nodes, apply graph patches, rebuild a distinct optimized `IrGpu` artifact, regenerate review backend source, and preserve rollback to original IR on any validation or evidence failure. `mad/fma`, `clamp`, `step`, `mix`, safe-local-CSE, constant folding, typed-DCE, and the first loop-vectorization slice currently use dedicated conservative materializers rather than a shared graph-patch engine. +- [ ] Broaden peephole rules from canonical shapes to real generated forms. + Add regression fixtures for additional real generated clamp/step/mix variants: reordered clamp argument trees, inverted or swapped ternary mask branches, literal spelling variants, structurally duplicated but text-equivalent interpolation operands, helper-call operands, and non-`mad` expanded interpolation forms. Equality semantics and floating-point reassociation must be explicit blockers unless proven safe. +- [ ] Add an optional compile lifecycle event journal. + Provide an opt-in journal that records the path from program launch through descriptor loading, `IrGpu` loading, validation, optimizer-provider discovery, proposal execution, runtime-equivalence/review-package state, backend source selection, backend compilation, fallback/rollback, invocation, and shutdown. The journal should be useful for debugging without changing default runtime behavior. +- [ ] Promote optional `ir-optimizer` participation into the full runtime path when present. + Keep no-module and `optimizerProfile=off` behavior unchanged, but make the module-present diagnostic/review path explicit and documented: when enabled, the runtime should run optimizer proposals, dump original/optimized/selected artifacts, validate evidence, and still select original IR unless future production gates accept mutation. +- [ ] Expand `@GPUOptimize` into a richer method-level optimizer policy. + The annotation currently carries `fastMath` only. Future controls should cover optimizer enablement/profile, per-family toggles (`mad/fma`, `clamp`, `step`, `mix`, `dot`, vectorization, CSE, constant folding, private-array scalarization), review-only versus production intent, vectorization preference, vendor adaptation, register-pressure/resource shaping, and optional journal/dump hints while staying fail-closed by default. +- [ ] Evaluate the pre-existing private-array scalarizer prototype as an optimizer family. + The attached prototype code is a useful starting point for a future `private-array-scalarization` provider, but it needs to be ported into the current immutable proposal + typed-body contract, use conservative side-effect/index proofs, emit before/after review artifacts, and remain blocked from production selection until runtime-equivalence and rollback evidence exist. + ## Recommended Execution Order If the goal is to move forward pragmatically from the current state, the best order is now: diff --git a/docs/IR-Optimizer.md b/docs/IR-Optimizer.md index 5fa4ebf7..59efe6de 100644 --- a/docs/IR-Optimizer.md +++ b/docs/IR-Optimizer.md @@ -2,11 +2,11 @@ The optional IR optimizer module is the future backend-neutral transform layer for `IrGpu` artifacts. -The current module is intentionally proposal-first. It contributes ServiceLoader-discovered providers for no-op participation, conservative backend-neutral proposals, and preview-only proof surfaces without changing the selected IR. This keeps the default runtime behavior safe while establishing the module, packaging, and report boundary for future optimization families. +The current module is intentionally proposal-first. It contributes ServiceLoader-discovered providers for no-op participation, conservative backend-neutral proposals, preview-only proof surfaces, and narrowly scoped review-only materialized candidates without changing the selected IR by default. This keeps the default runtime behavior safe while establishing the module, packaging, and report boundary for future optimization families. ## First Alpha Scope -The first alpha is an evidence and contract boundary, not a production rewrite release. It includes the optional `ir-optimizer` / `ir-vendor-optimizer` module split, immutable proposal contracts, the validation sandwich, preview-only optimizer families, approval-template metadata, optimized-artifact candidate envelopes, and OpenCL evidence/report/validator guardrails. +The first alpha is an evidence and contract boundary, not a production rewrite release. It includes the optional `ir-optimizer` / `ir-vendor-optimizer` module split, immutable proposal contracts, the validation sandwich, preview-only optimizer families, backend-neutral source materialization, constrained review-only constant-folding materialization, review-only safe-local-CSE materialization that reuses an existing local value, review-only fast-math `mad/fma` materialization, review-only `clamp` materialization for generated `min(max(x, lo), hi)` shapes, review-only `step` materialization for ternary masks, review-only `mix` materialization for interpolation shapes, review-only fixed-width loop-vectorization materialization, typed dead-code materialization for unreachable pure typed nodes, approval-template metadata, optimized-artifact candidate envelopes, and OpenCL evidence/report/validator guardrails. It intentionally excludes runtime IR selection, selected-IR replacement, production mutation, real vendor rewrites, and any automatic choice of optimized artifacts. Those remain future gates above runtime-equivalence, rollback, approval, and production-promotion evidence. @@ -27,15 +27,33 @@ The first real proposal provider is `GpuIrTextCanonicalizationProposalProvider`. `GpuIrHelperDependencyDeduplicationProposalProvider` is the first backend-neutral metadata cleanup pass. It removes duplicate helper-dependency entries from method bodies while preserving first-occurrence order and leaving text bodies, typed bodies, backend outputs, and the original artifact unchanged. It is proposal-only by default and exists to prove the real rewrite/proposal path without changing kernel semantics. +`GpuIrBackendNeutralSourceMaterializationProposalProvider` is the first review-only backend-neutral source materialization pass. For OpenCL runtime review requests, it verifies that the `IrGpu` artifact can emit reconstructed OpenCL source, then returns a distinct optimized artifact with `backendNeutralSourceReady` metadata set. The proof records `backend-neutral-source-materialization`, source readiness, source length, no in-place mutation, and disabled production effect. This makes before/after backend dumps compare source-ready IR candidates while `backend.opencl-c` still comes from the selected runtime source. + `GpuIrConstantFoldingPreviewProposalProvider` is a preview-only proof surface for future typed constant folding. It scans typed IR for simple binary expressions with plain decimal literal operands, records candidate counts, operator breakdown, numeric-kind breakdown, first folded value, skipped blocker counts, policy state, and required proof gates, and always returns `NO_CHANGE`. It deliberately does not produce an optimized artifact, even when mutation is allowed. The preview records blockers such as divide-by-zero, non-even division, unsupported operators, non-literal operands, and non-plain literals. It also records that runtime-equivalence and approval are required before any future rewrite, and that integer-overflow / floating-point-rounding safety is not yet proven. This keeps constant folding useful as evidence while preventing it from silently becoming an applied transform. -`GpuIrSafeLocalCsePreviewProposalProvider` is a second preview-only proof surface for future safe local common-subexpression elimination. It scans typed IR for repeated local expressions, records expression/candidate/duplicate/equivalence-class counts, and reports blockers such as unsupported operators, impure operands, and control-flow boundaries. It always returns `NO_CHANGE`, marks the proof as preview-only, and records that dominance, side-effect freedom, runtime equivalence, and approval must be proven before any future rewrite can graduate. +`GpuIrConstantFoldingMaterializationProposalProvider` is the first review-only materialized transform candidate. It handles plain 32-bit integer literal unary `-` plus binary `+`, `-`, `*`, and exact `/` expressions where the divisor is non-zero and the division has no remainder, rewrites matching typed nodes into literals, and rewrites matching `ir-text-v1` expressions in the same method body. The pass also includes a constrained symbolic identity slice for deterministic pure retained expressions: `x + 0`, `0 + x`, `x - 0`, `x * 1`, `1 * x`, and `x / 1` can materialize to `x`, and the retained side can now be a named variable, plain int32 literal leaf, arithmetic binary tree, or unary minus tree that the pass can exactly re-render back to `ir-text-v1`. `x * 0`, `1 / x`, and broader division algebra remain intentionally excluded until stronger side-effect / unused-operand and equivalence proof exists. The pass runs safe literal and identity folds to a fixed point over reachable typed nodes, applying one materialized rewrite per pass so nested, mixed, repeated multi-root expressions, and multiple method bodies are recomputed against the updated typed body. Nested shapes such as `((-2) * 4)` and `((2 + 3) * 4)` can materialize as `-8` and `20`, while identity shapes such as `((a + b) + 0)`, `((a + b) / 1)`, `((x / 1) + 0)`, or repeated `x + 0` assignments can materialize as `(a + b)` or `x` in the optimized review artifact without crossing text replacements. If the textual body cannot be updated consistently, if division is unsafe/non-exact, if the retained identity expression contains unsupported / non-deterministic / non-plain-literal nodes, or if OpenCL review source cannot be regenerated for an OpenCL runtime request, it returns `NO_CHANGE`. The proof now records a compact exact-int/symbolic-identity `runtimeEquivalencePayload.*` with CPU/reference, pre-optimization, post-optimization, tolerance, failure-fixture, indexed case fields, per-case `MethodName`, `NodeId`, and `RewriteKind`, `runtimeEquivalencePayload.CaseIdentity=method-name-and-node-id`, `methodBody.rewriteScope=all-method-bodies`, `literalRewrite.count`, `identityRewrite.count`, `safety.identityOperandKind=GpuIrPureExpression`, `safety.identityRetainedExpressionKinds`, `fixedPoint.pass.count`, `fixedPoint.reachableNodeScan`, `fixedPoint.rewriteGranularity`, and `bodyTextReplacement.scope`, allowing the runtime-equivalence review gate to become `review-ready` for this narrow family. By default the bridge still reports the transformed artifact as proposal-only/candidate evidence; `backend.opencl-c` still comes from the runtime-selected source unless future selection gates explicitly allow otherwise. + +`GpuIrSafeLocalCsePreviewProposalProvider` is a second preview-only proof surface for broader safe local common-subexpression elimination. It scans typed IR for repeated local expressions, records expression/candidate/duplicate/equivalence-class counts, and reports blockers such as unsupported operators, impure operands, and control-flow boundaries. It always returns `NO_CHANGE`, marks the proof as preview-only, and records that dominance, side-effect freedom, runtime equivalence, and approval must be proven before any future broader rewrite can graduate. + +`GpuIrSafeLocalCseMaterializationProposalProvider` is the first review-only safe-local-CSE transform. It does not introduce new temporaries; instead it reuses already materialized local bindings in straight-line `ir-text-v1` bodies and runs to a fixed point over reachable roots. For example, after `var int tmp = (a + b)`, later matching assignments such as `set output[0] = (a + b)` and `set output[1] = (a + b)` can become `set output[0] = tmp` and `set output[1] = tmp` in the optimized review artifact. The pass blocks on control-flow boundaries, missing typed bodies, missing child references, unsupported/impure operands, missing text patterns, missing existing local bindings, or OpenCL review-source emission failures. Proof fields use `optimizerFamily=safe-local-cse-materialization`, `RewriteKind=safe-local-cse-reuse-existing-local`, `runtimeEquivalencePayload.CaseIdentity=method-name-and-node-id`, `fixedPoint.pass.count`, `safety.newTemporaryIntroduced=false`, and `provider.mutatesOriginal=false`. By default the bridge still keeps the original runtime IR selected; only `optimized.backend.opencl-c` / `optimized.irgpu.properties` show the review candidate. + +`GpuIrMadFmaMaterializationProposalProvider` is the first review-only typed peephole materializer. It requires fast-math permission from `@GPUOptimize(fastMath = true)` / optimizer policy, scans reachable typed `a * b + c` and `c + a * b` shapes, rewrites the matching `ir-text-v1` expression to `intrinsic(mad template="" args=[a, b, c])`, and replaces the typed root with a `GpuIrIntrinsicCall` review node. It runs one materialized rewrite per fixed-point pass, requires OpenCL review-source emission for OpenCL requests, and records `optimizerFamily=mad-fma-materialization`, `targetIntrinsic=mad`, `policy.fastMathAllowed`, `safety.fastMathRequired=true`, `safety.strictFloatPreserved=false`, `fixedPoint.pass.count`, `bodyTextReplacement.count`, and per-case `RewriteKind=mad-fma-to-opencl-mad`. Strict/default policy, missing typed body, unsupported text format, missing text pattern, or blocked OpenCL review-source emission returns `NO_CHANGE`. This can make `optimized.backend.opencl-c` show `mad(...)`, but `backend.opencl-c` remains the original selected source unless future proof/approval/selection gates explicitly allow otherwise. + +`GpuIrClampMaterializationProposalProvider` is the first review-only non-fast-math typed peephole materializer for generated clamp shapes. It scans reachable typed OpenCL intrinsic-call trees for the conservative shape `min(max(x, lo), hi)`, rewrites the matching `ir-text-v1` expression to `intrinsic(clamp template="" args=[x, lo, hi])`, and replaces the typed root with a `GpuIrIntrinsicCall` review node. It intentionally does not match reordered `min(hi, max(...))` or strict comparison / algebraic variants yet, because argument order, equality behavior, and floating-point edge cases must stay explicit. The proof records `optimizerFamily=clamp-materialization`, `targetIntrinsic=clamp`, `safety.fastMathRequired=false`, `safety.strictFloatPreserved=true`, `safety.argumentOrderPreserved=true`, `fixedPoint.pass.count`, `bodyTextReplacement.count`, and per-case `RewriteKind=min-max-to-opencl-clamp`. This can make `optimized.backend.opencl-c` show `clamp(...)`, but `backend.opencl-c` remains the original selected source unless future proof/approval/selection gates explicitly allow otherwise. + +`GpuIrStepMaterializationProposalProvider` is a review-only non-fast-math typed peephole materializer for ternary float masks. It scans reachable typed ternary/conditional/select nodes whose condition is a binary `<`, `<=`, `>`, or `>=` comparison and whose branches are exact `1.0` / `0.0` literals. Inclusive shapes materialize directly, for example `a >= b ? 1.0F : 0.0F` becomes `intrinsic(step template="" args=[b, a])`. Strict shapes preserve equality and NaN behavior by using the inverted OpenCL form, for example `a > b ? 1.0F : 0.0F` becomes `(1.0F - intrinsic(step template="" args=[a, b]))`. The proof records `optimizerFamily=step-materialization`, `targetIntrinsic=step`, `directStep.count`, `invertedStep.count`, `safety.fastMathRequired=false`, `safety.strictComparisonPreserved=true`, `safety.equalityBehaviorPreserved=true`, `safety.nanComparisonPreserved=true`, and per-case `RewriteKind=ternary-mask-to-opencl-step` or `RewriteKind=ternary-mask-to-inverted-opencl-step`. This can make `optimized.backend.opencl-c` show `step(...)` review candidates, but `backend.opencl-c` remains the original selected source unless future proof/approval/selection gates explicitly allow otherwise. + +`GpuIrMixMaterializationProposalProvider` is a review-only typed peephole materializer for linear interpolation shapes. It materializes canonical `a + t * (b - a)` / `t * (b - a) + a` forms to `intrinsic(mix template="" args=[a, b, t])` without requiring fast-math permission. It also recognizes expanded weighted forms such as `t * b + (1.0F - t) * a` and generated `mad(b, t, (1.0F - t) * a)` shapes, including the optimizer-journal style `mad(blend, isSolid, ((1.0F - isSolid) * (clampedWave * 0.1F)))`; those expanded forms require `fastMathAllowed` because they change floating-point operation ordering. The proof records `optimizerFamily=mix-materialization`, `targetIntrinsic=mix`, `canonicalMix.count`, `expandedMix.count`, `madExpandedMix.count`, `safety.fastMathRequired`, `safety.strictFloatPreserved`, `safety.algebraicReassociationRequired`, and per-case `RewriteKind=linear-interpolation-to-opencl-mix`, `expanded-linear-interpolation-to-opencl-mix`, or `mad-expanded-linear-interpolation-to-opencl-mix`. This can make `optimized.backend.opencl-c` show `mix(...)` review candidates, but `backend.opencl-c` remains the original selected source unless future proof/approval/selection gates explicitly allow otherwise. + +`GpuIrLoopVectorizationMaterializationProposalProvider` is the first review-only loop vectorization materializer. It recognizes a strict fixed-width contiguous float reduction shape such as `float sum = 0.0F; for (int i = 0; i < 4; i++) sum += input[id * 4 + i]`, including equivalent `input[mad(id, 4, i)]` / `input[intrinsic(mad ...)]` index forms, `input[(base + i)]` where `base` is a straight-line local `int` binding for the width-scaled base, reversed `input[(i + base)]`, and constant-offset bases such as `input[((id * 4) + 8 + i)]`. Local base aliases are resolved conservatively from preceding straight-line `int` declarations only, are cleared across control-flow boundaries, and are invalidated by later assignment to the same local. The pass rewrites the review `ir-text-v1` body to `vload4` plus an ordered scalar lane reduction through `Float4`. It also reconstructs a matching review typed body for the supported `ir-text-v1` subset, using `GpuIrIntrinsicCall(vload4)`, `GpuIrUnary(&)`, `GpuIrArrayAccess`, `GpuIrFieldAccess` lane reads, and ordered `GpuIrBinary(+)` reduction nodes; if surrounding body text falls outside that subset, it fails closed to typed-body invalidation for the review candidate. It records `optimizerFamily=loop-vectorization-materialization`, `targetIntrinsic=vload4`, `vectorWidth=4`, `safety.loopTripCountProven=true`, `safety.contiguousLoadProven=true`, `safety.orderedReductionPreserved=true`, `typedBody.materialized.count`, `typedBody.invalidated.count`, and per-case `RewriteKind=fixed-width-contiguous-vload4-ordered-reduction`. The pass intentionally avoids `dot` and reassociation, so it does not require fast-math permission. The original runtime IR remains selected; only `optimized.backend.opencl-c` / `optimized.irgpu.properties` show the `vload4` candidate. `GpuIrTypedDeadCodePreviewProposalProvider` is a third preview-only proof surface for future typed dead-code cleanup. It walks typed-body roots, records reachable versus unreachable node counts, unreachable-kind breakdown, missing root / missing child-reference blockers, and side-effecting unreachable-node blockers. It always returns `NO_CHANGE`, marks the proof as preview-only, and records that runtime equivalence, approval, and side-effect freedom must be proven before any future cleanup rewrite can graduate. -OpenCL validation aggregates these proof fields into `constantFoldingPreview.*`, `safeLocalCsePreview.*`, and `typedDeadCodePreview.*` summary fields inside `runtime-ir-optimizer-evidence.properties`. The same artifact also records `previewReadiness.*` fields that summarize preview-family status across constant folding, safe-local CSE, and typed dead-code cleanup as `not-recorded`, `no-candidates`, `candidates-recorded`, `blocked-by-proof`, or `ready-for-runtime-equivalence-review`. `runtimeEquivalenceReview.*` then exposes a fail-closed review gate over that aggregate: it can mark a preview family set as `review-ready`, but production mutation and selected-IR replacement remain disabled/manual-review-only. `reviewPackage.*` sits above that gate as the manual-review bundle boundary: it records whether a package is required, whether it is complete, the first blocker, proposal-pass and pending-approval counts, and repeats the disabled production mutation / selected-IR replacement guardrails. The generated `Runtime IR Optimizer Evidence` Markdown section shows constant-folding preview pass/candidate/blocker counts, safe-local-CSE pass/candidate-expression/duplicate-expression/blocker counts, typed dead-code pass/unreachable-node/blocker counts, aggregate preview-readiness status, runtime-equivalence review eligibility, and review-package status. These fields are report-only and do not participate in source switching, selected-IR replacement, or production mutation gates. +`GpuIrTypedDeadCodeMaterializationProposalProvider` is the first non-constant review-only materialized transform. It removes only unreachable pure typed nodes, leaves method text bodies unchanged, blocks on missing roots, missing child references, or side-effecting unreachable nodes, and emits runtime-equivalence payload cases keyed by method name and node id. By default the bridge still reports the transformed artifact as proposal-only/candidate evidence; the selected runtime IR remains unchanged. + +OpenCL validation aggregates these proof fields into `backendNeutralSourceMaterialization.*`, `constantFoldingPreview.*`, `constantFoldingMaterialization.*`, `safeLocalCsePreview.*`, `safeLocalCseMaterialization.*`, `madFmaMaterialization.*`, `clampMaterialization.*`, `stepMaterialization.*`, `mixMaterialization.*`, `loopVectorizationMaterialization.*`, `typedDeadCodePreview.*`, and `typedDeadCodeMaterialization.*` summary fields inside `runtime-ir-optimizer-evidence.properties`. Backend-neutral source materialization evidence records pass/candidate/source-ready counts, total reconstructed source length, status, and the first blocker so CI can see source-ready review candidates without treating them as selected IR. Materialized constant-folding evidence records pass/transformed-node/literal-rewrite/identity-rewrite/fixed-point-pass counts, runtime-equivalence payload presence/pass counts, status, and the first blocker so CI can see real transformed review candidates without treating them as selected IR. Materialized safe-local-CSE evidence records pass/local-binding/transformed-node/body-text-replacement counts, runtime-equivalence payload presence/pass counts, status, and the first blocker so CI can see repeated pure expressions rewritten to existing local references without treating them as selected IR. Materialized mad/FMA evidence records pass/candidate/transformed-node/body-text-replacement/fixed-point-pass counts, skipped fast-math-policy / body-text-pattern blockers, fast-math policy state, runtime-equivalence payload presence/pass counts, status, and the first blocker so CI can see fast-math peephole candidates without treating them as selected IR. Materialized clamp/step/mix evidence records pass/candidate/transformed-node/body-text-replacement counts, direct/inverted step counts, canonical/expanded/MAD-expanded mix counts, fast-math/reassociation requirements, runtime-equivalence payload presence/pass counts, status, and the first blocker so CI can see intrinsic materialization candidates without treating them as selected IR. Materialized loop-vectorization evidence records pass/candidate/transformed-loop/changed-method-body/body-text-replacement counts, typed-body materialization/invalidation counts, skipped loop-shape / unsupported-width / unsafe-load blockers, ordered-reduction proof, runtime-equivalence payload presence/pass counts, status, and the first blocker so CI can see fixed-width contiguous `vload4` review candidates without treating them as selected IR. Materialized typed dead-code evidence records pass/node/unreachable/removed-node counts, side-effect-freedom proof, runtime-equivalence payload presence/pass counts, status, and the first blocker so CI can see pure unreachable-node cleanup candidates without treating them as selected IR. The same payloads are also routed into `runtime-optimizer-family-equivalence-payload.properties` and durable per-pass component files. The artifact also records `previewReadiness.*` fields that summarize preview-family status across constant folding, safe-local CSE, and typed dead-code cleanup as `not-recorded`, `no-candidates`, `candidates-recorded`, `blocked-by-proof`, or `ready-for-runtime-equivalence-review`. `runtimeEquivalenceReview.*` then exposes a fail-closed review gate over preview readiness, materialized constant-folding candidates, materialized safe-local-CSE candidates, materialized mad/FMA candidates, materialized clamp/step/mix candidates, materialized loop-vectorization candidates, and materialized typed dead-code candidates: it can mark a candidate set as `review-ready`, but production mutation and selected-IR replacement remain disabled/manual-review-only. `reviewPackage.*` sits above that gate as the manual-review bundle boundary: backend-neutral source materialization and materialized candidates can produce pending approval templates, but the package remains pending on approval/manual review and repeats the disabled production mutation / selected-IR replacement guardrails. The generated `Runtime IR Optimizer Evidence` Markdown section shows backend-neutral source materialization status/source-ready counts, constant-folding preview pass/candidate/blocker counts, constant-folding materialization status/materialized-node/literal-rewrite/identity-rewrite/fixed-point-pass/blocker counts, safe-local-CSE preview pass/candidate-expression/duplicate-expression/blocker counts, safe-local-CSE materialization status/materialized-node/blocker counts, mad/FMA materialization status/materialized-node/text-replacement/fast-math/blocker counts, clamp/step/mix materialization status/materialized-node/text-replacement/payload/blocker counts, loop-vectorization materialization status/transformed-loop/text-replacement/typed-body-materialization/typed-body-invalidation/payload/blocker counts, typed dead-code preview pass/unreachable-node/blocker counts, typed dead-code materialization removed-node/payload/blocker counts, aggregate preview-readiness status, runtime-equivalence review eligibility, and review-package status. These fields are report-only and do not participate in source switching, selected-IR replacement, or production mutation gates. ## Immutable Proposal Flow @@ -72,7 +90,7 @@ If mutation is disabled, a valid optimized artifact is reported as `PROPOSAL_ONL `GpuIrProposalRuntimeBridgePass` is the runtime adapter between the existing runtime optimizer pipeline and the immutable proposal-provider contract. It consumes providers from `GpuIrOptimizationProposalRegistry`, runs each provider through the validation sandwich, and reports the result as a runtime optimizer pass report. -The default bridge is intentionally proposal-only. It can surface canonicalization proposals and proof metadata in runtime optimizer reports, but it does not select optimized IR unless constructed with explicit mutation permission. This lets `ir-optimizer` participate in runtime evidence collection without enabling production mutation. +The default bridge is intentionally proposal-only. It can surface canonicalization proposals, materialized review candidates, and proof metadata in runtime optimizer reports, but it does not select optimized IR unless constructed with explicit mutation permission. Proposal-only materialized candidates are composed through a separate review-working artifact, so later providers can see earlier review transforms such as constant folding before safe-local-CSE and `mad/fma` materialization run. The final runtime `artifact` remains the original selected IR, while `candidateArtifact`, `optimized.irgpu.properties`, and `optimized.backend.opencl-c` carry the accumulated review candidate. This lets `ir-optimizer` participate in runtime evidence collection without enabling production mutation. Runtime artifact dumping records bridge/provider evidence in `runtime-ir-optimizer-evidence.properties`. The artifact is filtered by the stable `javatogpu.ir-optimizer` provider prefix and includes pass counts, outcomes, proposal-only/selected counts, original/transformed IR identities, proof artifact fields, and diagnostics. This is an evidence surface only; it does not authorize optimized IR selection or production mutation by itself. @@ -88,7 +106,7 @@ Runtime peephole/InstCombine proof is also kept read-only. `GpuRuntimeIrPeephole `GpuRuntimeIrPeepholeOptimizedArtifactSelectionPreflight` records the final read-only selection boundary above proof binding. It reports whether proof binding is ready, whether production gate and mutation policy would still block selection, and keeps optimized-artifact selection, selection application, mutation, and selected-IR replacement disabled. This makes the future handoff to runtime IR selection auditable without changing the selected artifact. -The same evidence artifact now records `approvalTemplate.*` fields for each optimizer pass. Real proposal rewrites with distinct original/optimized identities and accepted proof metadata are marked `pending`; preview and no-change proposals are marked `not-applicable` with a first blocker such as `proposal-decision-not-proposed`. +The same evidence artifact now records `approvalTemplate.*` fields for each optimizer pass. Real proposal rewrites with distinct original/optimized identities and accepted proof metadata are marked `pending`; preview and no-change proposals are marked `not-applicable` with a first blocker such as `proposal-decision-not-proposed`. Runtime evidence also lifts payload-bound approval readiness into `approvalTemplate.runtimeEquivalencePayloadRequired.count`, `approvalTemplate.runtimeEquivalencePayloadPresent.count`, `approvalTemplate.runtimeEquivalencePayloadPassed.count`, and `approvalTemplate.runtimeEquivalencePayloadComplete.count`, so review packages can show whether pending approvals are bound to complete runtime-equivalence payloads without completing the package or selecting optimized IR. When the bridge has full proposal context, proof fields also include `approvalTemplate.resourcePath`; the dumper exposes this under `pass.N.approvalTemplate.field.resourcePath` as the review package location for the manifest candidate. `GpuIrOptimizationApprovalManifestLoader` then probes that deterministic classpath resource, validates any loaded manifest against the proposal/request binding, and emits `approvalManifest.*` proof fields. `reviewPackage.approvalManifest.*` records the package-level manifest boundary: required state, present/accepted counts, resource-path summary, first blocker, and disabled production mutation / selected-IR replacement. Missing, stale, or payload-incomplete manifests remain fail-closed; accepted manifests are auditable review evidence only and do not select optimized IR. ## Vendor Optimizer SPI @@ -116,15 +134,15 @@ Provider authors must expose stable `extensionId`, `extensionVersion`, and `exte ## Approval Manifest -`GpuIrOptimizationApprovalManifest` writes a review-only approval template for one immutable optimizer proposal. The template is bound to the optimizer id/version, original and optimized `IrGpu` identities, backend target, optimization profile, optional device vendor/label context, proof source/verdict, and rollback requirement. It is intended for packaging under: +`GpuIrOptimizationApprovalManifest` writes a review-only approval template for one immutable optimizer proposal. The template is bound to the optimizer id/version, original and optimized `IrGpu` identities, backend target, optimization profile, optional device vendor/label context, proof source/verdict, runtime-equivalence payload binding when required, and rollback requirement. It is intended for packaging under: ```text META-INF/javatogpu/ir-optimization-approvals/ ``` -Validation requires `status=approved`, explicit approval id/author/timestamp, matching proposal identities, matching backend/device context, matching proof metadata, `binding.rollback.required=true`, and `authorization.productionMutation=disabled`. A valid manifest is still `manual-review-only`: it proves that the proposal was reviewed, but it does not select optimized IR or grant production mutation by itself. +Validation requires `status=approved`, explicit approval id/author/timestamp, matching proposal identities, matching backend/device context, matching proof metadata, matching runtime-equivalence payload resource/comparison/case metadata when the proposal requires payload evidence, `binding.rollback.required=true`, and `authorization.productionMutation=disabled`. Required payloads must be present, passed, component-complete, and have at least one recorded case. A valid manifest is still `manual-review-only`: it proves that the proposal was reviewed, but it does not select optimized IR or grant production mutation by itself. -`GpuIrOptimizationApprovalTemplateFormatter` is the bridge between proposal evidence and approval artifacts. It returns a pending template only for a real `PROPOSED` rewrite with distinct original/optimized identities and accepted proof metadata. Preview or `NO_CHANGE` proposals return `not-applicable` with a first blocker such as `proposal-decision-not-proposed`, so report tooling can expose why no approval template should be packaged yet. +`GpuIrOptimizationApprovalTemplateFormatter` is the bridge between proposal evidence and approval artifacts. It returns a pending template only for a real `PROPOSED` rewrite with distinct original/optimized identities and accepted proof metadata, emits a deterministic `manifest.resourcePath` under `META-INF/javatogpu/ir-optimization-approvals/`, and exposes compact `runtimeEquivalencePayload.*` template fields so review packages can show whether approval is bound to complete payload evidence. Preview or `NO_CHANGE` proposals return `not-applicable` with a first blocker such as `proposal-decision-not-proposed`, so report tooling can expose why no approval template should be packaged yet. ## Read Next diff --git a/docs/Runtime-Guide.md b/docs/Runtime-Guide.md index 33c1bf83..b49ac393 100644 --- a/docs/Runtime-Guide.md +++ b/docs/Runtime-Guide.md @@ -235,7 +235,13 @@ OpenCL isolated runtime-equivalence checks write raw pipeline comparison cases i Non-analysis optimizer passes with runtime-equivalence payload evidence also produce `runtime-optimizer-family-equivalence-payload.properties` plus durable files under `runtime-optimizer-family-equivalence-payload/family--/pass-/`. Each pass directory contains `manifest.properties`, CPU/reference, pre-optimization, post-optimization, tolerance, failure-fixture, and diagnostics properties files. When proof fields contain structured case evidence, the component files include deterministic indexed fields for case names, inputs, raw outputs, tolerances, equivalence flags, and diagnostics. Missing components are materialized with `status=not-recorded`; they are never inferred from another pass. Artifact paths are normalized and must remain inside the kernel's `runtime-compile-artifacts` directory. These files are diagnostic evidence only and cannot enable source switching or production mutation. -When the optional IR optimizer bridge participates, each kernel directory may also receive `runtime-ir-optimizer-evidence.properties`. The artifact records only proposal/provider evidence from stable `javatogpu.ir-optimizer` sources: pass counts, proposal-only versus selected optimized counts, rollback counts, IR identities, proof fields, approval-template applicability, and diagnostics. The validation sandwich now also has an internal optimized-artifact candidate envelope for proposed artifacts; it records candidate identity and selection blockers as report-only evidence while final runtime IR selection remains separate and fail-closed. Candidate envelope proof fields are also lifted into top-level `optimizedArtifactCandidate.*` summary fields for status/counts, ready/blocked counts, first blockers, selection-ready/applied counts, selected-IR replacement counts, mutation-policy count, and disabled selection/replacement guardrails. Constant-folding preview proof is also aggregated into `constantFoldingPreview.*` fields for pass count, candidate count, skipped blockers, required runtime-equivalence/approval gates, and unresolved integer-overflow / floating-point-rounding safety flags. Safe-local-CSE preview proof is aggregated into `safeLocalCsePreview.*` fields for pass count, expression/candidate/duplicate/equivalence-class counts, unsupported-operator / impure-operand / control-flow-boundary blockers, required runtime-equivalence/approval gates, and unresolved dominance / side-effect-freedom proof flags. Typed dead-code preview proof is aggregated into `typedDeadCodePreview.*` fields for pass count, typed/reachable/unreachable node counts, missing-root / missing-child-reference / side-effecting-unreachable blockers, required runtime-equivalence/approval gates, and unresolved side-effect-freedom proof flags. The artifact also records `previewReadiness.*` fields with aggregate status, recorded/candidate/blocked family counts, and a per-family status summary so CI can distinguish no-candidate previews from proof-blocked previews and review-ready runtime-equivalence candidates. `runtimeEquivalenceReview.*` records the next fail-closed review boundary: status, eligibility, requirement, first blocker, family summary, disabled production mutation, disabled selected-IR replacement, and manual-review-only state. `reviewPackage.*` records the manual-review package boundary above that review gate, including package status, requirement/completeness, first blocker, proposal-pass count, pending approval count, original/optimized IR and proof-summary requirements, manual-review-only state, and disabled production mutation / selected-IR replacement. `openClValidationReport` aggregates this into `Runtime IR Optimizer Evidence` for CI visibility, including optimized-artifact candidate status/blockers, constant-folding, safe-local-CSE, typed dead-code preview totals, preview-readiness status, runtime-equivalence review eligibility, and review-package status. `validateOpenClRuntimeIrOptimizerEvidence` then validates the raw runtime evidence files and fails CI if those fail-closed guardrails are missing, completed early, or production-enabling. The section and validator are evidence-only and do not participate in history, source switching, selected-IR replacement, or production mutation gates. +When the optional IR optimizer bridge participates, each kernel directory may also receive `runtime-ir-optimizer-evidence.properties`. The artifact records only proposal/provider evidence from stable `javatogpu.ir-optimizer` sources: pass counts, proposal-only versus selected optimized counts, rollback counts, IR identities, proof fields, approval-template applicability, and diagnostics. Proposal-only materialized candidates are composed through a separate review-working artifact, so `optimized.irgpu.properties` and `optimized.backend.opencl-c` can accumulate safe review transforms from multiple providers while `backend.opencl-c` still remains the selected source compiled by OpenCL. The validation sandwich now also has an internal optimized-artifact candidate envelope for proposed artifacts; it records candidate identity and selection blockers as report-only evidence while final runtime IR selection remains separate and fail-closed. Candidate envelope proof fields are also lifted into top-level `optimizedArtifactCandidate.*` summary fields for status/counts, ready/blocked counts, first blockers, selection-ready/applied counts, selected-IR replacement counts, mutation-policy count, and disabled selection/replacement guardrails. Backend-neutral source materialization proof is aggregated into `backendNeutralSourceMaterialization.*` fields for pass count, candidate count, source-ready count, total source length, status, and first blocker; it is review-only source metadata evidence and does not require runtime-equivalence review by itself. Constant-folding preview proof is also aggregated into `constantFoldingPreview.*` fields for pass count, candidate count, skipped blockers, required runtime-equivalence/approval gates, and unresolved integer-overflow / floating-point-rounding safety flags. A separate constant-folding materialization provider can now emit a review-only transformed artifact for plain 32-bit integer literal unary `-` plus binary `+`, `-`, `*`, and exact `/` expressions where the divisor is non-zero and the division has no remainder, including nested safe folds materialized to a fixed point. It also supports a narrow pure-symbolic identity slice for `x + 0`, `0 + x`, `x - 0`, `x * 1`, `1 * x`, and `x / 1`, where the retained side can be a deterministic `GpuIrPureExpression` composed of named variables, plain int32 literal leaves, arithmetic binary nodes, and unary minus nodes; mixed identity chains such as `((x / 1) + 0)`, repeated identities across multiple roots, and multiple method bodies are recomputed one reachable rewrite per fixed-point pass so text and typed roots stay aligned. `x * 0`, `1 / x`, and broader division algebra remain excluded until stronger side-effect / unused-operand and equivalence proof exists. Top-level `constantFoldingMaterialization.*` fields record pass/transformed-node/literal-rewrite/identity-rewrite/fixed-point-pass counts, all-method-body rewrite scope, reachable-node-scan and rewrite-granularity metadata, body-text replacement scope, exact-int or symbolic-identity runtime-equivalence payload presence/pass counts, per-case method/node/rewrite-kind identity, status, skipped divide-by-zero/non-even-division counts, and first blocker. The same compact proof payload is routed into `runtime-optimizer-family-equivalence-payload.properties` plus durable component files, so this narrow materialized family can become runtime-equivalence `review-ready` while approval/manual review still blocks completion. Pending approval templates also export `pass.N.approvalTemplate.field.runtimeEquivalencePayload.*`, `pass.N.approvalTemplate.field.resourceDirectory`, and `pass.N.approvalTemplate.field.resourcePath` fields so the manual-review package is visibly bound to the same payload resource, comparison mode, completeness, case count, and manifest location; top-level `approvalTemplate.runtimeEquivalencePayloadRequired.count`, `approvalTemplate.runtimeEquivalencePayloadPresent.count`, `approvalTemplate.runtimeEquivalencePayloadPassed.count`, and `approvalTemplate.runtimeEquivalencePayloadComplete.count` summarize those bindings across pending templates. The bridge also probes the deterministic approval manifest classpath resource and emits `approvalManifest.*` fields for missing, blocked, or accepted manifest validation. `reviewPackage.approvalManifest.*` records the next package boundary over those templates, including manifest requirement, present/accepted counts, resource-path summary, first blocker, manual-review-only state, and disabled production mutation / selected-IR replacement. Safe-local-CSE preview proof is aggregated into `safeLocalCsePreview.*` fields for pass count, expression/candidate/duplicate/equivalence-class counts, unsupported-operator / impure-operand / control-flow-boundary blockers, required runtime-equivalence/approval gates, and unresolved dominance / side-effect-freedom proof flags. Safe-local-CSE materialization proof is aggregated into `safeLocalCseMaterialization.*` fields for pass count, existing-local binding count, transformed-node count, body-text replacement count, fixed-point pass count, runtime-equivalence payload presence/pass counts, status, and first blocker. This materialized family only rewrites repeated pure binary expressions to already existing local references in straight-line `ir-text-v1` bodies; it introduces no new temporary and changes only the optimized review artifact. Intrinsic materialization proof is aggregated into `madFmaMaterialization.*`, `clampMaterialization.*`, `stepMaterialization.*`, and `mixMaterialization.*` fields for pass/candidate/transformed-node/body-text-replacement counts, fast-math or strict-float safety policy, direct/inverted step accounting, canonical/expanded/MAD-expanded mix accounting, runtime-equivalence payload presence/pass counts, status, and first blocker. Loop-vectorization proof is aggregated into `loopVectorizationMaterialization.*` fields for transformed-loop/text-replacement/typed-body materialization counts and ordered-reduction safety evidence. Typed dead-code preview proof is aggregated into `typedDeadCodePreview.*` fields for pass count, typed/reachable/unreachable node counts, missing-root / missing-child-reference / side-effecting-unreachable blockers, required runtime-equivalence/approval gates, and unresolved side-effect-freedom proof flags. The artifact also records `previewReadiness.*` fields with aggregate status, recorded/candidate/blocked family counts, and a per-family status summary so CI can distinguish no-candidate previews from proof-blocked previews and review-ready runtime-equivalence candidates. `runtimeEquivalenceReview.*` records the next fail-closed review boundary: status, eligibility, requirement, first blocker, family summary, disabled production mutation, disabled selected-IR replacement, and manual-review-only state. `reviewPackage.*` records the manual-review package boundary above that review gate, including package status, requirement/completeness, first blocker, proposal-pass count, pending approval count, original/optimized IR and proof-summary requirements, manual-review-only state, and disabled production mutation / selected-IR replacement. `openClValidationReport` aggregates this into `Runtime IR Optimizer Evidence` for CI visibility, including optimized-artifact candidate status/blockers, backend-neutral source materialization readiness, approval-template payload readiness, approval-manifest package readiness, constant-folding preview/materialization, safe-local-CSE preview/materialization, mad/FMA, clamp, step, mix, loop-vectorization, typed dead-code preview/materialization totals, preview-readiness status, runtime-equivalence review eligibility, and review-package status. `validateOpenClRuntimeIrOptimizerEvidence` then validates the raw runtime evidence files and fails CI if those fail-closed guardrails are missing, completed early, or production-enabling. The section and validator are evidence-only and do not participate in history, source switching, selected-IR replacement, or production mutation gates. + +Typed dead-code materialization is now represented as a separate review-only evidence family. `typedDeadCodeMaterialization.*` records pass count, typed/unreachable/removed node counts, missing-root / missing-child-reference / side-effecting-unreachable blockers, runtime-equivalence payload presence/pass counts, side-effect-freedom proof, status, and first blocker. It removes only unreachable pure typed nodes from the optimized review artifact, leaves method text bodies unchanged, and is included in `runtimeEquivalenceReview.*` / OpenCL Markdown summaries without enabling source switching, selected-IR replacement, or production mutation. + +Safe-local-CSE materialization is also review-only. `safeLocalCseMaterialization.*` records pass count, existing-local binding count, transformed-node count, body-text replacement count, fixed-point pass count, runtime-equivalence payload presence/pass counts, status, and first blocker. In dumps, this is the easiest first alpha source-changing example to inspect: `optimized.backend.opencl-c` can replace repeated expressions with existing local variables, while `backend.opencl-c` remains the original selected source compiled by OpenCL. + +Intrinsic materialization is review-only too. `madFmaMaterialization.*`, `clampMaterialization.*`, `stepMaterialization.*`, and `mixMaterialization.*` record whether `optimized.backend.opencl-c` can show `mad(...)`, `clamp(...)`, `step(...)`, and `mix(...)` candidates, including text replacement counts and payload pass counts. `backend.opencl-c` remains the selected source unless future proof, approval, production gate, and mutation policy explicitly allow optimized artifact selection. Runtime peephole/InstCombine remains diagnostic-only. `GpuRuntimeIrPeepholeReplacementPlan` records read-only typed replacement plans for current rule families: `mad/fma`, `clamp`, `step`, `dot`, and `mix`. Built-in rules now use `GpuRuntimeIrTypedNodeGraph` as the shared read-only typed-node traversal surface, and each emitted plan is checked by `GpuRuntimeIrPeepholeReplacementPlanValidation` before proof fields are exported. `GpuRuntimeIrPeepholeTypedRewriteVisitor` adds a shared traversal preflight above validated plans: it records deterministic visit order, visitor-ready/blocked counts, first visitor blocker, and disabled replacement-builder / transformed-IR / mutation / selected-IR flags. `GpuRuntimeIrPeepholeReplacementBlueprint` adds a read-only target intrinsic-call blueprint above visitor-ready plans: it records target node kind/operation, argument node ids/roles, blueprint-ready/blocked counts, first blueprint blocker, and the same disabled builder / transformed-IR / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeRewriteTransactionPreflight` adds the graph-transaction shape above blueprint-ready plans: replaced root ids, removed covered ids, retained input ids, planned added-node count, `plannedAddedNodeIds=not-allocated`, and disabled node-id allocation / graph rewrite / transformed-IR / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeNodeIdAllocationPreflight` adds a diagnostic allocation preview above transaction-ready plans: it records current graph max node id, deterministic candidate node ids, allocation-ready/blocked counts, first blocker, and disabled id reservation / allocator application / graph rewrite / transformed-IR / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeReplacementNodePreflight` adds the next diagnostic preview: candidate replacement-node id, target intrinsic-call kind/operation, argument ids/roles, ready/blocked counts, first blocker, and disabled replacement-node build / replacement-builder / graph rewrite / transformed-IR / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeGraphPatchPreflight` adds a diagnostic graph-patch preview: replacement node id, replaced/removed/retained/inserted node sets, ready/blocked counts, first blocker, and disabled patch application / graph rewrite / transformed-IR / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeTransformedGraphPreflight` adds the transformed-graph materialization preview above that patch: original IR identity, deterministic materialization key, `transformedGraphIdentity=not-built`, ready/blocked counts, first blocker, and disabled transformed-graph build / transformed-IR / patch application / graph rewrite / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeRewriteSketch` records the mutation-free structural rewrite skeleton around the validated replacement plan: ready sketches prove only that the future rewrite shape is describable, while `rewriteBuilderImplemented`, `mutationAllowed`, and `selectedIrReplacement` remain `false`. `rewriteSelection.*` fields add a read-only selection preflight over those sketches, including sketch/conflict counts, selection status, first blocker, proof/approval requirements, and disabled mutation/selection guardrails. `rewriteProof.*` fields add the matching proof preflight: proof status, first blocker, runtime-equivalence payload presence/completeness, rollback evidence, approval acceptance, original IR identity, absent transformed IR identity, and disabled selected-IR replacement. `rewriteReviewPackage.*` fields add the manual-review package boundary above proof readiness: package required/complete state, conflict count, proof acceptance, runtime-equivalence payload, rollback, approval, manual-review-only state, and disabled mutation / selection / selected-IR replacement. The same visitor/blueprint/transaction/allocation/replacement-node/graph-patch/transformed-graph/selection/proof/package preflight fields are also emitted under `rule.N.*`, making rule-family blockers visible without enabling any rewrite. Proof fields expose the candidate root node, covered nodes, input nodes, completeness, first blocker, structural validation counters, visitor counters, blueprint counters, transaction counters, allocation counters, replacement-node counters, graph-patch counters, transformed-graph counters, sketch counters, selection preflight fields, proof preflight fields, and review-package fields. `runtime-optimizer-drift.properties` aggregates complete/partial replacement-plan counts, plan-validation total/valid/invalid counts, rewrite-visitor ready/blocked counts, replacement-blueprint ready/blocked counts, rewrite-transaction ready/blocked counts, node-id allocation ready/blocked counts, replacement-node preview ready/blocked counts, graph-patch preview ready/blocked counts, transformed-graph materialization ready/blocked counts, rewrite-sketch ready/blocked counts, conflict counts, selection-readiness guardrails, proof/runtime-equivalence/rollback blockers, package-completeness blockers, first incomplete/invalid/visitor/blueprint/transaction/allocation/replacement-node/graph-patch/transformed-graph/sketch/selection/proof/package blockers, and compact `optimizerRule.*` summaries from existing `rule.N.*` proof fields so CI can detect planning drift while the rewrite engine remains absent. Workload gates carry the same visitor/blueprint/transaction/allocation/replacement-node/graph-patch/transformed-graph fields forward as evidence-only telemetry. These plans, visitors, blueprints, transactions, allocation previews, replacement-node previews, graph-patch previews, transformed-graph previews, and sketches describe what a future structural rewrite would need to replace; they do not edit typed bodies, reserve node ids, build replacement nodes, apply graph patches, build transformed graphs, apply allocators, rewrite graphs, select optimized IR, or enable production mutation. diff --git a/docs/Validation-and-Operations.md b/docs/Validation-and-Operations.md index 7173c5c8..dc99136e 100644 --- a/docs/Validation-and-Operations.md +++ b/docs/Validation-and-Operations.md @@ -109,7 +109,7 @@ Use `activate` only after manifest validation succeeds. The workflow then writes The vendor workflow also runs `openClOptimizerFamilyPayloadFixtureTest` after the main validation bucket. It must produce `fixture-summary.properties` with two complete families and fourteen durable files. This fixture proves the nested artifact contract is uploadable and path-safe; it does not alter real-workload optimizer-family counts or production readiness. -`validateOpenClRuntimeIrOptimizerEvidence` checks `runtime-ir-optimizer-evidence.properties` under `runtime-compile-artifacts`. It fails if runtime-equivalence review, optimized-artifact candidate, or review-package guardrails stop being fail-closed, if production mutation or selected-IR replacement becomes enabled, if candidate selection is applied, if a required review package has no blocker, or if a package is marked complete before manual-review activation exists. The operational and NVIDIA routines run this validator after `openClValidationReport`. +`validateOpenClRuntimeIrOptimizerEvidence` checks `runtime-ir-optimizer-evidence.properties` under `runtime-compile-artifacts`. It fails if runtime-equivalence review, optimized-artifact candidate, or review-package guardrails stop being fail-closed, if production mutation or selected-IR replacement becomes enabled, if candidate selection is applied, if a required review package has no blocker, or if a package is marked complete before manual-review activation exists. When materialization-family evidence is present, it also verifies family-specific review-ready invariants: safe-local-CSE needs existing-local reuse, dominance/side-effect proof, matching text replacement, required runtime-equivalence/approval gates, and passed payloads; fast-math `mad/fma` needs matching text replacement, fast-math policy evidence, required runtime-equivalence/approval gates, and passed payloads; `clamp` needs matching text replacement, strict-float/no-fast-math safety evidence, required runtime-equivalence/approval gates, and passed payloads; `step` needs matching text replacement, direct/inverted step accounting, strict comparison/equality/NaN safety evidence, required runtime-equivalence/approval gates, and passed payloads; `mix` needs matching text replacement, canonical/expanded/MAD-expanded accounting, fast-math/reassociation evidence when required, required runtime-equivalence/approval gates, and passed payloads; loop vectorization needs matching text replacement, fixed trip-count proof, contiguous-load proof, ordered-reduction preservation, typed-body materialization/invalidation accounting, required runtime-equivalence/approval gates, and passed payloads; typed dead-code removal needs consistent node counts, side-effect proof, required runtime-equivalence/approval gates, and passed payloads. The operational and NVIDIA routines run this validator after `openClValidationReport`. ## Optional IR Validation diff --git a/examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md b/examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md index d3c99894..72ede064 100644 --- a/examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md +++ b/examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md @@ -57,7 +57,15 @@ optimizer-report.txt runtime-ir-optimizer-evidence.properties ``` -`original.backend.opencl-c` and `optimized.backend.opencl-c` are the before/after backend source files for comparing generated OpenCL around the optimizer boundary. `backend.opencl-c` is the selected backend source code that the OpenCL backend actually compiles. `original.irgpu.properties` and `optimized.irgpu.properties` are the matching before/after IR files. `runtime-ir-optimizer-evidence.properties` is the machine-readable guardrail summary that keeps the current alpha fail-closed. +`original.backend.opencl-c` and `optimized.backend.opencl-c` are the before/after backend source files for comparing generated OpenCL around the optimizer boundary. `optimized.backend.opencl-c` may come from an accumulated review-only materialized candidate, including backend-neutral source materialization metadata, the constrained integer constant-folding transform, safe-local-CSE materialization, the fast-math `mad/fma` peephole materializer, `clamp` materialization, `step` materialization, `mix` materialization, or fixed-width loop vectorization. Constant folding supports plain 32-bit integer literal unary `-`, binary `+`, `-`, `*`, exact `/`, nested safe literal patterns, and the current pure-symbolic identity slice (`x + 0`, `0 + x`, `x - 0`, `x * 1`, `1 * x`, `x / 1`, mixed chains such as `((x / 1) + 0)`, repeated identities across multiple roots, and multiple method bodies) over deterministic retained expressions made from named variables, plain int32 literal leaves, arithmetic binary nodes, and unary minus nodes. Safe-local-CSE materialization reuses already existing local bindings in straight-line `ir-text-v1`, for example replacing later repeated `(value * 3.1415f)` expressions with the earlier local `scale` until no more safe candidates remain. Mad/FMA materialization requires `@GPUOptimize(fastMath = true)` and rewrites reachable `a * b + c` / `c + a * b` text plus typed nodes to `intrinsic(mad template="" args=[a, b, c])` in the optimized review artifact. `x * 0`, `1 / x`, broader division algebra, new CSE temporaries, control-flow-spanning CSE, reordered clamp/minmax variants, non-mask ternaries, and mix forms outside the current canonical / expanded / MAD-expanded shapes remain excluded for now. `backend.opencl-c` is the selected backend source code that the OpenCL backend actually compiles. `original.irgpu.properties` and `optimized.irgpu.properties` are the matching before/after IR files. `runtime-ir-optimizer-evidence.properties` is the machine-readable guardrail summary that keeps the current alpha fail-closed; for materialized review candidates, check `backendNeutralSourceMaterialization.*`, `constantFoldingMaterialization.*`, `safeLocalCseMaterialization.*`, `madFmaMaterialization.*`, `clampMaterialization.*`, `stepMaterialization.*`, `mixMaterialization.*`, `loopVectorizationMaterialization.*`, `typedDeadCodeMaterialization.*`, `runtimeEquivalenceReview.*`, `approvalTemplate.runtimeEquivalencePayload*.count`, `pass.N.approvalTemplate.field.resourcePath`, `pass.N.approvalTemplate.field.approvalManifest.*`, `reviewPackage.approvalManifest.*`, and the durable `runtime-optimizer-family-equivalence-payload/` files to see source-ready counts, transformed-node counts, literal/identity rewrite counts, fixed-point-pass counts, existing-local CSE reuse counts, mad/FMA fast-math policy, clamp/step/mix intrinsic counts, loop-vectorization counts, body-text replacement counts, reachable-node scan / rewrite-granularity fields, per-case method/node identity, exact-int / symbolic-identity / existing-local-reuse / fast-math-mad / intrinsic-materialization payload evidence, approval payload readiness, loaded/accepted manifest evidence, the review manifest location, and the remaining manual-review/approval blocker. + +Typed dead-code materialization is also review-only. When it participates, check `typedDeadCodeMaterialization.*` for typed/unreachable/removed node counts, side-effect-freedom proof, status, first blocker, and payload pass counts. It removes only unreachable pure typed nodes in the optimized review artifact, leaves method text bodies unchanged, and does not make `backend.opencl-c` switch to the optimized source. + +Safe-local-CSE materialization is review-only too. When it participates, check `safeLocalCseMaterialization.*` for existing local binding count, transformed-node count, body-text replacement count, fixed-point pass count, status, first blocker, and payload pass counts. It can make `optimized.backend.opencl-c` visibly different by reusing local values, but it still does not make `backend.opencl-c` switch to the optimized source. + +Mad/FMA materialization is review-only as well. When it participates, check `madFmaMaterialization.*` for candidate/transformed-node counts, body-text replacement count, fixed-point pass count, skipped fast-math policy count, `fastMathAllowed`, status, first blocker, and payload pass counts. The raw pass proof fields still include `optimizerFamily=mad-fma-materialization`, `targetIntrinsic=mad`, and per-case `runtimeEquivalencePayload.Case.*`. It can make `optimized.backend.opencl-c` show `mad(...)`, but `backend.opencl-c` remains the selected source unless future proof/approval/selection gates explicitly allow otherwise. + +Clamp, step, and mix materialization are review-only as well. In this example, `computeVoxelDensity` intentionally contains `min(max(wave1, -1.0f), 1.0f)`, a strict ternary mask `blend > threshold ? 1.0f : 0.0f`, canonical interpolation, and expanded weighted interpolation shapes. When the optional optimizer bridge participates, `optimized.backend.opencl-c` can show `clamp(...)`, `step(...)`, and `mix(...)` candidates while `backend.opencl-c` remains the selected source. Check `clampMaterialization.*`, `stepMaterialization.*`, and `mixMaterialization.*` for candidate/transformed-node counts, direct versus inverted step counts, canonical/expanded/MAD-expanded mix counts, fast-math requirements, body-text replacement counts, status, first blocker, and payload pass counts. To tell whether `backend.opencl-c` came from optimized IR or from the original/pass-through IR, open `runtime-ir-handoff.properties` and check `selectedStage` plus `optimizedDiffersFromOriginal`. diff --git a/examples-app/src/main/java/net/sixik/ga_utils/examples/OptimizationJournalExample.java b/examples-app/src/main/java/net/sixik/ga_utils/examples/OptimizationJournalExample.java index 4b834bba..3caa55b9 100644 --- a/examples-app/src/main/java/net/sixik/ga_utils/examples/OptimizationJournalExample.java +++ b/examples-app/src/main/java/net/sixik/ga_utils/examples/OptimizationJournalExample.java @@ -37,6 +37,21 @@ public final class OptimizationJournalExample { "optimizer-report.txt", "runtime-ir-optimizer-evidence.properties" ); + private static final List EXPECTED_OPTIMIZED_SOURCE_MARKERS = List.of( + "mad(", + "clamp(", + "step(", + "mix(", + "vload4(" + ); + private static final List EXPECTED_EVIDENCE_MARKERS = List.of( + "madFmaMaterialization.status", + "clampMaterialization.status", + "stepMaterialization.status", + "mixMaterialization.status", + "loopVectorizationMaterialization.status", + "runtimeEquivalenceReview.familySummary" + ); private OptimizationJournalExample() { } @@ -46,8 +61,12 @@ public static void main(String[] args) { String previousArtifactDirectory = System.getProperty(ARTIFACT_DIRECTORY_PROPERTY); System.setProperty(ARTIFACT_DIRECTORY_PROPERTY, journalRoot.toString()); - float[] input = new float[]{1.0f, 2.0f, 3.0f, 4.0f}; - float[] output = new float[input.length]; + int vectorRows = 4; + float[] input = new float[vectorRows * 4]; + for (int index = 0; index < input.length; index++) { + input[index] = index + 1.0f; + } + float[] output = new float[vectorRows]; GpuRuntimeCompileOptions compileOptions = GpuRuntimeCompileOptions.openCl( List.of(), "diagnostic" @@ -58,16 +77,37 @@ public static void main(String[] args) { System.out.println("Artifact journal root: " + journalRoot.toAbsolutePath().normalize()); System.out.println("Runtime artifact property: -D" + ARTIFACT_DIRECTORY_PROPERTY + "=" + journalRoot); + + int sizeX = 16; + int sizeY = 256; + int sizeZ = 16; + + float chunkOffsetX = 1024.0f; + float chunkOffsetY = 0.0f; + float chunkOffsetZ = -512.0f; + + int totalVoxels = sizeX * sizeY * sizeZ; + float[] densityMap = new float[totalVoxels]; + try (GpuRuntimeScope ignored = GpuRuntime.useOpenClSharedCache()) { GpuGeneratedLauncherInvoker.invokeWithConfigAndCompileOptions( OptimizationJournalExample.class, "optimizerJournalKernel", - GpuExecutionConfig.oneDimensional(input.length), + GpuExecutionConfig.oneDimensional(output.length), compileOptions, input, output, 0f ); + GpuGeneratedLauncherInvoker.invokeWithConfigAndCompileOptions( + OptimizationJournalExample.class, + "computeVoxelDensity", + GpuExecutionConfig.threeDimensional(sizeX, sizeY, sizeZ), + compileOptions, + densityMap, + sizeX, sizeY, + chunkOffsetX, chunkOffsetY, chunkOffsetZ + ); System.out.println("output[0] = " + output[0]); printJournal(journalRoot); } catch (RuntimeException exception) { @@ -80,7 +120,7 @@ public static void main(String[] args) { } @net.sixik.ga_utils.javatogpu.api.annotations.GPU - @GPUOptimize(fastMath = true) // Разрешаем оптимизатору применять fma/mad/mix и менять округление + @GPUOptimize(fastMath = true) // Allows diagnostic peephole evidence such as mad/fma/mix planning. public static void optimizerJournalKernel( @GPUGlobal float[] input, @GPUGlobal float[] output, @@ -89,37 +129,74 @@ public static void optimizerJournalKernel( int id = GPU.get_global_id(0); float value = input[id]; - // 1. Мишень для CSE (Common Subexpression Elimination) - // Выражение (value * 3.1415f) повторяется три раза подряд без мутации 'value'. - float a = value * 3.1415f; - float b = (value * 3.1415f) + 10.0f; - float c = (value * 3.1415f) - 5.0f; - - // 2. Мишень для InstCombine (mad / fma) - // Паттерн a * b + c - float fmaTarget = a * b + c; + // Existing-local CSE review candidate: later repeats can reuse scale in optimized.backend.opencl-c. + float scale = value * 3.1415f; + float shifted = (value * 3.1415f) + 10.0f; + float lowered = (value * 3.1415f) - 5.0f; - // 3. Мишень для InstCombine (clamp) - // Паттерн min(max(x, lo), hi) + // Fast-math mad/fma now materializes into the optimized review artifact only. + float fmaTarget = scale * shifted + lowered; float clampTarget = GPU.min(GPU.max(fmaTarget, 0.0f), 1.0f); + float mixTarget = scale + t * (shifted - scale); - // 4. Мишень для InstCombine (mix / lerp) - // Паттерн a + t * (b - a) - float mixTarget = a + t * (b - a); + // Constant-folding materialization can collapse this in the optimized review artifact. + int foldedSeed = (2 + 3) * 4; - // 5. Мишень для Auto-vectorization - // Последовательный доступ к памяти внутри цикла с фиксированным размером float sum = 0.0f; for (int i = 0; i < 4; i++) { sum = sum + input[id * 4 + i]; } - // 6. Давление на регистры (Register Pressure) - // Множество промежуточных переменных сходятся в одном тяжелом математическом выражении, - // что должно отразиться в GpuRuntimeRegisterPressureAnalyzer - float heavyMath = GPU.sin(clampTarget) + GPU.cos(mixTarget) + sum; + foldedSeed += (int) sum; - output[id] = heavyMath; + // A small expression chain keeps register-pressure diagnostics visible in the journal. + float heavyMath = GPU.sin(clampTarget) + GPU.cos(mixTarget) + lowered; + output[id] = heavyMath + foldedSeed * 0.001f; + } + + @net.sixik.ga_utils.javatogpu.api.annotations.GPU + @GPUOptimize(fastMath = true) + public static void computeVoxelDensity( + @GPUGlobal float[] densityMap, + int sizeX, int sizeY, + float offsetX, float offsetY, float offsetZ + ) { + // (3D NDRange) + int x = GPU.get_global_id(0); + int y = GPU.get_global_id(1); + int z = GPU.get_global_id(2); + + // Target CSE + int index = x + (y * sizeX) + (z * sizeX * sizeY); + + // Target (CSE + MAD/FMA) + float worldX = (x * 0.015f) + offsetX; + float worldY = (y * 0.015f) + offsetY; + float worldZ = (z * 0.015f) + offsetZ; + + // Target (MAD/FMA) + float nx = worldX * 2.5f + 10.0f; + float ny = worldY * 2.5f + 10.0f; + float nz = worldZ * 2.5f + 10.0f; + + // Target Register + float wave1 = GPU.sin(nx) * GPU.cos(ny) + GPU.sin(nz); + float wave2 = GPU.cos(nx) * GPU.sin(ny) + GPU.cos(nz); + + // Target (Clamp (min/max)) = clamp(wave1, -1.0f, 1.0f)? + float clampedWave = GPU.min(GPU.max(wave1, -1.0f), 1.0f); + + // Target hidden Mix/Lerp = mix(wave2, clampedWave, 0.5f) -> a + t * (b - a)? + float blend = wave2 + 0.5f * (clampedWave - wave2); + + // Target Step + float threshold = 0.25f; + float isSolid = blend > threshold ? 1.0f : 0.0f; + + // Target (Mix/MAD) = mix(clampedWave * 0.1f, blend, isSolid) + float finalDensity = blend * isSolid + (1.0f - isSolid) * (clampedWave * 0.1f); + + densityMap[index] = finalDensity; } static Path resolveJournalRoot(String[] args) { @@ -129,6 +206,14 @@ static Path resolveJournalRoot(String[] args) { return DEFAULT_JOURNAL_ROOT; } + static List expectedOptimizedSourceMarkers() { + return EXPECTED_OPTIMIZED_SOURCE_MARKERS; + } + + static List expectedEvidenceMarkers() { + return EXPECTED_EVIDENCE_MARKERS; + } + static List interestingJournalFiles(Path journalRoot) throws IOException { if (journalRoot == null || !Files.isDirectory(journalRoot)) { return List.of(); @@ -153,6 +238,14 @@ private static void printJournal(Path journalRoot) { for (Path file : files) { System.out.println(" - " + journalRoot.relativize(file)); } + System.out.println("Expected review-only markers in optimized.backend.opencl-c:"); + for (String marker : expectedOptimizedSourceMarkers()) { + System.out.println(" - " + marker); + } + System.out.println("Expected evidence markers in runtime-ir-optimizer-evidence.properties:"); + for (String marker : expectedEvidenceMarkers()) { + System.out.println(" - " + marker); + } } catch (IOException exception) { System.out.println("Failed to list journal artifacts: " + exception.getMessage()); } diff --git a/examples-app/src/test/java/net/sixik/ga_utils/examples/IrGpuGeneratedArtifactParityTest.java b/examples-app/src/test/java/net/sixik/ga_utils/examples/IrGpuGeneratedArtifactParityTest.java index 4f68cd83..1cc84a71 100644 --- a/examples-app/src/test/java/net/sixik/ga_utils/examples/IrGpuGeneratedArtifactParityTest.java +++ b/examples-app/src/test/java/net/sixik/ga_utils/examples/IrGpuGeneratedArtifactParityTest.java @@ -26,7 +26,7 @@ void generatedIrGpuArtifactsReconstructToGeneratedOpenClSources() throws Excepti List classpathRoots = javatogpuClasspathRoots(); List manifests = generatedIrGpuManifests(classpathRoots); - assertEquals(29, manifests.size(), "examples-app should keep all showcase IrGpu artifacts under parity coverage"); + assertEquals(30, manifests.size(), "examples-app should keep all showcase IrGpu artifacts under parity coverage"); ArrayList failures = new ArrayList<>(); for (Path manifestPath : manifests) { diff --git a/examples-app/src/test/java/net/sixik/ga_utils/examples/OptimizationJournalExampleTest.java b/examples-app/src/test/java/net/sixik/ga_utils/examples/OptimizationJournalExampleTest.java index 9237e3fc..57468ff0 100644 --- a/examples-app/src/test/java/net/sixik/ga_utils/examples/OptimizationJournalExampleTest.java +++ b/examples-app/src/test/java/net/sixik/ga_utils/examples/OptimizationJournalExampleTest.java @@ -1,17 +1,38 @@ package net.sixik.ga_utils.examples; +import net.sixik.ga_utils.javatogpu.api.GpuBackendTarget; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactParser; +import net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrClampMaterializationProposalProvider; +import net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrMixMaterializationProposalProvider; +import net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrOptimizationSandwichRunner; +import net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrProposalRuntimeBridgePass; +import net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrStepMaterializationProposalProvider; +import net.sixik.ga_utils.javatogpu.runtime.GpuKernelDescriptor; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileOptions; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileRequest; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeDeviceProfile; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationReport; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationRequest; +import net.sixik.ga_utils.javatogpu.runtime.opencl.OpenClIrGpuSourceEmission; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; +import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; class OptimizationJournalExampleTest { + private static final String DENSITY_IRGPU_RESOURCE = + "javatogpu/net/sixik/ga_utils/examples/OptimizationJournalExample/computeVoxelDensity.irgpu.properties"; + @TempDir Path temporaryDirectory; @@ -32,16 +53,81 @@ void listsOnlyRelevantJournalArtifacts() throws Exception { Files.writeString(kernelDirectory.resolve("original.irgpu.properties"), "original\n"); Files.writeString(kernelDirectory.resolve("optimized.irgpu.properties"), "optimized\n"); Files.writeString(kernelDirectory.resolve("runtime-ir-handoff.properties"), "handoff\n"); + Files.writeString(kernelDirectory.resolve("optimizer-report.txt"), "report\n"); + Files.writeString(kernelDirectory.resolve("runtime-ir-optimizer-evidence.properties"), "evidence\n"); Files.writeString(kernelDirectory.resolve("unrelated.properties"), "ignored\n"); List files = OptimizationJournalExample.interestingJournalFiles(temporaryDirectory); - assertEquals(6, files.size()); + assertEquals(8, files.size()); assertTrue(files.stream().anyMatch(path -> path.getFileName().toString().equals("backend.opencl-c"))); assertTrue(files.stream().anyMatch(path -> path.getFileName().toString().equals("original.backend.opencl-c"))); assertTrue(files.stream().anyMatch(path -> path.getFileName().toString().equals("optimized.backend.opencl-c"))); assertTrue(files.stream().anyMatch(path -> path.getFileName().toString().equals("original.irgpu.properties"))); assertTrue(files.stream().anyMatch(path -> path.getFileName().toString().equals("optimized.irgpu.properties"))); assertTrue(files.stream().anyMatch(path -> path.getFileName().toString().equals("runtime-ir-handoff.properties"))); + assertTrue(files.stream().anyMatch(path -> path.getFileName().toString().equals("optimizer-report.txt"))); + assertTrue(files.stream().anyMatch(path -> path.getFileName().toString().equals("runtime-ir-optimizer-evidence.properties"))); + } + + @Test + void exposesExpectedReviewMarkersForIntrinsicMaterializers() { + assertTrue(OptimizationJournalExample.expectedOptimizedSourceMarkers().contains("clamp(")); + assertTrue(OptimizationJournalExample.expectedOptimizedSourceMarkers().contains("step(")); + assertTrue(OptimizationJournalExample.expectedOptimizedSourceMarkers().contains("mix(")); + + assertTrue(OptimizationJournalExample.expectedEvidenceMarkers().contains("clampMaterialization.status")); + assertTrue(OptimizationJournalExample.expectedEvidenceMarkers().contains("stepMaterialization.status")); + assertTrue(OptimizationJournalExample.expectedEvidenceMarkers().contains("mixMaterialization.status")); + } + + @Test + void optimizerBridgeProducesReviewSourceWithIntrinsicMaterializersForDensityExample() throws Exception { + IrGpuArtifact original = loadGeneratedArtifact(DENSITY_IRGPU_RESOURCE); + GpuRuntimeCompileRequest compileRequest = compileRequest(original); + + GpuRuntimeIrOptimizationReport report = new GpuIrProposalRuntimeBridgePass( + List.of( + new GpuIrClampMaterializationProposalProvider(), + new GpuIrStepMaterializationProposalProvider(), + new GpuIrMixMaterializationProposalProvider() + ), + GpuIrOptimizationSandwichRunner.alwaysValid(), + false + ).run(new GpuRuntimeIrOptimizationRequest(compileRequest, Optional.of(original))); + + assertEquals(original, report.artifact().orElseThrow()); + IrGpuArtifact candidate = report.candidateArtifact().orElseThrow(); + OpenClIrGpuSourceEmission emission = OpenClIrGpuSourceEmission.inspect(candidate); + assertTrue(emission.sourceGenerated(), () -> String.join(System.lineSeparator(), emission.blockers())); + String source = emission.source(); + assertTrue(source.contains("clamp("), source); + assertTrue(source.contains("step("), source); + assertTrue(source.contains("mix("), source); + } + + private static IrGpuArtifact loadGeneratedArtifact(String resource) throws Exception { + try (InputStream stream = OptimizationJournalExampleTest.class.getClassLoader().getResourceAsStream(resource)) { + if (stream == null) { + throw new AssertionError("Missing generated IrGpu resource: " + resource); + } + return IrGpuArtifactParser.parse(new String(stream.readAllBytes(), StandardCharsets.UTF_8)); + } + } + + private static GpuRuntimeCompileRequest compileRequest(IrGpuArtifact artifact) { + GpuRuntimeDeviceProfile profile = GpuRuntimeDeviceProfile.generic(GpuBackendTarget.OPENCL, "examples-test-device"); + return new GpuRuntimeCompileRequest( + new GpuKernelDescriptor( + artifact.module().entryEmittedName(), + artifact.derivedOpenClResource(), + "", + DENSITY_IRGPU_RESOURCE, + List.of() + ), + GpuRuntimeCompileOptions.openCl(List.of(), "diagnostic"), + profile, + Optional.of(artifact) + ); } } diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrBackendNeutralSourceMaterializationProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrBackendNeutralSourceMaterializationProposalProvider.java new file mode 100644 index 00000000..f7a6305b --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrBackendNeutralSourceMaterializationProposalProvider.java @@ -0,0 +1,127 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuRegenerationMetadata; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; +import net.sixik.ga_utils.javatogpu.runtime.opencl.OpenClIrGpuSourceEmission; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Review-only materialization pass that marks reconstructable IrGpu source as backend-neutral-ready. + */ +public final class GpuIrBackendNeutralSourceMaterializationProposalProvider implements GpuIrOptimizationProposalProvider { + + public static final String PROVIDER_ID = GpuIrOptimizerModule.MODULE_ID + ".backend-neutral-source-materialization"; + public static final String PROVIDER_VERSION = PROVIDER_ID + ":1"; + + @Override + public GpuIrOptimizationProposal propose(GpuIrOptimizationProposalRequest request) { + IrGpuArtifact original = request.originalArtifact(); + if (!"OPENCL".equals(request.contextFields().getOrDefault("backendTarget", ""))) { + return GpuIrOptimizationProposal.noChange( + extensionId(), + extensionVersion(), + original, + "backend-neutral source materialization currently runs only for OpenCL runtime review" + ); + } + if (original.regenerationMetadata().backendNeutralSourceReady()) { + return GpuIrOptimizationProposal.noChange( + extensionId(), + extensionVersion(), + original, + "IrGpu backend-neutral source is already materialized" + ); + } + + OpenClIrGpuSourceEmission emission = OpenClIrGpuSourceEmission.inspect(original); + if (!emission.sourceGenerated()) { + String firstBlocker = emission.blockers().isEmpty() ? "source-emission-blocked" : emission.blockers().get(0); + return GpuIrOptimizationProposal.noChange( + extensionId(), + extensionVersion(), + original, + "backend-neutral source materialization blocked: " + firstBlocker + ); + } + + IrGpuArtifact optimized = copyWithRegenerationMetadata(original, IrGpuRegenerationMetadata.backendNeutralReady()); + return GpuIrOptimizationProposal.proposed( + extensionId(), + extensionVersion(), + original, + optimized, + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.backend-neutral-source-materialization", + "review-only-source-materialized", + proofFields(request, emission) + ), + List.of("materialized backend-neutral IrGpu source for review-only optimized backend dump") + ); + } + + @Override + public String extensionId() { + return PROVIDER_ID; + } + + @Override + public String extensionVersion() { + return PROVIDER_VERSION; + } + + @Override + public int extensionOrder() { + return 350; + } + + private static Map proofFields( + GpuIrOptimizationProposalRequest request, + OpenClIrGpuSourceEmission emission + ) { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("backendTarget", "OPENCL"); + fields.put("optimizerFamily", "backend-neutral-source-materialization"); + fields.put("rewrite.proposed", "true"); + fields.put("rewrite.materialized", "true"); + fields.put("sourceGenerated", "true"); + fields.put("sourceReady", "true"); + fields.put("sourceLength", Integer.toString(emission.source().length())); + fields.put("mutationRequired", "false"); + fields.put("materializationOnly", "true"); + fields.put("productionAffecting", "false"); + fields.put("previewOnly", "false"); + fields.put("provider.mutatesOriginal", "false"); + fields.put("policy.mutationAllowed", Boolean.toString(request.mutationAllowed())); + fields.put("proof.approvalRequiredBeforeProduction", "true"); + return Map.copyOf(fields); + } + + private static IrGpuArtifact copyWithRegenerationMetadata( + IrGpuArtifact artifact, + IrGpuRegenerationMetadata regenerationMetadata + ) { + return new IrGpuArtifact( + artifact.header(), + artifact.module(), + artifact.entryParameters(), + artifact.launchMetadata(), + artifact.validationMetadata(), + artifact.featureMetadata(), + artifact.optimizerPolicyMetadata(), + regenerationMetadata, + artifact.structMetadata(), + artifact.constants(), + artifact.constantData(), + artifact.backendOutputs(), + artifact.runtimeDefaultBackend(), + artifact.runtimeOptimizationProfile(), + artifact.methodDeviceConstraints(), + artifact.methodFallbackVariants(), + artifact.extensionParticipationMetadata() + ); + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrClampMaterializationProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrClampMaterializationProposalProvider.java new file mode 100644 index 00000000..fa4fdffd --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrClampMaterializationProposalProvider.java @@ -0,0 +1,775 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuRegenerationMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; +import net.sixik.ga_utils.javatogpu.runtime.opencl.OpenClIrGpuSourceEmission; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +/** + * Review-only materialization for {@code min(max(x, lo), hi)} into an OpenCL {@code clamp(x, lo, hi)} intrinsic. + */ +public final class GpuIrClampMaterializationProposalProvider implements GpuIrOptimizationProposalProvider { + + public static final String PROVIDER_ID = GpuIrOptimizerModule.MODULE_ID + ".clamp-materialization"; + public static final String PROVIDER_VERSION = PROVIDER_ID + ":1"; + + private static final String TARGET_INTRINSIC = "clamp"; + + @Override + public GpuIrOptimizationProposal propose(GpuIrOptimizationProposalRequest request) { + IrGpuArtifact original = request.originalArtifact(); + ArtifactRewrite rewrite = ArtifactRewrite.from(original, isOpenClReview(request), request.mutationAllowed()); + if (!rewrite.changed()) { + return new GpuIrOptimizationProposal( + extensionId(), + extensionVersion(), + original, + Optional.empty(), + GpuIrOptimizationProposalDecision.NO_CHANGE, + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.clamp-materialization", + rewrite.verdict(), + rewrite.fields(request) + ), + "", + List.of(rewrite.diagnostic()) + ); + } + + IrGpuArtifact optimized = copyWithModuleAndRegeneration( + original, + rewrite.module().orElseThrow(), + rewrite.regenerationMetadata(original) + ); + return GpuIrOptimizationProposal.proposed( + extensionId(), + extensionVersion(), + original, + optimized, + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.clamp-materialization", + "review-only-clamp-materialized", + rewrite.fields(request) + ), + List.of("materialized " + rewrite.transformedNodeCount() + + " clamp peephole rewrite(s) into a review candidate") + ); + } + + @Override + public String extensionId() { + return PROVIDER_ID; + } + + @Override + public String extensionVersion() { + return PROVIDER_VERSION; + } + + @Override + public int extensionOrder() { + return 421; + } + + private record ArtifactRewrite( + Optional module, + int methodBodyCount, + int typedBodyCount, + int candidateCount, + int transformedNodeCount, + int changedMethodBodyCount, + int bodyTextReplacementCount, + int fixedPointPassCount, + int skippedTypedBodyMissingCount, + int skippedUnsupportedFormatCount, + int skippedMissingChildReferenceCount, + int skippedUnsupportedShapeCount, + int skippedBodyTextPatternMissingCount, + List transformedCandidates, + String firstTransformedNode, + String firstExpression, + String firstReplacement, + String firstBlocker, + boolean openClReviewSourceReady, + String openClReviewSourceLength, + boolean mutationAllowed + ) { + + private static ArtifactRewrite from(IrGpuArtifact artifact, boolean openClReview, boolean mutationAllowed) { + ArrayList rewrittenBodies = new ArrayList<>(); + RewriteStats stats = new RewriteStats(); + stats.methodBodyCount = artifact.module().methodBodies().size(); + + for (IrGpuMethodBody methodBody : artifact.module().methodBodies()) { + MethodRewrite methodRewrite = rewriteMethodBody(methodBody); + rewrittenBodies.add(methodRewrite.methodBody()); + stats.add(methodRewrite.stats()); + } + + if (stats.transformedNodeCount == 0) { + return noChange(stats, firstBlocker(stats), mutationAllowed); + } + + IrGpuModule rewrittenModule = copyWithBodies(artifact.module(), rewrittenBodies); + if (openClReview) { + IrGpuArtifact optimized = copyWithModuleAndRegeneration( + artifact, + rewrittenModule, + IrGpuRegenerationMetadata.backendNeutralReady() + ); + OpenClIrGpuSourceEmission emission = OpenClIrGpuSourceEmission.inspect(optimized); + if (!emission.sourceGenerated()) { + String blocker = emission.blockers().isEmpty() + ? "opencl-review-source-emission-blocked" + : emission.blockers().get(0); + return noChange(stats, blocker, mutationAllowed); + } + return changed(stats, rewrittenModule, true, Integer.toString(emission.source().length()), mutationAllowed); + } + return changed(stats, rewrittenModule, false, "not-requested", mutationAllowed); + } + + private static ArtifactRewrite changed( + RewriteStats stats, + IrGpuModule module, + boolean openClReviewSourceReady, + String openClReviewSourceLength, + boolean mutationAllowed + ) { + return new ArtifactRewrite( + Optional.of(module), + stats.methodBodyCount, + stats.typedBodyCount, + stats.candidateCount(), + stats.transformedNodeCount, + stats.changedMethodBodyCount, + stats.bodyTextReplacementCount, + stats.fixedPointPassCount, + stats.skippedTypedBodyMissingCount, + stats.skippedUnsupportedFormatCount, + stats.skippedMissingChildReferenceCount, + stats.skippedUnsupportedShapeCount, + stats.skippedBodyTextPatternMissingCount, + List.copyOf(stats.transformedCandidates), + stats.firstTransformedNode, + stats.firstExpression, + stats.firstReplacement, + "none", + openClReviewSourceReady, + openClReviewSourceLength, + mutationAllowed + ); + } + + private static ArtifactRewrite noChange(RewriteStats stats, String firstBlocker, boolean mutationAllowed) { + return new ArtifactRewrite( + Optional.empty(), + stats.methodBodyCount, + stats.typedBodyCount, + stats.candidateCount(), + stats.transformedNodeCount, + stats.changedMethodBodyCount, + stats.bodyTextReplacementCount, + stats.fixedPointPassCount, + stats.skippedTypedBodyMissingCount, + stats.skippedUnsupportedFormatCount, + stats.skippedMissingChildReferenceCount, + stats.skippedUnsupportedShapeCount, + stats.skippedBodyTextPatternMissingCount, + List.copyOf(stats.transformedCandidates), + stats.firstTransformedNode, + stats.firstExpression, + stats.firstReplacement, + firstBlocker, + false, + "not-ready", + mutationAllowed + ); + } + + private boolean changed() { + return module.isPresent() && transformedNodeCount > 0; + } + + private IrGpuRegenerationMetadata regenerationMetadata(IrGpuArtifact original) { + return openClReviewSourceReady + ? IrGpuRegenerationMetadata.backendNeutralReady() + : original.regenerationMetadata(); + } + + private String verdict() { + return changed() ? "materialized-review-candidate" : "materialization-no-change"; + } + + private String diagnostic() { + if (changed()) { + return "clamp materialization rewrote " + transformedNodeCount + + " min/max expression(s) to OpenCL clamp intrinsic calls"; + } + return "clamp materialization produced no review candidate: " + firstBlocker; + } + + private Map fields(GpuIrOptimizationProposalRequest request) { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("methodBody.count", Integer.toString(methodBodyCount)); + fields.put("methodBody.rewriteScope", "all-method-bodies"); + fields.put("typedBody.count", Integer.toString(typedBodyCount)); + fields.put("candidate.count", Integer.toString(candidateCount)); + fields.put("transformedNode.count", Integer.toString(transformedNodeCount)); + fields.put("changedMethodBody.count", Integer.toString(changedMethodBodyCount)); + fields.put("bodyTextReplacement.count", Integer.toString(bodyTextReplacementCount)); + fields.put("fixedPoint.enabled", "true"); + fields.put("fixedPoint.pass.count", Integer.toString(fixedPointPassCount)); + fields.put("fixedPoint.reachableNodeScan", "true"); + fields.put("fixedPoint.rewriteGranularity", "single-clamp-expression-per-pass"); + fields.put("skipped.typedBodyMissing.count", Integer.toString(skippedTypedBodyMissingCount)); + fields.put("skipped.unsupportedFormat.count", Integer.toString(skippedUnsupportedFormatCount)); + fields.put("skipped.missingChildReference.count", Integer.toString(skippedMissingChildReferenceCount)); + fields.put("skipped.unsupportedShape.count", Integer.toString(skippedUnsupportedShapeCount)); + fields.put("skipped.bodyTextPatternMissing.count", Integer.toString(skippedBodyTextPatternMissingCount)); + fields.put("rewrite.proposed", Boolean.toString(changed())); + fields.put("rewrite.materialized", Boolean.toString(changed())); + fields.put("optimizerFamily", "clamp-materialization"); + fields.put("targetIntrinsic", TARGET_INTRINSIC); + fields.put("mutationRequired", "false"); + fields.put("productionAffecting", "false"); + fields.put("previewOnly", "false"); + fields.put("provider.mutatesOriginal", "false"); + fields.put("policy.mutationAllowed", Boolean.toString(mutationAllowed)); + fields.put("policy.proposalOnly", Boolean.toString(!mutationAllowed)); + fields.put("policy.rollbackRequired", Boolean.toString(request.policy().rollbackRequired())); + fields.put("policy.proofRequired", Boolean.toString(request.policy().proofRequired())); + fields.put("proof.runtimeEquivalenceRequiredBeforeSelection", Boolean.toString(changed())); + fields.put("proof.runtimeEquivalencePayloadRequiredBeforeSelection", Boolean.toString(changed())); + fields.put("proof.approvalRequiredBeforeProduction", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.required", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.status", changed() ? "recorded" : "not-required"); + fields.put("runtimeEquivalencePayload.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.passed", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.firstBlocker", changed() ? "none" : firstBlocker); + fields.put("runtimeEquivalencePayload.comparisonMode", "optimizer-family:clamp-materialization:review-candidate"); + fields.put("runtimeEquivalencePayload.resource", "ir-optimizer://clamp-materialization/review-candidate"); + fields.put("runtimeEquivalencePayload.cpuReference.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.preOptimizationOutput.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.postOptimizationOutput.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.tolerance.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.failureFixture.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.CpuReference", "clampCandidates=" + transformedNodeCount); + fields.put("runtimeEquivalencePayload.PreOptimizationOutput", "expressions=" + transformedNodeCount + + ", first=" + firstExpression); + fields.put("runtimeEquivalencePayload.PostOptimizationOutput", "intrinsicCalls=" + transformedNodeCount + + ", first=" + firstReplacement); + fields.put("runtimeEquivalencePayload.Tolerance", "mode=exact-min-max-to-clamp-review"); + fields.put("runtimeEquivalencePayload.FailureFixture", "none"); + fields.put("runtimeEquivalencePayload.ReferenceMode", "static-min-max-to-clamp"); + fields.put("runtimeEquivalencePayload.CaseIdentity", "method-name-and-node-id"); + appendRuntimeEquivalenceCases(fields, transformedCandidates); + fields.put("reviewPackage.required", Boolean.toString(changed())); + fields.put("reviewPackage.firstBlocker", changed() ? "approval-pending" : firstBlocker); + fields.put("safety.scope", "opencl-clamp-review-candidate"); + fields.put("safety.fastMathRequired", "false"); + fields.put("safety.strictFloatPreserved", "true"); + fields.put("safety.sideEffectFreedomProven", Boolean.toString(changed())); + fields.put("safety.argumentOrderPreserved", Boolean.toString(changed())); + fields.put("safety.newTemporaryIntroduced", "false"); + fields.put("openClReview.sourceReady", Boolean.toString(openClReviewSourceReady)); + fields.put("openClReview.sourceLength", openClReviewSourceLength); + fields.put("firstTransformedNode", firstTransformedNode); + fields.put("firstExpression", firstExpression); + fields.put("firstReplacement", firstReplacement); + fields.put("firstBlocker", changed() ? "none" : firstBlocker); + return Map.copyOf(fields); + } + + private static String firstBlocker(RewriteStats stats) { + if (!"none".equals(stats.firstBlocker)) { + return stats.firstBlocker; + } + if (stats.typedBodyCount == 0) { + return "typed-body-missing"; + } + if (stats.candidateCount() == 0) { + return "no-clamp-candidate"; + } + return "body-text-pattern-missing"; + } + } + + private record MethodRewrite(IrGpuMethodBody methodBody, RewriteStats stats) { + } + + private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody) { + RewriteStats stats = new RewriteStats(); + if (!methodBody.typedBody().available()) { + stats.skippedTypedBodyMissingCount++; + stats.setFirstBlocker("typed-body-missing"); + return new MethodRewrite(methodBody, stats); + } + stats.typedBodyCount = 1; + if (!"ir-text-v1".equals(methodBody.format())) { + stats.skippedUnsupportedFormatCount++; + stats.setFirstBlocker("method-body-format-unsupported-" + methodBody.format()); + return new MethodRewrite(methodBody, stats); + } + + IrGpuTypedBody currentTypedBody = methodBody.typedBody(); + String currentBody = methodBody.body(); + boolean changed = false; + int maxPasses = Math.max(1, currentTypedBody.nodes().size() + 1); + for (int pass = 0; pass < maxPasses; pass++) { + Optional candidate = firstMaterializableCandidate(methodBody.name(), currentTypedBody, currentBody, stats); + if (candidate.isEmpty()) { + break; + } + ClampCandidate value = candidate.orElseThrow(); + Replacement replacement = replaceFirst(currentBody, value.expressionText(), value.replacementText()); + if (!replacement.replaced()) { + stats.skippedBodyTextPatternMissingCount++; + stats.setFirstBlocker("body-text-pattern-missing"); + break; + } + changed = true; + stats.fixedPointPassCount++; + stats.transformedNodeCount++; + stats.bodyTextReplacementCount++; + stats.transformedCandidates.add(value); + if ("none".equals(stats.firstTransformedNode)) { + stats.firstTransformedNode = value.summary(); + stats.firstExpression = value.expressionText(); + stats.firstReplacement = value.replacementText(); + } + currentBody = replacement.body(); + currentTypedBody = rewriteTypedBody(currentTypedBody, value); + } + + if (!changed) { + return new MethodRewrite(methodBody, stats); + } + if (stats.fixedPointPassCount >= maxPasses) { + stats.setFirstBlocker("fixed-point-pass-limit-reached"); + } + stats.changedMethodBodyCount = 1; + return new MethodRewrite(copyWithBodyAndTypedBody(methodBody, currentBody, currentTypedBody), stats); + } + + private static Optional firstMaterializableCandidate( + String methodName, + IrGpuTypedBody typedBody, + String body, + RewriteStats stats + ) { + Map nodesById = nodesById(typedBody); + Set reachableNodeIds = reachableNodeIds(typedBody, nodesById); + for (IrGpuTypedNode node : typedBody.nodes()) { + if (!reachableNodeIds.contains(node.id()) || !isCall(node, "min")) { + continue; + } + Optional candidate = analyzeCandidate(methodName, node, nodesById); + if (candidate.isEmpty()) { + stats.skippedUnsupportedShapeCount++; + stats.setFirstBlocker("clamp-shape-unsupported"); + continue; + } + ClampCandidate value = candidate.orElseThrow(); + stats.recordCandidate(value); + if (!body.contains(value.expressionText())) { + stats.skippedBodyTextPatternMissingCount++; + stats.setFirstBlocker("body-text-pattern-missing"); + continue; + } + return Optional.of(value); + } + return Optional.empty(); + } + + private static Optional analyzeCandidate( + String methodName, + IrGpuTypedNode minNode, + Map nodesById + ) { + List minArgs = callArguments(minNode); + if (minArgs.size() != 2) { + return Optional.empty(); + } + IrGpuTypedNode maxNode = nodesById.get(minArgs.get(0)); + if (!isCall(maxNode, "max")) { + return Optional.empty(); + } + List maxArgs = callArguments(maxNode); + if (maxArgs.size() != 2) { + return Optional.empty(); + } + int valueId = maxArgs.get(0); + int lowId = maxArgs.get(1); + int highId = minArgs.get(1); + Optional expressionText = sourceText(minNode, nodesById); + Optional valueText = sourceText(nodesById.get(valueId), nodesById); + Optional lowText = sourceText(nodesById.get(lowId), nodesById); + Optional highText = sourceText(nodesById.get(highId), nodesById); + if (expressionText.isEmpty() || valueText.isEmpty() || lowText.isEmpty() || highText.isEmpty()) { + return Optional.empty(); + } + String replacementText = "intrinsic(" + TARGET_INTRINSIC + " template=\"\" args=[" + + valueText.orElseThrow() + ", " + + lowText.orElseThrow() + ", " + + highText.orElseThrow() + "])"; + return Optional.of(new ClampCandidate( + methodName, + minNode.id(), + maxNode.id(), + List.of(valueId, lowId, highId), + expressionText.orElseThrow(), + replacementText, + List.of(valueText.orElseThrow(), lowText.orElseThrow(), highText.orElseThrow()) + )); + } + + private record ClampCandidate( + String methodName, + int rootNodeId, + int maxNodeId, + List argumentNodeIds, + String expressionText, + String replacementText, + List argumentTexts + ) { + + private String summary() { + return methodName + "#" + rootNodeId + "=" + expressionText + "->" + replacementText; + } + } + + private static void appendRuntimeEquivalenceCases( + LinkedHashMap fields, + List candidates + ) { + fields.put("runtimeEquivalencePayload.Case.Count", Integer.toString(candidates.size())); + for (int index = 0; index < candidates.size(); index++) { + ClampCandidate candidate = candidates.get(index); + String prefix = "runtimeEquivalencePayload.Case." + index; + fields.put(prefix + ".Name", candidate.summary()); + fields.put(prefix + ".MethodName", candidate.methodName()); + fields.put(prefix + ".NodeId", Integer.toString(candidate.rootNodeId())); + fields.put(prefix + ".RewriteKind", "min-max-to-opencl-clamp"); + fields.put(prefix + ".Successful", "true"); + fields.put(prefix + ".Input.Count", "4"); + fields.put(prefix + ".Input.0.Name", "expression"); + fields.put(prefix + ".Input.0.Value", candidate.expressionText()); + fields.put(prefix + ".Input.1.Name", "value"); + fields.put(prefix + ".Input.1.Value", candidate.argumentTexts().get(0)); + fields.put(prefix + ".Input.2.Name", "low"); + fields.put(prefix + ".Input.2.Value", candidate.argumentTexts().get(1)); + fields.put(prefix + ".Input.3.Name", "high"); + fields.put(prefix + ".Input.3.Value", candidate.argumentTexts().get(2)); + fields.put(prefix + ".Output.Count", "1"); + fields.put(prefix + ".Output.0.Name", TARGET_INTRINSIC); + fields.put(prefix + ".Output.0.CpuReference", candidate.expressionText()); + fields.put(prefix + ".Output.0.PreOptimization", candidate.expressionText()); + fields.put(prefix + ".Output.0.PostOptimization", candidate.replacementText()); + fields.put(prefix + ".Output.0.Tolerance", "exact-min-max-to-clamp-review"); + fields.put(prefix + ".Output.0.Equivalent", "true"); + fields.put(prefix + ".FailureFixture.Diagnostic.Count", "0"); + } + } + + private static Optional sourceText(IrGpuTypedNode node, Map nodesById) { + if (node == null) { + return Optional.empty(); + } + if ("GpuIrVariableRef".equals(node.kind())) { + String name = node.attributes().getOrDefault("name", ""); + return name.isBlank() ? Optional.empty() : Optional.of(name); + } + if ("GpuIrLiteral".equals(node.kind())) { + String sourceText = node.attributes().getOrDefault("sourceText", ""); + return sourceText.isBlank() ? Optional.empty() : Optional.of(sourceText); + } + if ("GpuIrUnary".equals(node.kind())) { + String operator = node.attributes().getOrDefault("operator", ""); + Optional operand = childSourceText(node, "operand", nodesById); + if (operator.isBlank() || operand.isEmpty()) { + return Optional.empty(); + } + return Optional.of("(" + operator + operand.orElseThrow() + ")"); + } + if ("GpuIrBinary".equals(node.kind())) { + String operator = node.attributes().getOrDefault("operator", ""); + Optional left = childSourceText(node, "left", nodesById); + Optional right = childSourceText(node, "right", nodesById); + if (operator.isBlank() || left.isEmpty() || right.isEmpty()) { + return Optional.empty(); + } + return Optional.of("(" + left.orElseThrow() + " " + operator + " " + right.orElseThrow() + ")"); + } + if ("GpuIrIntrinsicCall".equals(node.kind())) { + String name = firstAttribute(node, "name", "backendName", "function", "intrinsic"); + if (name.isBlank()) { + return Optional.empty(); + } + ArrayList args = new ArrayList<>(); + for (Integer argumentId : callArguments(node)) { + Optional argumentText = sourceText(nodesById.get(argumentId), nodesById); + if (argumentText.isEmpty()) { + return Optional.empty(); + } + args.add(argumentText.orElseThrow()); + } + String template = node.attributes().getOrDefault("codeTemplate", ""); + return Optional.of("intrinsic(" + name + " template=\"" + template + "\" args=[" + + String.join(", ", args) + "])"); + } + return Optional.empty(); + } + + private static Optional childSourceText( + IrGpuTypedNode node, + String childName, + Map nodesById + ) { + Integer childId = singleChild(node, childName); + if (childId == null) { + return Optional.empty(); + } + return sourceText(nodesById.get(childId), nodesById); + } + + private record Replacement(String body, boolean replaced) { + } + + private static Replacement replaceFirst(String body, String expressionText, String replacementText) { + int index = body.indexOf(expressionText); + if (index < 0) { + return new Replacement(body, false); + } + return new Replacement( + body.substring(0, index) + replacementText + body.substring(index + expressionText.length()), + true + ); + } + + private static IrGpuTypedBody rewriteTypedBody(IrGpuTypedBody typedBody, ClampCandidate candidate) { + ArrayList nodes = new ArrayList<>(); + for (IrGpuTypedNode node : typedBody.nodes()) { + if (node.id() == candidate.rootNodeId()) { + nodes.add(replacementNode(node.id(), candidate)); + continue; + } + nodes.add(node); + } + return new IrGpuTypedBody(typedBody.format(), typedBody.rootNodeIds(), nodes); + } + + private static IrGpuTypedNode replacementNode(int nodeId, ClampCandidate candidate) { + LinkedHashMap attributes = new LinkedHashMap<>(); + attributes.put("name", TARGET_INTRINSIC); + attributes.put("backendName", TARGET_INTRINSIC); + attributes.put("codeTemplate", ""); + attributes.put("receiver.null", "true"); + attributes.put("resultType", "clamp-review"); + attributes.put("argumentTypes.count", "3"); + attributes.put("argumentTypes.0", "clamp-review"); + attributes.put("argumentTypes.1", "clamp-review"); + attributes.put("argumentTypes.2", "clamp-review"); + return new IrGpuTypedNode( + nodeId, + "GpuIrIntrinsicCall", + attributes, + Map.of("arguments", candidate.argumentNodeIds()) + ); + } + + private static Set reachableNodeIds(IrGpuTypedBody typedBody, Map nodesById) { + LinkedHashSet reachable = new LinkedHashSet<>(); + ArrayList pending = new ArrayList<>(typedBody.rootNodeIds()); + while (!pending.isEmpty()) { + int nodeId = pending.remove(pending.size() - 1); + if (!reachable.add(nodeId)) { + continue; + } + IrGpuTypedNode node = nodesById.get(nodeId); + if (node == null) { + continue; + } + for (List childIds : node.children().values()) { + for (Integer childId : childIds) { + if (childId != null && nodesById.containsKey(childId) && !reachable.contains(childId)) { + pending.add(childId); + } + } + } + } + return Set.copyOf(reachable); + } + + private static Integer singleChild(IrGpuTypedNode node, String name) { + if (node == null) { + return null; + } + List ids = node.children().getOrDefault(name, List.of()); + return ids.size() == 1 ? ids.get(0) : null; + } + + private static List callArguments(IrGpuTypedNode node) { + if (node == null) { + return List.of(); + } + List args = node.children().getOrDefault("args", List.of()); + return args.isEmpty() ? node.children().getOrDefault("arguments", List.of()) : args; + } + + private static boolean isCall(IrGpuTypedNode node, String backendName) { + return node != null + && "GpuIrIntrinsicCall".equals(node.kind()) + && backendName.equals(firstAttribute(node, "backendName", "name", "function", "intrinsic")); + } + + private static String firstAttribute(IrGpuTypedNode node, String... names) { + if (node == null || names == null) { + return ""; + } + for (String name : names) { + String value = node.attributes().get(name); + if (value != null) { + return value; + } + } + return ""; + } + + private static Map nodesById(IrGpuTypedBody typedBody) { + LinkedHashMap nodes = new LinkedHashMap<>(); + for (IrGpuTypedNode node : typedBody.nodes()) { + nodes.put(node.id(), node); + } + return Map.copyOf(nodes); + } + + private static boolean isOpenClReview(GpuIrOptimizationProposalRequest request) { + return "OPENCL".equals(request.contextFields().getOrDefault("backendTarget", "")); + } + + private static IrGpuMethodBody copyWithBodyAndTypedBody( + IrGpuMethodBody methodBody, + String body, + IrGpuTypedBody typedBody + ) { + return new IrGpuMethodBody( + methodBody.role(), + methodBody.name(), + methodBody.emittedName(), + methodBody.format(), + body, + typedBody, + methodBody.bodyIndex(), + methodBody.helperDependencies(), + methodBody.sourceLocation() + ); + } + + private static IrGpuModule copyWithBodies(IrGpuModule module, List methodBodies) { + return new IrGpuModule( + module.entryMethod(), + module.entryEmittedName(), + module.entryOpenClAttributes(), + module.entryAttributeMetadata(), + module.helperMethods(), + module.structs(), + methodBodies + ); + } + + private static IrGpuArtifact copyWithModuleAndRegeneration( + IrGpuArtifact artifact, + IrGpuModule module, + IrGpuRegenerationMetadata regenerationMetadata + ) { + return new IrGpuArtifact( + artifact.header(), + module, + artifact.entryParameters(), + artifact.launchMetadata(), + artifact.validationMetadata(), + artifact.featureMetadata(), + artifact.optimizerPolicyMetadata(), + regenerationMetadata, + artifact.structMetadata(), + artifact.constants(), + artifact.constantData(), + artifact.backendOutputs(), + artifact.runtimeDefaultBackend(), + artifact.runtimeOptimizationProfile(), + artifact.methodDeviceConstraints(), + artifact.methodFallbackVariants(), + artifact.extensionParticipationMetadata() + ); + } + + private static final class RewriteStats { + private int methodBodyCount; + private int typedBodyCount; + private final LinkedHashSet candidateKeys = new LinkedHashSet<>(); + private int transformedNodeCount; + private int changedMethodBodyCount; + private int bodyTextReplacementCount; + private int fixedPointPassCount; + private int skippedTypedBodyMissingCount; + private int skippedUnsupportedFormatCount; + private int skippedMissingChildReferenceCount; + private int skippedUnsupportedShapeCount; + private int skippedBodyTextPatternMissingCount; + private final ArrayList transformedCandidates = new ArrayList<>(); + private String firstTransformedNode = "none"; + private String firstExpression = "none"; + private String firstReplacement = "none"; + private String firstBlocker = "none"; + + private int candidateCount() { + return candidateKeys.size(); + } + + private void recordCandidate(ClampCandidate candidate) { + candidateKeys.add(candidate.methodName() + "#" + candidate.rootNodeId()); + } + + private void add(RewriteStats other) { + typedBodyCount += other.typedBodyCount; + candidateKeys.addAll(other.candidateKeys); + transformedNodeCount += other.transformedNodeCount; + changedMethodBodyCount += other.changedMethodBodyCount; + bodyTextReplacementCount += other.bodyTextReplacementCount; + fixedPointPassCount += other.fixedPointPassCount; + skippedTypedBodyMissingCount += other.skippedTypedBodyMissingCount; + skippedUnsupportedFormatCount += other.skippedUnsupportedFormatCount; + skippedMissingChildReferenceCount += other.skippedMissingChildReferenceCount; + skippedUnsupportedShapeCount += other.skippedUnsupportedShapeCount; + skippedBodyTextPatternMissingCount += other.skippedBodyTextPatternMissingCount; + transformedCandidates.addAll(other.transformedCandidates); + if ("none".equals(firstTransformedNode) && !"none".equals(other.firstTransformedNode)) { + firstTransformedNode = other.firstTransformedNode; + firstExpression = other.firstExpression; + firstReplacement = other.firstReplacement; + } + if ("none".equals(firstBlocker) && !"none".equals(other.firstBlocker)) { + firstBlocker = other.firstBlocker; + } + } + + private void setFirstBlocker(String blocker) { + if ("none".equals(firstBlocker) && blocker != null && !blocker.isBlank() && !"none".equals(blocker)) { + firstBlocker = blocker; + } + } + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingMaterializationProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingMaterializationProposalProvider.java new file mode 100644 index 00000000..3391f24e --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingMaterializationProposalProvider.java @@ -0,0 +1,1072 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuRegenerationMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; +import net.sixik.ga_utils.javatogpu.runtime.opencl.OpenClIrGpuSourceEmission; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * First narrow materialized constant-folding rewrite for plain 32-bit integer literal binary expressions. + */ +public final class GpuIrConstantFoldingMaterializationProposalProvider implements GpuIrOptimizationProposalProvider { + + public static final String PROVIDER_ID = GpuIrOptimizerModule.MODULE_ID + ".constant-folding-materialization"; + public static final String PROVIDER_VERSION = PROVIDER_ID + ":1"; + + private static final BigInteger INT_MIN = BigInteger.valueOf(Integer.MIN_VALUE); + private static final BigInteger INT_MAX = BigInteger.valueOf(Integer.MAX_VALUE); + private static final Pattern PLAIN_INTEGER_LITERAL = Pattern.compile("[-+]?(?:0|[1-9][0-9]*)"); + + @Override + public GpuIrOptimizationProposal propose(GpuIrOptimizationProposalRequest request) { + IrGpuArtifact original = request.originalArtifact(); + ArtifactRewrite rewrite = ArtifactRewrite.from(original, isOpenClReview(request)); + if (!rewrite.changed()) { + return GpuIrOptimizationProposal.noChange( + extensionId(), + extensionVersion(), + original, + rewrite.noChangeDiagnostic() + ); + } + + IrGpuArtifact optimized = copyWithModuleAndRegeneration( + original, + rewrite.module().orElseThrow(), + rewrite.regenerationMetadata(original) + ); + return GpuIrOptimizationProposal.proposed( + extensionId(), + extensionVersion(), + original, + optimized, + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.constant-folding-materialization", + "review-only-constant-folding-materialized", + rewrite.fields(request) + ), + List.of("materialized " + rewrite.transformedNodeCount() + + " integer constant-folding rewrite(s) into a review candidate") + ); + } + + @Override + public String extensionId() { + return PROVIDER_ID; + } + + @Override + public String extensionVersion() { + return PROVIDER_VERSION; + } + + @Override + public int extensionOrder() { + return 405; + } + + private record ArtifactRewrite( + Optional module, + int methodBodyCount, + int typedBodyCount, + int candidateCount, + int transformedNodeCount, + int literalRewriteCount, + int identityRewriteCount, + int fixedPointPassCount, + int changedMethodBodyCount, + int bodyTextReplacementCount, + int skippedUnsupportedOperatorCount, + int skippedNonIntegerLiteralCount, + int skippedNonLiteralOperandCount, + int skippedDivideByZeroCount, + int skippedNonEvenDivisionCount, + int skippedIntegerRangeCount, + int skippedIntegerOverflowCount, + int skippedBodyTextPatternMissingCount, + String firstTransformedNode, + String firstFoldedExpression, + String firstFoldedValue, + List transformedCandidates, + String firstBlocker, + boolean openClReviewSourceReady, + String openClReviewSourceLength + ) { + + private static ArtifactRewrite from(IrGpuArtifact artifact, boolean openClReview) { + ArrayList rewrittenBodies = new ArrayList<>(); + RewriteStats stats = new RewriteStats(); + stats.methodBodyCount = artifact.module().methodBodies().size(); + + for (IrGpuMethodBody methodBody : artifact.module().methodBodies()) { + MethodRewrite methodRewrite = rewriteMethodBody(methodBody); + rewrittenBodies.add(methodRewrite.methodBody()); + stats.add(methodRewrite.stats()); + } + + if (stats.transformedNodeCount == 0) { + return noChange(stats, firstBlocker(stats)); + } + + IrGpuModule rewrittenModule = copyWithBodies(artifact.module(), rewrittenBodies); + if (openClReview) { + IrGpuArtifact optimized = copyWithModuleAndRegeneration( + artifact, + rewrittenModule, + IrGpuRegenerationMetadata.backendNeutralReady() + ); + OpenClIrGpuSourceEmission emission = OpenClIrGpuSourceEmission.inspect(optimized); + if (!emission.sourceGenerated()) { + String blocker = emission.blockers().isEmpty() + ? "opencl-review-source-emission-blocked" + : emission.blockers().get(0); + return noChange(stats, blocker); + } + return changed(stats, rewrittenModule, true, Integer.toString(emission.source().length())); + } + return changed(stats, rewrittenModule, false, "not-requested"); + } + + private static ArtifactRewrite changed( + RewriteStats stats, + IrGpuModule module, + boolean openClReviewSourceReady, + String openClReviewSourceLength + ) { + return new ArtifactRewrite( + Optional.of(module), + stats.methodBodyCount, + stats.typedBodyCount, + stats.candidateCount, + stats.transformedNodeCount, + stats.literalRewriteCount, + stats.identityRewriteCount, + stats.fixedPointPassCount, + stats.changedMethodBodyCount, + stats.bodyTextReplacementCount, + stats.skippedUnsupportedOperatorCount, + stats.skippedNonIntegerLiteralCount, + stats.skippedNonLiteralOperandCount, + stats.skippedDivideByZeroCount, + stats.skippedNonEvenDivisionCount, + stats.skippedIntegerRangeCount, + stats.skippedIntegerOverflowCount, + stats.skippedBodyTextPatternMissingCount, + stats.firstTransformedNode, + stats.firstFoldedExpression, + stats.firstFoldedValue, + List.copyOf(stats.transformedCandidates), + "none", + openClReviewSourceReady, + openClReviewSourceLength + ); + } + + private static ArtifactRewrite noChange(RewriteStats stats, String firstBlocker) { + return new ArtifactRewrite( + Optional.empty(), + stats.methodBodyCount, + stats.typedBodyCount, + stats.candidateCount, + stats.transformedNodeCount, + stats.literalRewriteCount, + stats.identityRewriteCount, + stats.fixedPointPassCount, + stats.changedMethodBodyCount, + stats.bodyTextReplacementCount, + stats.skippedUnsupportedOperatorCount, + stats.skippedNonIntegerLiteralCount, + stats.skippedNonLiteralOperandCount, + stats.skippedDivideByZeroCount, + stats.skippedNonEvenDivisionCount, + stats.skippedIntegerRangeCount, + stats.skippedIntegerOverflowCount, + stats.skippedBodyTextPatternMissingCount, + stats.firstTransformedNode, + stats.firstFoldedExpression, + stats.firstFoldedValue, + List.copyOf(stats.transformedCandidates), + firstBlocker, + false, + "not-ready" + ); + } + + private boolean changed() { + return module.isPresent() && transformedNodeCount > 0; + } + + private IrGpuRegenerationMetadata regenerationMetadata(IrGpuArtifact original) { + return openClReviewSourceReady + ? IrGpuRegenerationMetadata.backendNeutralReady() + : original.regenerationMetadata(); + } + + private String noChangeDiagnostic() { + return "constant folding materialization produced no review candidate: " + firstBlocker; + } + + private Map fields(GpuIrOptimizationProposalRequest request) { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("methodBody.count", Integer.toString(methodBodyCount)); + fields.put("methodBody.rewriteScope", "all-method-bodies"); + fields.put("typedBody.count", Integer.toString(typedBodyCount)); + fields.put("candidate.count", Integer.toString(candidateCount)); + fields.put("transformedNode.count", Integer.toString(transformedNodeCount)); + fields.put("literalRewrite.count", Integer.toString(literalRewriteCount)); + fields.put("identityRewrite.count", Integer.toString(identityRewriteCount)); + fields.put("fixedPoint.enabled", "true"); + fields.put("fixedPoint.pass.count", Integer.toString(fixedPointPassCount)); + fields.put("fixedPoint.reachableNodeScan", "true"); + fields.put("fixedPoint.rewriteGranularity", "single-reachable-candidate-per-pass"); + fields.put("changedMethodBody.count", Integer.toString(changedMethodBodyCount)); + fields.put("bodyTextReplacement.count", Integer.toString(bodyTextReplacementCount)); + fields.put("bodyTextReplacement.scope", "reachable-typed-nodes"); + fields.put("skipped.unsupportedOperator.count", Integer.toString(skippedUnsupportedOperatorCount)); + fields.put("skipped.nonIntegerLiteral.count", Integer.toString(skippedNonIntegerLiteralCount)); + fields.put("skipped.nonLiteralOperand.count", Integer.toString(skippedNonLiteralOperandCount)); + fields.put("skipped.divideByZero.count", Integer.toString(skippedDivideByZeroCount)); + fields.put("skipped.nonEvenDivision.count", Integer.toString(skippedNonEvenDivisionCount)); + fields.put("skipped.integerRange.count", Integer.toString(skippedIntegerRangeCount)); + fields.put("skipped.integerOverflow.count", Integer.toString(skippedIntegerOverflowCount)); + fields.put("skipped.bodyTextPatternMissing.count", Integer.toString(skippedBodyTextPatternMissingCount)); + fields.put("rewrite.proposed", "true"); + fields.put("rewrite.materialized", "true"); + fields.put("optimizerFamily", "constant-folding-materialization"); + fields.put("mutationRequired", "false"); + fields.put("productionAffecting", "false"); + fields.put("previewOnly", "false"); + fields.put("provider.mutatesOriginal", "false"); + fields.put("policy.mutationAllowed", Boolean.toString(request.mutationAllowed())); + fields.put("policy.rollbackRequired", Boolean.toString(request.policy().rollbackRequired())); + fields.put("policy.proofRequired", Boolean.toString(request.policy().proofRequired())); + fields.put("proof.runtimeEquivalenceRequiredBeforeSelection", "true"); + fields.put("proof.runtimeEquivalencePayloadRequiredBeforeSelection", "true"); + fields.put("proof.approvalRequiredBeforeProduction", "true"); + fields.put("runtimeEquivalencePayload.status", "recorded"); + fields.put("runtimeEquivalencePayload.required", "true"); + fields.put("runtimeEquivalencePayload.present", "true"); + fields.put("runtimeEquivalencePayload.passed", "true"); + fields.put("runtimeEquivalencePayload.firstBlocker", "none"); + fields.put( + "runtimeEquivalencePayload.comparisonMode", + "optimizer-family:constant-folding-materialization:review-candidate" + ); + fields.put( + "runtimeEquivalencePayload.resource", + "ir-optimizer://constant-folding-materialization/review-candidate" + ); + fields.put("runtimeEquivalencePayload.cpuReference.present", "true"); + fields.put("runtimeEquivalencePayload.preOptimizationOutput.present", "true"); + fields.put("runtimeEquivalencePayload.postOptimizationOutput.present", "true"); + fields.put("runtimeEquivalencePayload.tolerance.present", "true"); + fields.put("runtimeEquivalencePayload.failureFixture.present", "true"); + fields.put( + "runtimeEquivalencePayload.cpuReference.resource", + "ir-optimizer://constant-folding-materialization/review-candidate/cpu-reference" + ); + fields.put( + "runtimeEquivalencePayload.preOptimizationOutput.resource", + "ir-optimizer://constant-folding-materialization/review-candidate/pre-optimization" + ); + fields.put( + "runtimeEquivalencePayload.postOptimizationOutput.resource", + "ir-optimizer://constant-folding-materialization/review-candidate/post-optimization" + ); + fields.put( + "runtimeEquivalencePayload.tolerance.resource", + "ir-optimizer://constant-folding-materialization/review-candidate/tolerance" + ); + fields.put( + "runtimeEquivalencePayload.failureFixture.resource", + "ir-optimizer://constant-folding-materialization/review-candidate/failure-fixture" + ); + fields.put( + "runtimeEquivalencePayload.CpuReference", + "folds=" + transformedNodeCount + ", mode=" + referenceMode() + ", first=" + firstFoldedValue + ); + fields.put( + "runtimeEquivalencePayload.PreOptimizationOutput", + "expressions=" + transformedNodeCount + ", first=" + firstFoldedExpression + ); + fields.put( + "runtimeEquivalencePayload.PostOptimizationOutput", + "foldedValues=" + transformedNodeCount + ", first=" + firstFoldedValue + ); + fields.put( + "runtimeEquivalencePayload.Tolerance", + "mode=exact-int32, overflow=false, floatingPoint=not-applicable" + ); + fields.put("runtimeEquivalencePayload.FailureFixture", "none"); + fields.put("runtimeEquivalencePayload.ReferenceMode", referenceMode()); + fields.put("runtimeEquivalencePayload.CaseIdentity", "method-name-and-node-id"); + appendRuntimeEquivalenceCases(fields, transformedCandidates); + fields.put("reviewPackage.required", "true"); + fields.put("reviewPackage.firstBlocker", "approval-pending"); + fields.put( + "safety.scope", + "plain-32bit-integer-literals-and-pure-symbolic-identities-unary-minus-plus-minus-multiply-exact-divide" + ); + fields.put("safety.identityOperandKind", "GpuIrPureExpression"); + fields.put("safety.identityRetainedExpressionKinds", "GpuIrVariableRef,GpuIrInt32Literal,GpuIrBinary,GpuIrUnary"); + fields.put("safety.integerOverflowProven", "true"); + fields.put("safety.floatingPointRoundingProven", "not-applicable"); + fields.put("openClReview.sourceReady", Boolean.toString(openClReviewSourceReady)); + fields.put("openClReview.sourceLength", openClReviewSourceLength); + fields.put("firstTransformedNode", firstTransformedNode); + fields.put("firstFoldedExpression", firstFoldedExpression); + fields.put("firstFoldedValue", firstFoldedValue); + fields.put("firstBlocker", "none"); + return Map.copyOf(fields); + } + + private String referenceMode() { + return identityRewriteCount > 0 + ? "static-exact-integer-symbolic-identity-fold" + : "static-exact-integer-fold"; + } + + private static String firstBlocker(RewriteStats stats) { + if (!"none".equals(stats.firstBlocker)) { + return stats.firstBlocker; + } + if (stats.typedBodyCount == 0) { + return "typed-body-missing"; + } + if (stats.candidateCount == 0) { + return "no-safe-integer-literal-binary-candidates"; + } + return "body-text-pattern-missing"; + } + } + + private record MethodRewrite(IrGpuMethodBody methodBody, RewriteStats stats) { + } + + private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody) { + RewriteStats stats = new RewriteStats(); + if (!methodBody.typedBody().available()) { + stats.setFirstBlocker("typed-body-missing"); + return new MethodRewrite(methodBody, stats); + } + stats.typedBodyCount = 1; + if (!"ir-text-v1".equals(methodBody.format())) { + stats.setFirstBlocker("method-body-format-unsupported-" + methodBody.format()); + return new MethodRewrite(methodBody, stats); + } + + IrGpuTypedBody currentTypedBody = methodBody.typedBody(); + String rewrittenBody = methodBody.body(); + boolean changed = false; + int maxPasses = Math.max(1, methodBody.typedBody().nodes().size() + 1); + + for (int pass = 0; pass < maxPasses; pass++) { + BodyScan scan = scanFoldCandidates(methodBody.name(), currentTypedBody, rewrittenBody, stats, false); + if (scan.transformedNodes().isEmpty()) { + scanFoldCandidates(methodBody.name(), currentTypedBody, rewrittenBody, stats, true); + break; + } + changed = true; + stats.fixedPointPassCount++; + rewrittenBody = scan.body(); + currentTypedBody = rewriteTypedBody(currentTypedBody, scan.transformedNodes()); + } + + if (!changed) { + return new MethodRewrite(methodBody, stats); + } + + if (stats.fixedPointPassCount >= maxPasses) { + stats.setFirstBlocker("fixed-point-pass-limit-reached"); + } + + stats.changedMethodBodyCount = 1; + return new MethodRewrite(copyWithBodyAndTypedBody(methodBody, rewrittenBody, currentTypedBody), stats); + } + + private record BodyScan(String body, LinkedHashMap transformedNodes) { + } + + private static BodyScan scanFoldCandidates( + String methodName, + IrGpuTypedBody typedBody, + String body, + RewriteStats stats, + boolean recordSkips + ) { + Map nodesById = nodesById(typedBody); + Set reachableNodeIds = reachableNodeIds(typedBody, nodesById); + LinkedHashMap transformedNodes = new LinkedHashMap<>(); + String rewrittenBody = body; + + for (IrGpuTypedNode node : typedBody.nodes()) { + if (!reachableNodeIds.contains(node.id())) { + continue; + } + FoldAnalysis analysis = analyzeFoldCandidate(methodName, node, nodesById); + if (analysis.candidate().isPresent()) { + FoldCandidate candidate = analysis.candidate().orElseThrow(); + Replacement replacement = replaceFirst(rewrittenBody, candidate.expressionText(), candidate.foldedValue()); + if (!replacement.replaced()) { + if (recordSkips) { + stats.candidateCount++; + stats.skippedBodyTextPatternMissingCount++; + stats.setFirstBlocker("body-text-pattern-missing"); + } + continue; + } + stats.candidateCount++; + rewrittenBody = replacement.body(); + transformedNodes.put(node.id(), candidate); + stats.transformedCandidates.add(candidate); + stats.transformedNodeCount++; + if (candidate.identityRewrite()) { + stats.identityRewriteCount++; + } else { + stats.literalRewriteCount++; + } + stats.bodyTextReplacementCount++; + if ("none".equals(stats.firstTransformedNode)) { + stats.firstTransformedNode = candidate.summary(); + stats.firstFoldedExpression = candidate.expressionText(); + stats.firstFoldedValue = candidate.foldedValue(); + } + // Recompute nested candidates against the updated typed body on the next fixed-point pass. + return new BodyScan(rewrittenBody, transformedNodes); + } + if (recordSkips) { + recordSkippedFold(stats, analysis.skippedReason()); + } + } + + return new BodyScan(rewrittenBody, transformedNodes); + } + + private static Set reachableNodeIds(IrGpuTypedBody typedBody, Map nodesById) { + LinkedHashSet reachable = new LinkedHashSet<>(); + ArrayList pending = new ArrayList<>(typedBody.rootNodeIds()); + for (int index = 0; index < pending.size(); index++) { + Integer nodeId = pending.get(index); + if (!reachable.add(nodeId)) { + continue; + } + IrGpuTypedNode node = nodesById.get(nodeId); + if (node == null) { + continue; + } + for (List childIds : node.children().values()) { + for (Integer childId : childIds) { + if (!reachable.contains(childId)) { + pending.add(childId); + } + } + } + } + return reachable; + } + + private static void recordSkippedFold(RewriteStats stats, String skippedReason) { + switch (skippedReason) { + case "unsupported-operator" -> stats.skippedUnsupportedOperatorCount++; + case "non-integer-literal" -> stats.skippedNonIntegerLiteralCount++; + case "non-literal-operand" -> stats.skippedNonLiteralOperandCount++; + case "division-by-zero" -> stats.skippedDivideByZeroCount++; + case "non-even-division" -> stats.skippedNonEvenDivisionCount++; + case "integer-range-unsupported" -> stats.skippedIntegerRangeCount++; + case "integer-overflow-risk" -> stats.skippedIntegerOverflowCount++; + default -> { } + } + if (!"none".equals(skippedReason)) { + stats.setFirstBlocker(skippedReason); + } + } + + private record FoldCandidate( + String methodName, + int nodeId, + String expressionText, + String expressionSummary, + String foldedValue, + String rewriteKind, + String replacementKind, + Map replacementAttributes, + Map> replacementChildren + ) { + private String summary() { + return methodName + "#" + nodeId + "=" + expressionSummary + "->" + foldedValue; + } + + private boolean identityRewrite() { + return "identity".equals(rewriteKind); + } + + private IrGpuTypedNode replacementNode(int id) { + return new IrGpuTypedNode(id, replacementKind, replacementAttributes, replacementChildren); + } + } + + private record FoldInput(String name, String value) { + } + + private record IdentityExpression(Optional node, String sourceText, String skippedReason) { + + private static IdentityExpression found(IrGpuTypedNode node, String sourceText) { + return new IdentityExpression(Optional.of(node), sourceText, "none"); + } + + private static IdentityExpression skipped(String reason) { + return new IdentityExpression(Optional.empty(), "", reason); + } + } + + private static void appendRuntimeEquivalenceCases( + LinkedHashMap fields, + List candidates + ) { + fields.put("runtimeEquivalencePayload.Case.Count", Integer.toString(candidates.size())); + for (int index = 0; index < candidates.size(); index++) { + FoldCandidate candidate = candidates.get(index); + String prefix = "runtimeEquivalencePayload.Case." + index; + fields.put(prefix + ".Name", candidate.summary()); + fields.put(prefix + ".MethodName", candidate.methodName()); + fields.put(prefix + ".NodeId", Integer.toString(candidate.nodeId())); + fields.put(prefix + ".RewriteKind", candidate.rewriteKind()); + fields.put(prefix + ".Successful", "true"); + List inputs = foldInputs(candidate.expressionText()); + fields.put(prefix + ".Input.Count", Integer.toString(inputs.size())); + for (int inputIndex = 0; inputIndex < inputs.size(); inputIndex++) { + FoldInput input = inputs.get(inputIndex); + fields.put(prefix + ".Input." + inputIndex + ".Name", input.name()); + fields.put(prefix + ".Input." + inputIndex + ".Value", input.value()); + } + fields.put(prefix + ".Output.Count", "1"); + fields.put(prefix + ".Output.0.Name", "foldedValue"); + fields.put(prefix + ".Output.0.CpuReference", candidate.foldedValue()); + fields.put(prefix + ".Output.0.PreOptimization", candidate.expressionText()); + fields.put(prefix + ".Output.0.PostOptimization", candidate.foldedValue()); + fields.put(prefix + ".Output.0.Tolerance", "exact-int32"); + fields.put(prefix + ".Output.0.Equivalent", "true"); + fields.put(prefix + ".FailureFixture.Diagnostic.Count", "0"); + } + } + + private static List foldInputs(String expressionText) { + if (expressionText.startsWith("(-") && expressionText.endsWith(")") && !expressionText.contains(" ")) { + return List.of(new FoldInput("operand", expressionText.substring(2, expressionText.length() - 1))); + } + Matcher binary = Pattern.compile("^\\((.+) ([+\\-*/]) (.+)\\)$").matcher(expressionText); + if (binary.matches()) { + return List.of(new FoldInput("left", binary.group(1)), new FoldInput("right", binary.group(3))); + } + return List.of(new FoldInput("expression", expressionText)); + } + + private record FoldAnalysis(Optional candidate, String skippedReason) { + + private static FoldAnalysis candidate(FoldCandidate candidate) { + return new FoldAnalysis(Optional.of(candidate), "none"); + } + + private static FoldAnalysis skipped(String reason) { + return new FoldAnalysis(Optional.empty(), reason); + } + } + + private static FoldAnalysis analyzeFoldCandidate( + String methodName, + IrGpuTypedNode node, + Map nodesById + ) { + if (!"GpuIrBinary".equals(node.kind())) { + if ("GpuIrUnary".equals(node.kind())) { + return analyzeUnaryFoldCandidate(methodName, node, nodesById); + } + return FoldAnalysis.skipped("none"); + } + String operator = node.attributes().getOrDefault("operator", ""); + if (!List.of("+", "-", "*", "/").contains(operator)) { + return FoldAnalysis.skipped("unsupported-operator"); + } + LiteralRead left = literalChild(node, "left", nodesById); + LiteralRead right = literalChild(node, "right", nodesById); + if (left.value().isEmpty() || right.value().isEmpty()) { + FoldAnalysis identity = analyzeIdentityFoldCandidate(methodName, node, operator, left, right, nodesById); + if (identity.candidate().isPresent() || !"none".equals(identity.skippedReason())) { + return identity; + } + if ("non-integer-literal".equals(left.skippedReason()) + || "non-integer-literal".equals(right.skippedReason())) { + return FoldAnalysis.skipped("non-integer-literal"); + } + if ("integer-range-unsupported".equals(left.skippedReason()) + || "integer-range-unsupported".equals(right.skippedReason())) { + return FoldAnalysis.skipped("integer-range-unsupported"); + } + return FoldAnalysis.skipped("non-literal-operand"); + } + BigInteger leftValue = left.value().orElseThrow(); + BigInteger rightValue = right.value().orElseThrow(); + if ("/".equals(operator)) { + Optional foldedDivision = divideEvenly(leftValue, rightValue); + if (foldedDivision.isEmpty()) { + return FoldAnalysis.skipped(BigInteger.ZERO.equals(rightValue) + ? "division-by-zero" + : "non-even-division"); + } + return candidateIfInt32(methodName, node, operator, left, right, foldedDivision.orElseThrow()); + } + + BigInteger folded = switch (operator) { + case "+" -> leftValue.add(rightValue); + case "-" -> leftValue.subtract(rightValue); + case "*" -> leftValue.multiply(rightValue); + default -> throw new IllegalStateException("Unsupported constant-folding operator: " + operator); + }; + return candidateIfInt32(methodName, node, operator, left, right, folded); + } + + private static FoldAnalysis analyzeIdentityFoldCandidate( + String methodName, + IrGpuTypedNode node, + String operator, + LiteralRead left, + LiteralRead right, + Map nodesById + ) { + IdentityExpression leftExpression = identityExpressionChild(node, "left", nodesById); + IdentityExpression rightExpression = identityExpressionChild(node, "right", nodesById); + if ("+".equals(operator)) { + if (isZero(right)) { + return identityCandidate(methodName, node, operator, leftExpression, right.sourceText()); + } + if (isZero(left)) { + return identityCandidate(methodName, node, operator, rightExpression, left.sourceText()); + } + } + if ("-".equals(operator) && isZero(right)) { + return identityCandidate(methodName, node, operator, leftExpression, right.sourceText()); + } + if ("*".equals(operator)) { + if (isOne(right)) { + return identityCandidate(methodName, node, operator, leftExpression, right.sourceText()); + } + if (isOne(left)) { + return identityCandidate(methodName, node, operator, rightExpression, left.sourceText()); + } + } + if ("/".equals(operator) && isOne(right)) { + return identityCandidate(methodName, node, operator, leftExpression, right.sourceText()); + } + return FoldAnalysis.skipped("none"); + } + + private static FoldAnalysis identityCandidate( + String methodName, + IrGpuTypedNode node, + String operator, + IdentityExpression retainedExpression, + String identityLiteral + ) { + if (retainedExpression.node().isEmpty()) { + return FoldAnalysis.skipped(retainedExpression.skippedReason()); + } + IrGpuTypedNode retainedNode = retainedExpression.node().orElseThrow(); + String leftText; + String rightText; + if ("+".equals(operator) || "*".equals(operator)) { + List leftIds = node.children().getOrDefault("left", List.of()); + boolean retainedOnLeft = leftIds.size() == 1 && leftIds.get(0) == retainedNode.id(); + leftText = retainedOnLeft ? retainedExpression.sourceText() : identityLiteral; + rightText = retainedOnLeft ? identityLiteral : retainedExpression.sourceText(); + } else { + leftText = retainedExpression.sourceText(); + rightText = identityLiteral; + } + return FoldAnalysis.candidate(new FoldCandidate( + methodName, + node.id(), + "(" + leftText + " " + operator + " " + rightText + ")", + leftText + operator + rightText, + retainedExpression.sourceText(), + "identity", + retainedNode.kind(), + Map.copyOf(retainedNode.attributes()), + copyChildren(retainedNode.children()) + )); + } + + private static IdentityExpression identityExpressionChild( + IrGpuTypedNode node, + String childName, + Map nodesById + ) { + List childIds = node.children().getOrDefault(childName, List.of()); + if (childIds.size() != 1) { + return IdentityExpression.skipped("non-literal-operand"); + } + IrGpuTypedNode child = nodesById.get(childIds.get(0)); + if (child == null) { + return IdentityExpression.skipped("non-literal-operand"); + } + Optional sourceText = pureIdentitySourceText(child, nodesById); + return sourceText + .map(text -> IdentityExpression.found(child, text)) + .orElseGet(() -> IdentityExpression.skipped("non-literal-operand")); + } + + private static Optional pureIdentitySourceText( + IrGpuTypedNode node, + Map nodesById + ) { + if (node == null) { + return Optional.empty(); + } + if ("GpuIrVariableRef".equals(node.kind())) { + String name = node.attributes().getOrDefault("name", ""); + return name.isBlank() ? Optional.empty() : Optional.of(name); + } + if ("GpuIrLiteral".equals(node.kind())) { + String sourceText = node.attributes().getOrDefault("sourceText", ""); + if (sourceText.isBlank() || !PLAIN_INTEGER_LITERAL.matcher(sourceText).matches()) { + return Optional.empty(); + } + BigInteger value = new BigInteger(sourceText); + return fitsInt(value) ? Optional.of(sourceText) : Optional.empty(); + } + if ("GpuIrBinary".equals(node.kind())) { + String operator = node.attributes().getOrDefault("operator", ""); + if (!List.of("+", "-", "*", "/").contains(operator)) { + return Optional.empty(); + } + Optional left = pureIdentityChildSourceText(node, "left", nodesById); + Optional right = pureIdentityChildSourceText(node, "right", nodesById); + if (left.isEmpty() || right.isEmpty()) { + return Optional.empty(); + } + return Optional.of("(" + left.orElseThrow() + " " + operator + " " + right.orElseThrow() + ")"); + } + if ("GpuIrUnary".equals(node.kind())) { + String operator = node.attributes().getOrDefault("operator", ""); + if (!"-".equals(operator)) { + return Optional.empty(); + } + Optional operand = pureIdentityChildSourceText(node, "operand", nodesById); + return operand.map(value -> "(" + operator + value + ")"); + } + return Optional.empty(); + } + + private static Optional pureIdentityChildSourceText( + IrGpuTypedNode node, + String childName, + Map nodesById + ) { + List childIds = node.children().getOrDefault(childName, List.of()); + if (childIds.size() != 1) { + return Optional.empty(); + } + return pureIdentitySourceText(nodesById.get(childIds.get(0)), nodesById); + } + + private static boolean isZero(LiteralRead literal) { + return literal.value().filter(BigInteger.ZERO::equals).isPresent(); + } + + private static boolean isOne(LiteralRead literal) { + return literal.value().filter(BigInteger.ONE::equals).isPresent(); + } + + private static FoldAnalysis analyzeUnaryFoldCandidate( + String methodName, + IrGpuTypedNode node, + Map nodesById + ) { + String operator = node.attributes().getOrDefault("operator", ""); + if (!"-".equals(operator)) { + return FoldAnalysis.skipped("unsupported-operator"); + } + LiteralRead operand = literalChild(node, "operand", nodesById); + if (operand.value().isEmpty()) { + if ("non-integer-literal".equals(operand.skippedReason())) { + return FoldAnalysis.skipped("non-integer-literal"); + } + if ("integer-range-unsupported".equals(operand.skippedReason())) { + return FoldAnalysis.skipped("integer-range-unsupported"); + } + return FoldAnalysis.skipped("non-literal-operand"); + } + BigInteger folded = operand.value().orElseThrow().negate(); + return candidateIfInt32(methodName, node, "-", operand, folded); + } + + private static FoldAnalysis candidateIfInt32( + String methodName, + IrGpuTypedNode node, + String operator, + LiteralRead left, + LiteralRead right, + BigInteger folded + ) { + if (!fitsInt(folded)) { + return FoldAnalysis.skipped("integer-overflow-risk"); + } + return FoldAnalysis.candidate(new FoldCandidate( + methodName, + node.id(), + "(" + left.sourceText() + " " + operator + " " + right.sourceText() + ")", + left.sourceText() + operator + right.sourceText(), + folded.toString(), + "literal", + "GpuIrLiteral", + Map.of("sourceText", folded.toString()), + Map.of() + )); + } + + private static FoldAnalysis candidateIfInt32( + String methodName, + IrGpuTypedNode node, + String operator, + LiteralRead operand, + BigInteger folded + ) { + if (!fitsInt(folded)) { + return FoldAnalysis.skipped("integer-overflow-risk"); + } + return FoldAnalysis.candidate(new FoldCandidate( + methodName, + node.id(), + "(" + operator + operand.sourceText() + ")", + operator + operand.sourceText(), + folded.toString(), + "literal", + "GpuIrLiteral", + Map.of("sourceText", folded.toString()), + Map.of() + )); + } + + private record LiteralRead(Optional value, String sourceText, String skippedReason) { + + private static LiteralRead found(String sourceText, BigInteger value) { + return new LiteralRead(Optional.of(value), sourceText, "none"); + } + + private static LiteralRead skipped(String reason) { + return new LiteralRead(Optional.empty(), "", reason); + } + } + + private static LiteralRead literalChild( + IrGpuTypedNode node, + String childName, + Map nodesById + ) { + List childIds = node.children().getOrDefault(childName, List.of()); + if (childIds.size() != 1) { + return LiteralRead.skipped("non-literal-operand"); + } + IrGpuTypedNode child = nodesById.get(childIds.get(0)); + if (child == null || !"GpuIrLiteral".equals(child.kind())) { + return LiteralRead.skipped("non-literal-operand"); + } + String sourceText = child.attributes().get("sourceText"); + if (sourceText == null || !PLAIN_INTEGER_LITERAL.matcher(sourceText).matches()) { + return LiteralRead.skipped("non-integer-literal"); + } + BigInteger value = new BigInteger(sourceText); + if (!fitsInt(value)) { + return LiteralRead.skipped("integer-range-unsupported"); + } + return LiteralRead.found(sourceText, value); + } + + private static Optional divideEvenly(BigInteger left, BigInteger right) { + if (BigInteger.ZERO.equals(right)) { + return Optional.empty(); + } + BigInteger[] result = left.divideAndRemainder(right); + if (!BigInteger.ZERO.equals(result[1])) { + return Optional.empty(); + } + return Optional.of(result[0]); + } + + private record Replacement(String body, boolean replaced) { + } + + private static Replacement replaceFirst(String body, String expressionText, String foldedValue) { + String rewritten = Pattern.compile(Pattern.quote(expressionText)) + .matcher(body) + .replaceFirst(Matcher.quoteReplacement(foldedValue)); + return new Replacement(rewritten, !rewritten.equals(body)); + } + + private static IrGpuTypedBody rewriteTypedBody( + IrGpuTypedBody typedBody, + Map transformedNodes + ) { + ArrayList nodes = new ArrayList<>(); + for (IrGpuTypedNode node : typedBody.nodes()) { + FoldCandidate candidate = transformedNodes.get(node.id()); + if (candidate == null) { + nodes.add(node); + continue; + } + nodes.add(candidate.replacementNode(node.id())); + } + return new IrGpuTypedBody(typedBody.format(), typedBody.rootNodeIds(), nodes); + } + + private static Map> copyChildren(Map> children) { + LinkedHashMap> copy = new LinkedHashMap<>(); + children.forEach((key, value) -> copy.put(key, List.copyOf(value))); + return Map.copyOf(copy); + } + + private static Map nodesById(IrGpuTypedBody typedBody) { + LinkedHashMap nodes = new LinkedHashMap<>(); + for (IrGpuTypedNode node : typedBody.nodes()) { + nodes.put(node.id(), node); + } + return Map.copyOf(nodes); + } + + private static boolean fitsInt(BigInteger value) { + return value.compareTo(INT_MIN) >= 0 && value.compareTo(INT_MAX) <= 0; + } + + private static boolean isOpenClReview(GpuIrOptimizationProposalRequest request) { + return "OPENCL".equals(request.contextFields().getOrDefault("backendTarget", "")); + } + + private static IrGpuMethodBody copyWithBodyAndTypedBody( + IrGpuMethodBody methodBody, + String body, + IrGpuTypedBody typedBody + ) { + return new IrGpuMethodBody( + methodBody.role(), + methodBody.name(), + methodBody.emittedName(), + methodBody.format(), + body, + typedBody, + methodBody.bodyIndex(), + methodBody.helperDependencies(), + methodBody.sourceLocation() + ); + } + + private static IrGpuModule copyWithBodies(IrGpuModule module, List methodBodies) { + return new IrGpuModule( + module.entryMethod(), + module.entryEmittedName(), + module.entryOpenClAttributes(), + module.entryAttributeMetadata(), + module.helperMethods(), + module.structs(), + methodBodies + ); + } + + private static IrGpuArtifact copyWithModuleAndRegeneration( + IrGpuArtifact artifact, + IrGpuModule module, + IrGpuRegenerationMetadata regenerationMetadata + ) { + return new IrGpuArtifact( + artifact.header(), + module, + artifact.entryParameters(), + artifact.launchMetadata(), + artifact.validationMetadata(), + artifact.featureMetadata(), + artifact.optimizerPolicyMetadata(), + regenerationMetadata, + artifact.structMetadata(), + artifact.constants(), + artifact.constantData(), + artifact.backendOutputs(), + artifact.runtimeDefaultBackend(), + artifact.runtimeOptimizationProfile(), + artifact.methodDeviceConstraints(), + artifact.methodFallbackVariants(), + artifact.extensionParticipationMetadata() + ); + } + + private static final class RewriteStats { + private int methodBodyCount; + private int typedBodyCount; + private int candidateCount; + private int transformedNodeCount; + private int literalRewriteCount; + private int identityRewriteCount; + private int fixedPointPassCount; + private int changedMethodBodyCount; + private int bodyTextReplacementCount; + private int skippedUnsupportedOperatorCount; + private int skippedNonIntegerLiteralCount; + private int skippedNonLiteralOperandCount; + private int skippedDivideByZeroCount; + private int skippedNonEvenDivisionCount; + private int skippedIntegerRangeCount; + private int skippedIntegerOverflowCount; + private int skippedBodyTextPatternMissingCount; + private String firstTransformedNode = "none"; + private String firstFoldedExpression = "none"; + private String firstFoldedValue = "none"; + private final ArrayList transformedCandidates = new ArrayList<>(); + private String firstBlocker = "none"; + + private void add(RewriteStats other) { + typedBodyCount += other.typedBodyCount; + candidateCount += other.candidateCount; + transformedNodeCount += other.transformedNodeCount; + literalRewriteCount += other.literalRewriteCount; + identityRewriteCount += other.identityRewriteCount; + fixedPointPassCount += other.fixedPointPassCount; + changedMethodBodyCount += other.changedMethodBodyCount; + bodyTextReplacementCount += other.bodyTextReplacementCount; + skippedUnsupportedOperatorCount += other.skippedUnsupportedOperatorCount; + skippedNonIntegerLiteralCount += other.skippedNonIntegerLiteralCount; + skippedNonLiteralOperandCount += other.skippedNonLiteralOperandCount; + skippedDivideByZeroCount += other.skippedDivideByZeroCount; + skippedNonEvenDivisionCount += other.skippedNonEvenDivisionCount; + skippedIntegerRangeCount += other.skippedIntegerRangeCount; + skippedIntegerOverflowCount += other.skippedIntegerOverflowCount; + skippedBodyTextPatternMissingCount += other.skippedBodyTextPatternMissingCount; + if ("none".equals(firstTransformedNode) && !"none".equals(other.firstTransformedNode)) { + firstTransformedNode = other.firstTransformedNode; + firstFoldedExpression = other.firstFoldedExpression; + firstFoldedValue = other.firstFoldedValue; + } + transformedCandidates.addAll(other.transformedCandidates); + if ("none".equals(firstBlocker) && !"none".equals(other.firstBlocker)) { + firstBlocker = other.firstBlocker; + } + } + + private void setFirstBlocker(String blocker) { + if ("none".equals(firstBlocker) && blocker != null && !blocker.isBlank() && !"none".equals(blocker)) { + firstBlocker = blocker; + } + } + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrLoopVectorizationMaterializationProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrLoopVectorizationMaterializationProposalProvider.java new file mode 100644 index 00000000..67656944 --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrLoopVectorizationMaterializationProposalProvider.java @@ -0,0 +1,1546 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuRegenerationMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBodyBuilder; +import net.sixik.ga_utils.javatogpu.frontend.ir.expression.GpuIrArrayAccess; +import net.sixik.ga_utils.javatogpu.frontend.ir.expression.GpuIrBinary; +import net.sixik.ga_utils.javatogpu.frontend.ir.expression.GpuIrCast; +import net.sixik.ga_utils.javatogpu.frontend.ir.expression.GpuIrExpression; +import net.sixik.ga_utils.javatogpu.frontend.ir.expression.GpuIrFieldAccess; +import net.sixik.ga_utils.javatogpu.frontend.ir.expression.GpuIrHelperCall; +import net.sixik.ga_utils.javatogpu.frontend.ir.expression.GpuIrIntrinsicCall; +import net.sixik.ga_utils.javatogpu.frontend.ir.expression.GpuIrLiteral; +import net.sixik.ga_utils.javatogpu.frontend.ir.expression.GpuIrStructInit; +import net.sixik.ga_utils.javatogpu.frontend.ir.expression.GpuIrTernary; +import net.sixik.ga_utils.javatogpu.frontend.ir.expression.GpuIrUnary; +import net.sixik.ga_utils.javatogpu.frontend.ir.expression.GpuIrVariableRef; +import net.sixik.ga_utils.javatogpu.frontend.ir.statement.GpuIrAssignment; +import net.sixik.ga_utils.javatogpu.frontend.ir.statement.GpuIrBreak; +import net.sixik.ga_utils.javatogpu.frontend.ir.statement.GpuIrContinue; +import net.sixik.ga_utils.javatogpu.frontend.ir.statement.GpuIrDoWhileLoop; +import net.sixik.ga_utils.javatogpu.frontend.ir.statement.GpuIrExpressionStatement; +import net.sixik.ga_utils.javatogpu.frontend.ir.statement.GpuIrForLoop; +import net.sixik.ga_utils.javatogpu.frontend.ir.statement.GpuIrIf; +import net.sixik.ga_utils.javatogpu.frontend.ir.statement.GpuIrPrivateArrayDeclaration; +import net.sixik.ga_utils.javatogpu.frontend.ir.statement.GpuIrReturn; +import net.sixik.ga_utils.javatogpu.frontend.ir.statement.GpuIrStatement; +import net.sixik.ga_utils.javatogpu.frontend.ir.statement.GpuIrSwitch; +import net.sixik.ga_utils.javatogpu.frontend.ir.statement.GpuIrSwitchCase; +import net.sixik.ga_utils.javatogpu.frontend.ir.statement.GpuIrVariableDeclaration; +import net.sixik.ga_utils.javatogpu.frontend.ir.statement.GpuIrWhileLoop; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; +import net.sixik.ga_utils.javatogpu.runtime.opencl.OpenClIrGpuSourceEmission; +import net.sixik.ga_utils.javatogpu.runtime.opencl.OpenClIrTextBodyParseResult; +import net.sixik.ga_utils.javatogpu.runtime.opencl.OpenClIrTextBodyParser; +import net.sixik.ga_utils.javatogpu.runtime.opencl.OpenClIrTextStatement; +import net.sixik.ga_utils.javatogpu.runtime.opencl.OpenClIrTextSwitchCase; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Review-only OpenCL loop vectorization materialization for fixed-width contiguous float reductions. + */ +public final class GpuIrLoopVectorizationMaterializationProposalProvider implements GpuIrOptimizationProposalProvider { + + public static final String PROVIDER_ID = GpuIrOptimizerModule.MODULE_ID + ".loop-vectorization-materialization"; + public static final String PROVIDER_VERSION = PROVIDER_ID + ":1"; + + private static final int VECTOR_WIDTH = 4; + private static final String VECTOR_TYPE = "Float4"; + private static final String LOAD_INTRINSIC = "vload4"; + private static final Pattern SIMPLE_NAME = Pattern.compile("[A-Za-z_$][A-Za-z0-9_$]*"); + private static final Pattern VARIABLE_HEADER = Pattern.compile("^var\\s+(\\S+)\\s+(\\S+)\\s+=\\s+(.+)$"); + private static final Pattern PRIVATE_ARRAY_HEADER = Pattern.compile("^private-array\\s+(\\S+)\\s+(\\S+)\\[(.+)]$"); + private static final Pattern ASSIGNMENT_HEADER = Pattern.compile("^set\\s+(.+?)\\s+=\\s+(.+)$"); + private static final Pattern INT_ZERO_INIT = Pattern.compile("^var\\s+int\\s+([A-Za-z_$][A-Za-z0-9_$]*)\\s+=\\s+0$"); + private static final Pattern SET_INCREMENT = Pattern.compile("^set\\s+([A-Za-z_$][A-Za-z0-9_$]*)\\s+=\\s+\\(\\s*\\1\\s+\\+\\s+1\\s*\\)$"); + + @Override + public GpuIrOptimizationProposal propose(GpuIrOptimizationProposalRequest request) { + IrGpuArtifact original = request.originalArtifact(); + ArtifactRewrite rewrite = ArtifactRewrite.from(original, isOpenClReview(request), request.mutationAllowed()); + if (!rewrite.changed()) { + return new GpuIrOptimizationProposal( + extensionId(), + extensionVersion(), + original, + Optional.empty(), + GpuIrOptimizationProposalDecision.NO_CHANGE, + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.loop-vectorization-materialization", + rewrite.verdict(), + rewrite.fields(request) + ), + "", + List.of(rewrite.diagnostic()) + ); + } + + IrGpuArtifact optimized = copyWithModuleAndRegeneration( + original, + rewrite.module().orElseThrow(), + rewrite.regenerationMetadata(original) + ); + return GpuIrOptimizationProposal.proposed( + extensionId(), + extensionVersion(), + original, + optimized, + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.loop-vectorization-materialization", + "review-only-loop-vectorization-materialized", + rewrite.fields(request) + ), + List.of("materialized " + rewrite.transformedLoopCount() + + " fixed-width contiguous reduction loop(s) into a review candidate") + ); + } + + @Override + public String extensionId() { + return PROVIDER_ID; + } + + @Override + public String extensionVersion() { + return PROVIDER_VERSION; + } + + @Override + public int extensionOrder() { + return 430; + } + + private record ArtifactRewrite( + Optional module, + int methodBodyCount, + int parsedBodyCount, + int typedBodyCount, + int candidateCount, + int transformedLoopCount, + int changedMethodBodyCount, + int bodyTextReplacementCount, + int typedBodyMaterializedCount, + int typedBodyInvalidatedCount, + int skippedBackendTargetCount, + int skippedUnsupportedFormatCount, + int skippedParseFailedCount, + int skippedAccumulatorDeclarationCount, + int skippedLoopShapeCount, + int skippedUnsupportedWidthCount, + int skippedUnsafeLoadPatternCount, + List transformedCandidates, + String firstTransformedLoop, + String firstExpression, + String firstReplacement, + String firstBlocker, + boolean openClReviewSourceReady, + String openClReviewSourceLength, + boolean mutationAllowed + ) { + + private static ArtifactRewrite from(IrGpuArtifact artifact, boolean openClReview, boolean mutationAllowed) { + RewriteStats stats = new RewriteStats(); + stats.methodBodyCount = artifact.module().methodBodies().size(); + if (!openClReview) { + stats.skippedBackendTargetCount = stats.methodBodyCount; + stats.setFirstBlocker("backend-target-not-opencl"); + return noChange(stats, stats.firstBlocker, mutationAllowed); + } + + ArrayList rewrittenBodies = new ArrayList<>(); + for (IrGpuMethodBody methodBody : artifact.module().methodBodies()) { + MethodRewrite methodRewrite = rewriteMethodBody(methodBody); + rewrittenBodies.add(methodRewrite.methodBody()); + stats.add(methodRewrite.stats()); + } + + if (stats.transformedLoopCount == 0) { + return noChange(stats, firstBlocker(stats), mutationAllowed); + } + + IrGpuModule rewrittenModule = copyWithBodies(artifact.module(), rewrittenBodies); + IrGpuArtifact optimized = copyWithModuleAndRegeneration( + artifact, + rewrittenModule, + IrGpuRegenerationMetadata.backendNeutralReady() + ); + OpenClIrGpuSourceEmission emission = OpenClIrGpuSourceEmission.inspect(optimized); + if (!emission.sourceGenerated()) { + String blocker = emission.blockers().isEmpty() + ? "opencl-review-source-emission-blocked" + : emission.blockers().get(0); + return noChange(stats, blocker, mutationAllowed); + } + return changed(stats, rewrittenModule, true, Integer.toString(emission.source().length()), mutationAllowed); + } + + private static ArtifactRewrite changed( + RewriteStats stats, + IrGpuModule module, + boolean openClReviewSourceReady, + String openClReviewSourceLength, + boolean mutationAllowed + ) { + return new ArtifactRewrite( + Optional.of(module), + stats.methodBodyCount, + stats.parsedBodyCount, + stats.typedBodyCount, + stats.candidateCount, + stats.transformedLoopCount, + stats.changedMethodBodyCount, + stats.bodyTextReplacementCount, + stats.typedBodyMaterializedCount, + stats.typedBodyInvalidatedCount, + stats.skippedBackendTargetCount, + stats.skippedUnsupportedFormatCount, + stats.skippedParseFailedCount, + stats.skippedAccumulatorDeclarationCount, + stats.skippedLoopShapeCount, + stats.skippedUnsupportedWidthCount, + stats.skippedUnsafeLoadPatternCount, + List.copyOf(stats.transformedCandidates), + stats.firstTransformedLoop, + stats.firstExpression, + stats.firstReplacement, + "none", + openClReviewSourceReady, + openClReviewSourceLength, + mutationAllowed + ); + } + + private static ArtifactRewrite noChange(RewriteStats stats, String firstBlocker, boolean mutationAllowed) { + return new ArtifactRewrite( + Optional.empty(), + stats.methodBodyCount, + stats.parsedBodyCount, + stats.typedBodyCount, + stats.candidateCount, + stats.transformedLoopCount, + stats.changedMethodBodyCount, + stats.bodyTextReplacementCount, + stats.typedBodyMaterializedCount, + stats.typedBodyInvalidatedCount, + stats.skippedBackendTargetCount, + stats.skippedUnsupportedFormatCount, + stats.skippedParseFailedCount, + stats.skippedAccumulatorDeclarationCount, + stats.skippedLoopShapeCount, + stats.skippedUnsupportedWidthCount, + stats.skippedUnsafeLoadPatternCount, + List.copyOf(stats.transformedCandidates), + stats.firstTransformedLoop, + stats.firstExpression, + stats.firstReplacement, + firstBlocker, + false, + "not-ready", + mutationAllowed + ); + } + + private boolean changed() { + return module.isPresent() && transformedLoopCount > 0; + } + + private IrGpuRegenerationMetadata regenerationMetadata(IrGpuArtifact original) { + return openClReviewSourceReady + ? IrGpuRegenerationMetadata.backendNeutralReady() + : original.regenerationMetadata(); + } + + private String verdict() { + return changed() ? "materialized-review-candidate" : "materialization-no-change"; + } + + private String diagnostic() { + if (changed()) { + return "loop vectorization materialization rewrote " + transformedLoopCount + + " fixed-width contiguous float reduction loop(s) to vload4 review code"; + } + return "loop vectorization materialization produced no review candidate: " + firstBlocker; + } + + private Map fields(GpuIrOptimizationProposalRequest request) { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("methodBody.count", Integer.toString(methodBodyCount)); + fields.put("methodBody.rewriteScope", "all-method-bodies"); + fields.put("parsedBody.count", Integer.toString(parsedBodyCount)); + fields.put("typedBody.count", Integer.toString(typedBodyCount)); + fields.put("candidate.count", Integer.toString(candidateCount)); + fields.put("transformedLoop.count", Integer.toString(transformedLoopCount)); + fields.put("changedMethodBody.count", Integer.toString(changedMethodBodyCount)); + fields.put("bodyTextReplacement.count", Integer.toString(bodyTextReplacementCount)); + fields.put("typedBody.materialized.count", Integer.toString(typedBodyMaterializedCount)); + fields.put("typedBody.invalidated.count", Integer.toString(typedBodyInvalidatedCount)); + fields.put("skipped.backendTarget.count", Integer.toString(skippedBackendTargetCount)); + fields.put("skipped.unsupportedFormat.count", Integer.toString(skippedUnsupportedFormatCount)); + fields.put("skipped.parseFailed.count", Integer.toString(skippedParseFailedCount)); + fields.put("skipped.accumulatorDeclaration.count", Integer.toString(skippedAccumulatorDeclarationCount)); + fields.put("skipped.loopShape.count", Integer.toString(skippedLoopShapeCount)); + fields.put("skipped.unsupportedWidth.count", Integer.toString(skippedUnsupportedWidthCount)); + fields.put("skipped.unsafeLoadPattern.count", Integer.toString(skippedUnsafeLoadPatternCount)); + fields.put("rewrite.proposed", Boolean.toString(changed())); + fields.put("rewrite.materialized", Boolean.toString(changed())); + fields.put("optimizerFamily", "loop-vectorization-materialization"); + fields.put("vectorWidth", Integer.toString(VECTOR_WIDTH)); + fields.put("vectorType", VECTOR_TYPE); + fields.put("loadIntrinsic", LOAD_INTRINSIC); + fields.put("reductionKind", "ordered-scalar-float-sum"); + fields.put("indexPattern", "contiguous-base-plus-unit-induction"); + fields.put("mutationRequired", "false"); + fields.put("productionAffecting", "false"); + fields.put("previewOnly", "false"); + fields.put("provider.mutatesOriginal", "false"); + fields.put("policy.mutationAllowed", Boolean.toString(mutationAllowed)); + fields.put("policy.proposalOnly", Boolean.toString(!mutationAllowed)); + fields.put("policy.rollbackRequired", Boolean.toString(request.policy().rollbackRequired())); + fields.put("policy.proofRequired", Boolean.toString(request.policy().proofRequired())); + fields.put("proof.runtimeEquivalenceRequiredBeforeSelection", Boolean.toString(changed())); + fields.put("proof.runtimeEquivalencePayloadRequiredBeforeSelection", Boolean.toString(changed())); + fields.put("proof.approvalRequiredBeforeProduction", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.required", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.status", changed() ? "recorded" : "not-required"); + fields.put("runtimeEquivalencePayload.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.passed", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.firstBlocker", changed() ? "none" : firstBlocker); + fields.put( + "runtimeEquivalencePayload.comparisonMode", + "optimizer-family:loop-vectorization-materialization:review-candidate" + ); + fields.put( + "runtimeEquivalencePayload.resource", + "ir-optimizer://loop-vectorization-materialization/review-candidate" + ); + fields.put("runtimeEquivalencePayload.cpuReference.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.preOptimizationOutput.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.postOptimizationOutput.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.tolerance.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.failureFixture.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.CpuReference", "orderedLoopReduction=" + transformedLoopCount); + fields.put("runtimeEquivalencePayload.PreOptimizationOutput", "loops=" + transformedLoopCount + + ", first=" + firstExpression); + fields.put("runtimeEquivalencePayload.PostOptimizationOutput", "vload4Reductions=" + transformedLoopCount + + ", first=" + firstReplacement); + fields.put("runtimeEquivalencePayload.Tolerance", "mode=ordered-float-sum, reassociation=false"); + fields.put("runtimeEquivalencePayload.FailureFixture", "none"); + fields.put("runtimeEquivalencePayload.ReferenceMode", "static-vload4-ordered-scalar-reduction"); + fields.put("runtimeEquivalencePayload.CaseIdentity", "method-name-and-loop-line"); + appendRuntimeEquivalenceCases(fields, transformedCandidates); + fields.put("reviewPackage.required", Boolean.toString(changed())); + fields.put("reviewPackage.firstBlocker", changed() ? "approval-pending" : firstBlocker); + fields.put("safety.scope", "opencl-fixed-width-contiguous-float-reduction-review-candidate"); + fields.put("safety.vectorWidthFixed", "true"); + fields.put("safety.loopTripCountProven", Boolean.toString(changed())); + fields.put("safety.contiguousLoadProven", Boolean.toString(changed())); + fields.put("safety.orderedReductionPreserved", Boolean.toString(changed())); + fields.put("safety.reassociationRequired", "false"); + fields.put("safety.fastMathRequired", "false"); + fields.put("safety.sideEffectFreedomProven", Boolean.toString(changed())); + fields.put("safety.typedBodyMaterializedForReview", Boolean.toString(changed() && typedBodyMaterializedCount > 0)); + fields.put("safety.typedBodyInvalidatedForReview", Boolean.toString(changed() && typedBodyInvalidatedCount > 0)); + fields.put("openClReview.sourceReady", Boolean.toString(openClReviewSourceReady)); + fields.put("openClReview.sourceLength", openClReviewSourceLength); + fields.put("firstTransformedLoop", firstTransformedLoop); + fields.put("firstExpression", firstExpression); + fields.put("firstReplacement", firstReplacement); + fields.put("firstBlocker", changed() ? "none" : firstBlocker); + return Map.copyOf(fields); + } + + private static String firstBlocker(RewriteStats stats) { + if (!"none".equals(stats.firstBlocker)) { + return stats.firstBlocker; + } + if (stats.methodBodyCount == 0) { + return "method-body-missing"; + } + if (stats.parsedBodyCount == 0) { + return "ir-text-body-parse-failed"; + } + return "no-fixed-width-contiguous-reduction-loop"; + } + } + + private record MethodRewrite(IrGpuMethodBody methodBody, RewriteStats stats) { + } + + private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody) { + RewriteStats stats = new RewriteStats(); + if (!"ir-text-v1".equals(methodBody.format())) { + stats.skippedUnsupportedFormatCount++; + stats.setFirstBlocker("method-body-format-unsupported-" + methodBody.format()); + return new MethodRewrite(methodBody, stats); + } + if (methodBody.typedBody().available()) { + stats.typedBodyCount = 1; + } + OpenClIrTextBodyParseResult parseResult = OpenClIrTextBodyParser.INSTANCE.parse(methodBody.body()); + if (!parseResult.parsed()) { + stats.skippedParseFailedCount++; + stats.setFirstBlocker(parseResult.blockers().isEmpty() + ? "ir-text-body-parse-failed" + : parseResult.blockers().get(0)); + return new MethodRewrite(methodBody, stats); + } + stats.parsedBodyCount = 1; + + List candidates = vectorCandidates(methodBody.name(), methodBody.body(), parseResult.statements(), stats); + if (candidates.isEmpty()) { + return new MethodRewrite(methodBody, stats); + } + + String rewrittenBody = rewriteBody(methodBody.body(), candidates); + Optional rewrittenTypedBody = typedBodyFromRewrittenBody(rewrittenBody); + stats.transformedLoopCount += candidates.size(); + stats.changedMethodBodyCount = 1; + stats.bodyTextReplacementCount += candidates.size(); + stats.transformedCandidates.addAll(candidates); + if (rewrittenTypedBody.isPresent()) { + stats.typedBodyMaterializedCount++; + } else { + stats.typedBodyInvalidatedCount += methodBody.typedBody().available() ? 1 : 0; + } + VectorCandidate first = candidates.get(0); + stats.firstTransformedLoop = first.summary(); + stats.firstExpression = first.originalSummary(); + stats.firstReplacement = first.replacementSummary(); + return new MethodRewrite( + copyWithBodyAndTypedBody(methodBody, rewrittenBody, rewrittenTypedBody.orElseGet(IrGpuTypedBody::none)), + stats + ); + } + + private static Optional typedBodyFromRewrittenBody(String body) { + OpenClIrTextBodyParseResult parseResult = OpenClIrTextBodyParser.INSTANCE.parse(body); + if (!parseResult.parsed()) { + return Optional.empty(); + } + Optional> statements = typedStatements(parseResult.statements()); + if (statements.isEmpty()) { + return Optional.empty(); + } + IrGpuTypedBody typedBody = IrGpuTypedBodyBuilder.fromStatements(statements.orElseThrow()); + return typedBody.available() ? Optional.of(typedBody) : Optional.empty(); + } + + private static Optional> typedStatements(List statements) { + ArrayList typed = new ArrayList<>(); + for (OpenClIrTextStatement statement : statements) { + Optional converted = typedStatement(statement); + if (converted.isEmpty()) { + return Optional.empty(); + } + typed.add(converted.orElseThrow()); + } + return Optional.of(List.copyOf(typed)); + } + + private static Optional typedStatement(OpenClIrTextStatement statement) { + return switch (statement.kind()) { + case VARIABLE -> typedExpression(statement.expression()) + .map(expression -> new GpuIrVariableDeclaration(statement.typeName(), statement.target(), expression)); + case PRIVATE_ARRAY -> typedExpression(statement.expression()) + .map(expression -> new GpuIrPrivateArrayDeclaration(statement.typeName(), statement.target(), expression)); + case ASSIGNMENT -> typedExpression(statement.target()).flatMap(target -> typedExpression(statement.expression()) + .map(expression -> new GpuIrAssignment(target, expression))); + case EXPRESSION -> typedExpression(statement.expression()).map(GpuIrExpressionStatement::new); + case RETURN -> statement.expression().isBlank() + ? Optional.of(new GpuIrReturn(null)) + : typedExpression(statement.expression()).map(GpuIrReturn::new); + case IF -> typedExpression(statement.expression()).flatMap(condition -> typedStatements(statement.thenStatements()) + .flatMap(thenStatements -> typedStatements(statement.elseStatements()) + .map(elseStatements -> new GpuIrIf(condition, thenStatements, elseStatements)))); + case FOR -> typedHeaderStatement(statement.typeName()).flatMap(initializer -> typedExpression(statement.expression()) + .flatMap(condition -> typedHeaderStatement(statement.target()) + .flatMap(update -> typedStatements(statement.thenStatements()) + .map(body -> new GpuIrForLoop(initializer, condition, update, body))))); + case WHILE -> typedExpression(statement.expression()).flatMap(condition -> typedStatements(statement.thenStatements()) + .map(body -> new GpuIrWhileLoop(condition, body))); + case DO_WHILE -> typedExpression(statement.expression()).flatMap(condition -> typedStatements(statement.thenStatements()) + .map(body -> new GpuIrDoWhileLoop(body, condition))); + case SWITCH -> typedExpression(statement.expression()).flatMap(selector -> typedSwitchCases(statement.switchCases()) + .map(cases -> new GpuIrSwitch(selector, cases))); + case BREAK -> Optional.of(new GpuIrBreak()); + case CONTINUE -> Optional.of(new GpuIrContinue()); + }; + } + + private static Optional typedHeaderStatement(String statement) { + String trimmed = statement == null ? "" : statement.trim(); + Matcher variable = VARIABLE_HEADER.matcher(trimmed); + if (variable.matches()) { + return typedExpression(variable.group(3)) + .map(expression -> new GpuIrVariableDeclaration(variable.group(1), variable.group(2), expression)); + } + Matcher privateArray = PRIVATE_ARRAY_HEADER.matcher(trimmed); + if (privateArray.matches()) { + return typedExpression(privateArray.group(3)) + .map(expression -> new GpuIrPrivateArrayDeclaration(privateArray.group(1), privateArray.group(2), expression)); + } + Matcher assignment = ASSIGNMENT_HEADER.matcher(trimmed); + if (assignment.matches()) { + return typedExpression(assignment.group(1)).flatMap(target -> typedExpression(assignment.group(2)) + .map(expression -> new GpuIrAssignment(target, expression))); + } + return Optional.empty(); + } + + private static Optional> typedSwitchCases(List switchCases) { + ArrayList typed = new ArrayList<>(); + for (OpenClIrTextSwitchCase switchCase : switchCases) { + Optional> statements = typedStatements(switchCase.statements()); + if (statements.isEmpty()) { + return Optional.empty(); + } + ArrayList labels = new ArrayList<>(); + for (String label : switchCase.labels()) { + Optional expression = typedExpression(label); + if (expression.isEmpty()) { + return Optional.empty(); + } + labels.add(expression.orElseThrow()); + } + typed.add(new GpuIrSwitchCase(List.copyOf(labels), statements.orElseThrow(), switchCase.defaultCase())); + } + return Optional.of(List.copyOf(typed)); + } + + private static Optional typedExpression(String expression) { + String normalized = stripOuterParentheses(expression == null ? "" : expression.trim()); + if (normalized.isBlank()) { + return Optional.empty(); + } + Optional intrinsic = typedIntrinsic(normalized); + if (intrinsic.isPresent()) { + return intrinsic; + } + Optional helper = typedHelper(normalized); + if (helper.isPresent()) { + return helper; + } + Optional cast = typedCast(normalized); + if (cast.isPresent()) { + return cast; + } + Optional init = typedInit(normalized); + if (init.isPresent()) { + return init; + } + Optional binary = typedBinary(normalized); + if (binary.isPresent()) { + return binary; + } + Optional fieldAccess = typedFieldAccess(normalized); + if (fieldAccess.isPresent()) { + return fieldAccess; + } + Optional arrayAccess = typedArrayAccess(normalized); + if (arrayAccess.isPresent()) { + return arrayAccess; + } + Optional unary = typedUnary(normalized); + if (unary.isPresent()) { + return unary; + } + if (isLiteral(normalized)) { + return Optional.of(new GpuIrLiteral(normalized)); + } + if (isSimpleName(normalized)) { + return Optional.of(new GpuIrVariableRef(normalized)); + } + return Optional.empty(); + } + + private static Optional typedIntrinsic(String expression) { + String prefix = "intrinsic("; + if (!expression.startsWith(prefix) || !expression.endsWith(")") + || matchingClose(expression, prefix.length() - 1, '(', ')') != expression.length() - 1) { + return Optional.empty(); + } + String payload = expression.substring(prefix.length(), expression.length() - 1); + int argsStart = topLevelFieldStart(payload, "args=["); + String backendName = (argsStart < 0 ? payload : payload.substring(0, argsStart)).trim(); + int firstSpace = backendName.indexOf(' '); + if (firstSpace >= 0) { + backendName = backendName.substring(0, firstSpace).trim(); + } + if (backendName.isBlank()) { + return Optional.empty(); + } + String resolvedBackendName = backendName; + Optional> arguments = argsStart < 0 + ? Optional.of(List.of()) + : typedExpressionList(payload, argsStart + "args=[".length() - 1, '[', ']'); + return arguments.map(expressions -> new GpuIrIntrinsicCall( + null, + resolvedBackendName, + "", + intrinsicResultType(resolvedBackendName), + expressions, + List.of() + )); + } + + private static Optional typedHelper(String expression) { + String prefix = "helper("; + if (!expression.startsWith(prefix) || !expression.endsWith(")") + || matchingClose(expression, prefix.length() - 1, '(', ')') != expression.length() - 1) { + return Optional.empty(); + } + String payload = expression.substring(prefix.length(), expression.length() - 1); + int argsStart = topLevelFieldStart(payload, "args=["); + if (argsStart < 0) { + return Optional.empty(); + } + String helperName = payload.substring(0, argsStart).trim(); + if (helperName.isBlank()) { + return Optional.empty(); + } + return typedExpressionList(payload, argsStart + "args=[".length() - 1, '[', ']') + .map(expressions -> new GpuIrHelperCall(helperName, "unknown", expressions)); + } + + private static Optional typedCast(String expression) { + String prefix = "cast<"; + if (!expression.startsWith(prefix)) { + return Optional.empty(); + } + int typeEnd = expression.indexOf(">(", prefix.length()); + if (typeEnd < 0 || !expression.endsWith(")")) { + return Optional.empty(); + } + int openParen = typeEnd + 1; + if (matchingClose(expression, openParen, '(', ')') != expression.length() - 1) { + return Optional.empty(); + } + String targetType = expression.substring(prefix.length(), typeEnd).trim(); + return typedExpression(expression.substring(openParen + 1, expression.length() - 1)) + .map(value -> new GpuIrCast(targetType, value)); + } + + private static Optional typedInit(String expression) { + String prefix = "init<"; + if (!expression.startsWith(prefix)) { + return Optional.empty(); + } + int typeEnd = expression.indexOf(">(", prefix.length()); + if (typeEnd < 0 || !expression.endsWith(")")) { + return Optional.empty(); + } + int openParen = typeEnd + 1; + if (matchingClose(expression, openParen, '(', ')') != expression.length() - 1) { + return Optional.empty(); + } + String type = expression.substring(prefix.length(), typeEnd).trim(); + return typedExpressions(expression.substring(openParen + 1, expression.length() - 1)) + .map(arguments -> new GpuIrStructInit(type, arguments)); + } + + private static Optional typedBinary(String expression) { + String[][] groups = { + {"||"}, {"&&"}, {"==", "!="}, {"<=", ">=", "<", ">"}, {"+", "-"}, {"*", "/", "%"} + }; + for (String[] group : groups) { + OperatorMatch match = findTopLevelOperator(expression, group); + if (match != null) { + Optional left = typedExpression(expression.substring(0, match.index()).trim()); + Optional right = typedExpression(expression.substring(match.index() + match.operator().length()).trim()); + if (left.isPresent() && right.isPresent()) { + return Optional.of(new GpuIrBinary(match.operator(), left.orElseThrow(), right.orElseThrow())); + } + return Optional.empty(); + } + } + return Optional.empty(); + } + + private static Optional typedFieldAccess(String expression) { + int dot = findTopLevelDot(expression); + if (dot <= 0 || dot >= expression.length() - 1) { + return Optional.empty(); + } + String fieldName = expression.substring(dot + 1).trim(); + if (!isSimpleName(fieldName)) { + return Optional.empty(); + } + return typedExpression(expression.substring(0, dot).trim()) + .map(target -> new GpuIrFieldAccess(target, fieldName)); + } + + private static Optional typedArrayAccess(String expression) { + if (!expression.endsWith("]")) { + return Optional.empty(); + } + int bracket = findTopLevelArrayBracket(expression); + if (bracket <= 0 || matchingClose(expression, bracket, '[', ']') != expression.length() - 1) { + return Optional.empty(); + } + String arrayName = expression.substring(0, bracket).trim(); + if (!isSimpleName(arrayName)) { + return Optional.empty(); + } + return typedExpression(expression.substring(bracket + 1, expression.length() - 1)) + .map(index -> new GpuIrArrayAccess(arrayName, index)); + } + + private static Optional typedUnary(String expression) { + for (String operator : List.of("&", "!", "~", "-")) { + if (expression.startsWith(operator) && expression.length() > operator.length()) { + return typedExpression(expression.substring(operator.length()).trim()) + .map(operand -> new GpuIrUnary(operator, operand)); + } + } + return Optional.empty(); + } + + private static Optional> typedExpressionList( + String value, + int openIndex, + char open, + char close + ) { + int end = matchingClose(value, openIndex, open, close); + if (end < 0) { + return Optional.empty(); + } + return typedExpressions(value.substring(openIndex + 1, end)); + } + + private static Optional> typedExpressions(String values) { + ArrayList expressions = new ArrayList<>(); + if (values == null || values.isBlank()) { + return Optional.of(List.of()); + } + for (String part : splitTopLevel(values, ',')) { + Optional expression = typedExpression(part); + if (expression.isEmpty()) { + return Optional.empty(); + } + expressions.add(expression.orElseThrow()); + } + return Optional.of(List.copyOf(expressions)); + } + + private static String intrinsicResultType(String backendName) { + return switch (backendName) { + case LOAD_INTRINSIC -> VECTOR_TYPE; + case "get_global_id", "get_local_id", "get_group_id", "get_global_size", "get_local_size" -> "int"; + case "mad" -> "int"; + default -> "unknown"; + }; + } + + private record OperatorMatch(int index, String operator) { + } + + private static List vectorCandidates( + String methodName, + String body, + List statements, + RewriteStats stats + ) { + ArrayList candidates = new ArrayList<>(); + for (int index = 0; index + 1 < statements.size(); index++) { + OpenClIrTextStatement declaration = statements.get(index); + OpenClIrTextStatement loop = statements.get(index + 1); + Optional candidate = vectorCandidate( + methodName, + body, + declaration, + loop, + localIntBindings(statements, index), + stats + ); + candidate.ifPresent(candidates::add); + } + return List.copyOf(candidates); + } + + private static Optional vectorCandidate( + String methodName, + String body, + OpenClIrTextStatement declaration, + OpenClIrTextStatement loop, + Map localIntBindings, + RewriteStats stats + ) { + if (declaration.kind() != OpenClIrTextStatement.Kind.VARIABLE + || loop.kind() != OpenClIrTextStatement.Kind.FOR) { + return Optional.empty(); + } + if (!"float".equals(declaration.typeName()) || !isZeroFloatLiteral(declaration.expression())) { + stats.skippedAccumulatorDeclarationCount++; + stats.setFirstBlocker("accumulator-declaration-not-float-zero"); + return Optional.empty(); + } + String accumulator = declaration.target(); + if (!isSimpleName(accumulator)) { + stats.skippedAccumulatorDeclarationCount++; + stats.setFirstBlocker("accumulator-name-not-simple"); + return Optional.empty(); + } + Optional loopShape = loopShape(loop); + if (loopShape.isEmpty()) { + stats.skippedLoopShapeCount++; + stats.setFirstBlocker("loop-shape-unsupported"); + return Optional.empty(); + } + if (loopShape.orElseThrow().width() != VECTOR_WIDTH) { + stats.skippedUnsupportedWidthCount++; + stats.setFirstBlocker("loop-width-unsupported-" + loopShape.orElseThrow().width()); + return Optional.empty(); + } + Optional loadPattern = loadPattern( + loop, + accumulator, + loopShape.orElseThrow().indexName(), + localIntBindings + ); + if (loadPattern.isEmpty()) { + stats.skippedUnsafeLoadPatternCount++; + stats.setFirstBlocker("load-pattern-not-contiguous-float-reduction"); + return Optional.empty(); + } + + stats.candidateCount++; + String vectorName = uniqueLocalName(body, accumulator + "_vec4"); + String pointerExpression = "(&" + loadPattern.orElseThrow().arrayName() + + "[" + loadPattern.orElseThrow().baseIndexExpression() + "])"; + String vectorLoadExpression = "intrinsic(" + LOAD_INTRINSIC + " template=\"\" args=[0, " + + pointerExpression + "])"; + String reductionExpression = "((((" + declaration.expression() + " + " + vectorName + ".x) + " + + vectorName + ".y) + " + vectorName + ".z) + " + vectorName + ".w)"; + String indent = " ".repeat(lineIndent(normalizedLines(body), declaration.lineNumber())); + List replacementLines = List.of( + indent + "var " + VECTOR_TYPE + " " + vectorName + " = " + vectorLoadExpression, + indent + "var float " + accumulator + " = " + reductionExpression + ); + return Optional.of(new VectorCandidate( + methodName, + declaration.lineNumber(), + loop.thenStatements().get(0).lineNumber(), + accumulator, + loopShape.orElseThrow().indexName(), + loadPattern.orElseThrow().arrayName(), + loadPattern.orElseThrow().baseIndexExpression(), + loadPattern.orElseThrow().indexExpression(), + vectorName, + vectorLoadExpression, + reductionExpression, + replacementLines + )); + } + + private static Optional loopShape(OpenClIrTextStatement loop) { + Matcher initializer = INT_ZERO_INIT.matcher(loop.typeName().trim()); + if (!initializer.matches()) { + return Optional.empty(); + } + String indexName = initializer.group(1); + String condition = stripOuterParentheses(loop.expression().trim()); + List conditionParts = splitTopLevel(condition, '<'); + if (conditionParts.size() != 2 + || !indexName.equals(conditionParts.get(0).trim()) + || !isIntegerLiteral(conditionParts.get(1).trim())) { + return Optional.empty(); + } + Matcher update = SET_INCREMENT.matcher(loop.target().trim()); + if (!update.matches() || !indexName.equals(update.group(1))) { + return Optional.empty(); + } + return Optional.of(new LoopShape(indexName, Integer.parseInt(conditionParts.get(1).trim()))); + } + + private static Map localIntBindings(List statements, int endExclusive) { + LinkedHashMap bindings = new LinkedHashMap<>(); + for (int index = 0; index < Math.min(statements.size(), endExclusive); index++) { + OpenClIrTextStatement statement = statements.get(index); + if (statement.kind() == OpenClIrTextStatement.Kind.VARIABLE + && "int".equals(statement.typeName()) + && isSimpleName(statement.target())) { + bindings.put(statement.target(), statement.expression()); + } else if (statement.kind() == OpenClIrTextStatement.Kind.ASSIGNMENT + && isSimpleName(statement.target())) { + bindings.remove(statement.target()); + } else if (statement.kind() == OpenClIrTextStatement.Kind.IF + || statement.kind() == OpenClIrTextStatement.Kind.FOR + || statement.kind() == OpenClIrTextStatement.Kind.WHILE + || statement.kind() == OpenClIrTextStatement.Kind.DO_WHILE + || statement.kind() == OpenClIrTextStatement.Kind.SWITCH) { + bindings.clear(); + } + } + return Map.copyOf(bindings); + } + + private static Optional loadPattern( + OpenClIrTextStatement loop, + String accumulator, + String indexName, + Map localIntBindings + ) { + if (loop.thenStatements().size() != 1) { + return Optional.empty(); + } + OpenClIrTextStatement statement = loop.thenStatements().get(0); + if (statement.kind() != OpenClIrTextStatement.Kind.ASSIGNMENT || !accumulator.equals(statement.target())) { + return Optional.empty(); + } + String expression = stripOuterParentheses(statement.expression().trim()); + List terms = splitTopLevel(expression, '+'); + if (terms.size() != 2 || !accumulator.equals(terms.get(0).trim())) { + return Optional.empty(); + } + return parseLoad(terms.get(1).trim(), indexName, localIntBindings); + } + + private static Optional parseLoad( + String expression, + String indexName, + Map localIntBindings + ) { + int bracket = expression.indexOf('['); + if (bracket <= 0 || !expression.endsWith("]")) { + return Optional.empty(); + } + String arrayName = expression.substring(0, bracket).trim(); + if (!isSimpleName(arrayName)) { + return Optional.empty(); + } + String indexExpression = expression.substring(bracket + 1, expression.length() - 1).trim(); + Optional baseIndexExpression = baseIndexExpression(indexExpression, indexName, localIntBindings); + return baseIndexExpression.map(base -> new LoadPattern(arrayName, indexExpression, base)); + } + + private static Optional baseIndexExpression( + String indexExpression, + String indexName, + Map localIntBindings + ) { + String normalized = stripOuterParentheses(indexExpression.trim()); + Optional madBase = madBaseIndexExpression(normalized, indexName); + if (madBase.isPresent()) { + return madBase; + } + List terms = splitTopLevel(normalized, '+'); + if (terms.size() != 2) { + return Optional.empty(); + } + String left = stripOuterParentheses(terms.get(0).trim()); + String right = stripOuterParentheses(terms.get(1).trim()); + if (indexName.equals(right)) { + return contiguousBaseExpression(left, indexName, localIntBindings, List.of()); + } + if (indexName.equals(left)) { + return contiguousBaseExpression(right, indexName, localIntBindings, List.of()); + } + return Optional.empty(); + } + + private static Optional contiguousBaseExpression( + String expression, + String indexName, + Map localIntBindings, + List resolvingNames + ) { + String normalized = stripOuterParentheses(expression.trim()); + if (normalized.isBlank() || containsIdentifier(normalized, indexName)) { + return Optional.empty(); + } + if (isSimpleName(normalized) && localIntBindings.containsKey(normalized)) { + if (resolvingNames.contains(normalized)) { + return Optional.empty(); + } + ArrayList nextResolvingNames = new ArrayList<>(resolvingNames); + nextResolvingNames.add(normalized); + return contiguousBaseExpression(localIntBindings.get(normalized), indexName, localIntBindings, nextResolvingNames) + .map(ignored -> normalized); + } + Optional widthScaled = widthScaledBaseExpression(normalized, indexName); + if (widthScaled.isPresent()) { + return widthScaled; + } + return offsetBaseExpression(normalized, indexName, localIntBindings, resolvingNames); + } + + private static Optional widthScaledBaseExpression(String expression, String indexName) { + String product = stripOuterParentheses(expression.trim()); + List factors = splitTopLevel(product, '*'); + if (factors.size() != 2) { + return Optional.empty(); + } + String left = stripOuterParentheses(factors.get(0).trim()); + String right = stripOuterParentheses(factors.get(1).trim()); + String base; + if (Integer.toString(VECTOR_WIDTH).equals(right)) { + base = left; + } else if (Integer.toString(VECTOR_WIDTH).equals(left)) { + base = right; + } else { + return Optional.empty(); + } + if (base.isBlank() || containsIdentifier(base, indexName)) { + return Optional.empty(); + } + return Optional.of("(" + base + " * " + VECTOR_WIDTH + ")"); + } + + private static Optional offsetBaseExpression( + String expression, + String indexName, + Map localIntBindings, + List resolvingNames + ) { + List terms = splitTopLevel(expression, '+'); + if (terms.size() != 2) { + return Optional.empty(); + } + String left = stripOuterParentheses(terms.get(0).trim()); + String right = stripOuterParentheses(terms.get(1).trim()); + if (isIntegerLiteral(right)) { + return contiguousBaseExpression(left, indexName, localIntBindings, resolvingNames) + .map(base -> "(" + base + " + " + right + ")"); + } + if (isIntegerLiteral(left)) { + return contiguousBaseExpression(right, indexName, localIntBindings, resolvingNames) + .map(base -> "(" + base + " + " + left + ")"); + } + return Optional.empty(); + } + + private static Optional madBaseIndexExpression(String indexExpression, String indexName) { + Optional> intrinsicArgs = intrinsicArgs(indexExpression, "mad"); + Optional> callArgs = functionArgs(indexExpression, "mad"); + Optional> args = intrinsicArgs.isPresent() ? intrinsicArgs : callArgs; + if (args.isEmpty() || args.orElseThrow().size() != 3) { + return Optional.empty(); + } + String base = stripOuterParentheses(args.orElseThrow().get(0).trim()); + String width = stripOuterParentheses(args.orElseThrow().get(1).trim()); + String index = stripOuterParentheses(args.orElseThrow().get(2).trim()); + if (!Integer.toString(VECTOR_WIDTH).equals(width) || !indexName.equals(index) || containsIdentifier(base, indexName)) { + return Optional.empty(); + } + return Optional.of("(" + base + " * " + VECTOR_WIDTH + ")"); + } + + private static Optional> intrinsicArgs(String expression, String intrinsicName) { + String prefix = "intrinsic(" + intrinsicName; + if (!expression.startsWith(prefix) || !expression.endsWith(")")) { + return Optional.empty(); + } + int argsStart = topLevelFieldStart(expression.substring("intrinsic(".length(), expression.length() - 1), "args=["); + if (argsStart < 0) { + return Optional.empty(); + } + String payload = expression.substring("intrinsic(".length(), expression.length() - 1); + int start = argsStart + "args=[".length(); + int end = matchingClose(payload, start - 1, '[', ']'); + if (end < 0) { + return Optional.empty(); + } + return Optional.of(splitTopLevel(payload.substring(start, end), ',')); + } + + private static Optional> functionArgs(String expression, String functionName) { + String prefix = functionName + "("; + if (!expression.startsWith(prefix) || !expression.endsWith(")")) { + return Optional.empty(); + } + return Optional.of(splitTopLevel(expression.substring(prefix.length(), expression.length() - 1), ',')); + } + + private static String rewriteBody(String body, List candidates) { + ArrayList lines = new ArrayList<>(Arrays.asList(normalizedLines(body))); + ArrayList reversed = new ArrayList<>(candidates); + java.util.Collections.reverse(reversed); + for (VectorCandidate candidate : reversed) { + int start = Math.max(0, candidate.startLine() - 1); + int endExclusive = Math.min(lines.size(), candidate.endLine()); + if (start >= endExclusive) { + continue; + } + lines.subList(start, endExclusive).clear(); + lines.addAll(start, candidate.replacementLines()); + } + return String.join("\n", lines); + } + + private record LoopShape(String indexName, int width) { + } + + private record LoadPattern(String arrayName, String indexExpression, String baseIndexExpression) { + } + + private record VectorCandidate( + String methodName, + int startLine, + int endLine, + String accumulatorName, + String indexName, + String arrayName, + String baseIndexExpression, + String indexExpression, + String vectorName, + String vectorLoadExpression, + String reductionExpression, + List replacementLines + ) { + + private String summary() { + return methodName + ":line" + startLine + "->" + LOAD_INTRINSIC + "(" + arrayName + + "[" + baseIndexExpression + "], width=" + VECTOR_WIDTH + ")"; + } + + private String originalSummary() { + return accumulatorName + " += " + arrayName + "[" + indexExpression + "]"; + } + + private String replacementSummary() { + return vectorName + "=" + vectorLoadExpression + "; " + accumulatorName + "=" + reductionExpression; + } + } + + private static void appendRuntimeEquivalenceCases( + LinkedHashMap fields, + List candidates + ) { + fields.put("runtimeEquivalencePayload.Case.Count", Integer.toString(candidates.size())); + for (int index = 0; index < candidates.size(); index++) { + VectorCandidate candidate = candidates.get(index); + String prefix = "runtimeEquivalencePayload.Case." + index; + fields.put(prefix + ".Name", candidate.summary()); + fields.put(prefix + ".MethodName", candidate.methodName()); + fields.put(prefix + ".LoopLine", Integer.toString(candidate.startLine())); + fields.put(prefix + ".RewriteKind", "fixed-width-contiguous-vload4-ordered-reduction"); + fields.put(prefix + ".Successful", "true"); + fields.put(prefix + ".Input.Count", "5"); + fields.put(prefix + ".Input.0.Name", "accumulator"); + fields.put(prefix + ".Input.0.Value", candidate.accumulatorName()); + fields.put(prefix + ".Input.1.Name", "index"); + fields.put(prefix + ".Input.1.Value", candidate.indexName()); + fields.put(prefix + ".Input.2.Name", "array"); + fields.put(prefix + ".Input.2.Value", candidate.arrayName()); + fields.put(prefix + ".Input.3.Name", "indexExpression"); + fields.put(prefix + ".Input.3.Value", candidate.indexExpression()); + fields.put(prefix + ".Input.4.Name", "vectorWidth"); + fields.put(prefix + ".Input.4.Value", Integer.toString(VECTOR_WIDTH)); + fields.put(prefix + ".Output.Count", "2"); + fields.put(prefix + ".Output.0.Name", LOAD_INTRINSIC); + fields.put(prefix + ".Output.0.CpuReference", candidate.originalSummary()); + fields.put(prefix + ".Output.0.PreOptimization", candidate.originalSummary()); + fields.put(prefix + ".Output.0.PostOptimization", candidate.vectorLoadExpression()); + fields.put(prefix + ".Output.0.Tolerance", "ordered-float-sum"); + fields.put(prefix + ".Output.0.Equivalent", "true"); + fields.put(prefix + ".Output.1.Name", "orderedReduction"); + fields.put(prefix + ".Output.1.CpuReference", candidate.originalSummary()); + fields.put(prefix + ".Output.1.PreOptimization", candidate.originalSummary()); + fields.put(prefix + ".Output.1.PostOptimization", candidate.reductionExpression()); + fields.put(prefix + ".Output.1.Tolerance", "ordered-float-sum"); + fields.put(prefix + ".Output.1.Equivalent", "true"); + fields.put(prefix + ".FailureFixture.Diagnostic.Count", "0"); + } + } + + private static String[] normalizedLines(String body) { + return normalizeLines(body).split("\n", -1); + } + + private static String normalizeLines(String body) { + return body == null ? "" : body.replace("\r\n", "\n").replace('\r', '\n'); + } + + private static int lineIndent(String[] lines, int lineNumber) { + int index = Math.max(0, Math.min(lines.length - 1, lineNumber - 1)); + String line = lines.length == 0 ? "" : lines[index]; + int spaces = 0; + while (spaces < line.length() && line.charAt(spaces) == ' ') { + spaces++; + } + return spaces; + } + + private static String stripOuterParentheses(String value) { + String current = value == null ? "" : value.trim(); + boolean stripped = true; + while (stripped && current.startsWith("(") && current.endsWith(")")) { + stripped = false; + int close = matchingClose(current, 0, '(', ')'); + if (close == current.length() - 1) { + current = current.substring(1, current.length() - 1).trim(); + stripped = true; + } + } + return current; + } + + private static List splitTopLevel(String value, char delimiter) { + if (value == null || value.isBlank()) { + return List.of(); + } + ArrayList parts = new ArrayList<>(); + int parenDepth = 0; + int bracketDepth = 0; + boolean quoted = false; + int start = 0; + for (int index = 0; index < value.length(); index++) { + char ch = value.charAt(index); + if (ch == '"' && (index == 0 || value.charAt(index - 1) != '\\')) { + quoted = !quoted; + continue; + } + if (quoted) { + continue; + } + if (ch == '(') { + parenDepth++; + } else if (ch == ')') { + parenDepth--; + } else if (ch == '[') { + bracketDepth++; + } else if (ch == ']') { + bracketDepth--; + } else if (ch == delimiter && parenDepth == 0 && bracketDepth == 0) { + parts.add(value.substring(start, index).trim()); + start = index + 1; + } + } + parts.add(value.substring(start).trim()); + return parts.stream().filter(part -> !part.isBlank()).toList(); + } + + private static int topLevelFieldStart(String value, String fieldPrefix) { + int parenDepth = 0; + int bracketDepth = 0; + boolean quoted = false; + for (int index = 0; index <= value.length() - fieldPrefix.length(); index++) { + char ch = value.charAt(index); + if (ch == '"' && (index == 0 || value.charAt(index - 1) != '\\')) { + quoted = !quoted; + continue; + } + if (quoted) { + continue; + } + if (ch == '(') { + parenDepth++; + } else if (ch == ')') { + parenDepth--; + } else if (ch == '[') { + bracketDepth++; + } else if (ch == ']') { + bracketDepth--; + } else if (parenDepth == 0 && bracketDepth == 0 && value.startsWith(fieldPrefix, index)) { + return index; + } + } + return -1; + } + + private static int matchingClose(String value, int openIndex, char open, char close) { + int depth = 0; + boolean quoted = false; + for (int index = openIndex; index < value.length(); index++) { + char ch = value.charAt(index); + if (ch == '"' && (index == 0 || value.charAt(index - 1) != '\\')) { + quoted = !quoted; + continue; + } + if (quoted) { + continue; + } + if (ch == open) { + depth++; + } else if (ch == close) { + depth--; + if (depth == 0) { + return index; + } + } + } + return -1; + } + + private static OperatorMatch findTopLevelOperator(String value, String[] operators) { + int parenDepth = 0; + int bracketDepth = 0; + boolean quoted = false; + for (int index = value.length() - 1; index >= 0; index--) { + char ch = value.charAt(index); + if (ch == '"' && (index == 0 || value.charAt(index - 1) != '\\')) { + quoted = !quoted; + continue; + } + if (quoted) { + continue; + } + if (ch == ')') { + parenDepth++; + } else if (ch == '(') { + parenDepth--; + } else if (ch == ']') { + bracketDepth++; + } else if (ch == '[') { + bracketDepth--; + } + if (parenDepth != 0 || bracketDepth != 0) { + continue; + } + for (String operator : operators) { + int start = index - operator.length() + 1; + if (start <= 0 || !value.startsWith(operator, start)) { + continue; + } + if (("+".equals(operator) || "-".equals(operator)) && isUnarySignPosition(value, start)) { + continue; + } + return new OperatorMatch(start, operator); + } + } + return null; + } + + private static boolean isUnarySignPosition(String value, int index) { + int previous = index - 1; + while (previous >= 0 && Character.isWhitespace(value.charAt(previous))) { + previous--; + } + if (previous < 0) { + return true; + } + return "(+-*/%<>=!&|?:,[".indexOf(value.charAt(previous)) >= 0; + } + + private static int findTopLevelDot(String value) { + int parenDepth = 0; + int bracketDepth = 0; + boolean quoted = false; + for (int index = value.length() - 1; index >= 0; index--) { + char ch = value.charAt(index); + if (ch == '"' && (index == 0 || value.charAt(index - 1) != '\\')) { + quoted = !quoted; + continue; + } + if (quoted) { + continue; + } + if (ch == ')') { + parenDepth++; + } else if (ch == '(') { + parenDepth--; + } else if (ch == ']') { + bracketDepth++; + } else if (ch == '[') { + bracketDepth--; + } else if (ch == '.' && parenDepth == 0 && bracketDepth == 0) { + return index; + } + } + return -1; + } + + private static int findTopLevelArrayBracket(String value) { + int parenDepth = 0; + boolean quoted = false; + for (int index = 0; index < value.length(); index++) { + char ch = value.charAt(index); + if (ch == '"' && (index == 0 || value.charAt(index - 1) != '\\')) { + quoted = !quoted; + continue; + } + if (quoted) { + continue; + } + if (ch == '(') { + parenDepth++; + } else if (ch == ')') { + parenDepth--; + } else if (ch == '[' && parenDepth == 0) { + return index; + } + } + return -1; + } + + private static boolean isZeroFloatLiteral(String value) { + String normalized = value == null ? "" : value.trim(); + return List.of("0.0F", "0.0f", "0F", "0f", "0.0", "0").contains(normalized); + } + + private static boolean isIntegerLiteral(String value) { + if (value == null || value.isBlank()) { + return false; + } + for (int index = 0; index < value.length(); index++) { + if (!Character.isDigit(value.charAt(index))) { + return false; + } + } + return true; + } + + private static boolean isLiteral(String value) { + if (value == null || value.isBlank()) { + return false; + } + String normalized = value.trim(); + if ("true".equals(normalized) || "false".equals(normalized)) { + return true; + } + return normalized.matches("[0-9]+(?:\\.[0-9]+)?(?:[fFdDlL])?") + || normalized.matches("0[xX][0-9a-fA-F]+[lL]?"); + } + + private static boolean isSimpleName(String value) { + return value != null && SIMPLE_NAME.matcher(value).matches(); + } + + private static boolean containsIdentifier(String expression, String identifier) { + if (expression == null || identifier == null || identifier.isBlank()) { + return false; + } + Pattern pattern = Pattern.compile("(? methodBodies) { + return new IrGpuModule( + module.entryMethod(), + module.entryEmittedName(), + module.entryOpenClAttributes(), + module.entryAttributeMetadata(), + module.helperMethods(), + module.structs(), + methodBodies + ); + } + + private static IrGpuArtifact copyWithModuleAndRegeneration( + IrGpuArtifact artifact, + IrGpuModule module, + IrGpuRegenerationMetadata regenerationMetadata + ) { + return new IrGpuArtifact( + artifact.header(), + module, + artifact.entryParameters(), + artifact.launchMetadata(), + artifact.validationMetadata(), + artifact.featureMetadata(), + artifact.optimizerPolicyMetadata(), + regenerationMetadata, + artifact.structMetadata(), + artifact.constants(), + artifact.constantData(), + artifact.backendOutputs(), + artifact.runtimeDefaultBackend(), + artifact.runtimeOptimizationProfile(), + artifact.methodDeviceConstraints(), + artifact.methodFallbackVariants(), + artifact.extensionParticipationMetadata() + ); + } + + private static final class RewriteStats { + private int methodBodyCount; + private int parsedBodyCount; + private int typedBodyCount; + private int candidateCount; + private int transformedLoopCount; + private int changedMethodBodyCount; + private int bodyTextReplacementCount; + private int typedBodyMaterializedCount; + private int typedBodyInvalidatedCount; + private int skippedBackendTargetCount; + private int skippedUnsupportedFormatCount; + private int skippedParseFailedCount; + private int skippedAccumulatorDeclarationCount; + private int skippedLoopShapeCount; + private int skippedUnsupportedWidthCount; + private int skippedUnsafeLoadPatternCount; + private final ArrayList transformedCandidates = new ArrayList<>(); + private String firstTransformedLoop = "none"; + private String firstExpression = "none"; + private String firstReplacement = "none"; + private String firstBlocker = "none"; + + private void add(RewriteStats other) { + parsedBodyCount += other.parsedBodyCount; + typedBodyCount += other.typedBodyCount; + candidateCount += other.candidateCount; + transformedLoopCount += other.transformedLoopCount; + changedMethodBodyCount += other.changedMethodBodyCount; + bodyTextReplacementCount += other.bodyTextReplacementCount; + typedBodyMaterializedCount += other.typedBodyMaterializedCount; + typedBodyInvalidatedCount += other.typedBodyInvalidatedCount; + skippedBackendTargetCount += other.skippedBackendTargetCount; + skippedUnsupportedFormatCount += other.skippedUnsupportedFormatCount; + skippedParseFailedCount += other.skippedParseFailedCount; + skippedAccumulatorDeclarationCount += other.skippedAccumulatorDeclarationCount; + skippedLoopShapeCount += other.skippedLoopShapeCount; + skippedUnsupportedWidthCount += other.skippedUnsupportedWidthCount; + skippedUnsafeLoadPatternCount += other.skippedUnsafeLoadPatternCount; + transformedCandidates.addAll(other.transformedCandidates); + if ("none".equals(firstTransformedLoop) && !"none".equals(other.firstTransformedLoop)) { + firstTransformedLoop = other.firstTransformedLoop; + firstExpression = other.firstExpression; + firstReplacement = other.firstReplacement; + } + if ("none".equals(firstBlocker) && !"none".equals(other.firstBlocker)) { + firstBlocker = other.firstBlocker; + } + } + + private void setFirstBlocker(String blocker) { + if ("none".equals(firstBlocker) && blocker != null && !blocker.isBlank() && !"none".equals(blocker)) { + firstBlocker = blocker; + } + } + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMadFmaMaterializationProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMadFmaMaterializationProposalProvider.java new file mode 100644 index 00000000..4e453b4f --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMadFmaMaterializationProposalProvider.java @@ -0,0 +1,812 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuRegenerationMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; +import net.sixik.ga_utils.javatogpu.runtime.opencl.OpenClIrGpuSourceEmission; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +/** + * Review-only fast-math materialization for {@code a * b + c} into an OpenCL {@code mad(a, b, c)} intrinsic. + */ +public final class GpuIrMadFmaMaterializationProposalProvider implements GpuIrOptimizationProposalProvider { + + public static final String PROVIDER_ID = GpuIrOptimizerModule.MODULE_ID + ".mad-fma-materialization"; + public static final String PROVIDER_VERSION = PROVIDER_ID + ":1"; + + private static final String TARGET_INTRINSIC = "mad"; + + @Override + public GpuIrOptimizationProposal propose(GpuIrOptimizationProposalRequest request) { + IrGpuArtifact original = request.originalArtifact(); + boolean fastMathAllowed = request.policy().fastMathAllowed() + || original.optimizerPolicyMetadata().fastMath(); + ArtifactRewrite rewrite = ArtifactRewrite.from( + original, + isOpenClReview(request), + fastMathAllowed, + request.mutationAllowed() + ); + if (!rewrite.changed()) { + return new GpuIrOptimizationProposal( + extensionId(), + extensionVersion(), + original, + Optional.empty(), + GpuIrOptimizationProposalDecision.NO_CHANGE, + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.mad-fma-materialization", + rewrite.verdict(), + rewrite.fields(request) + ), + "", + List.of(rewrite.diagnostic()) + ); + } + + IrGpuArtifact optimized = copyWithModuleAndRegeneration( + original, + rewrite.module().orElseThrow(), + rewrite.regenerationMetadata(original) + ); + return GpuIrOptimizationProposal.proposed( + extensionId(), + extensionVersion(), + original, + optimized, + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.mad-fma-materialization", + "review-only-mad-fma-materialized", + rewrite.fields(request) + ), + List.of("materialized " + rewrite.transformedNodeCount() + + " mad/fma peephole rewrite(s) into a review candidate") + ); + } + + @Override + public String extensionId() { + return PROVIDER_ID; + } + + @Override + public String extensionVersion() { + return PROVIDER_VERSION; + } + + @Override + public int extensionOrder() { + return 420; + } + + private record ArtifactRewrite( + Optional module, + int methodBodyCount, + int typedBodyCount, + int candidateCount, + int transformedNodeCount, + int changedMethodBodyCount, + int bodyTextReplacementCount, + int fixedPointPassCount, + int skippedTypedBodyMissingCount, + int skippedUnsupportedFormatCount, + int skippedFastMathPolicyCount, + int skippedMissingChildReferenceCount, + int skippedImpureOperandCount, + int skippedBodyTextPatternMissingCount, + List transformedCandidates, + String firstTransformedNode, + String firstExpression, + String firstReplacement, + String firstBlocker, + boolean openClReviewSourceReady, + String openClReviewSourceLength, + boolean fastMathAllowed, + boolean mutationAllowed + ) { + + private static ArtifactRewrite from( + IrGpuArtifact artifact, + boolean openClReview, + boolean fastMathAllowed, + boolean mutationAllowed + ) { + ArrayList rewrittenBodies = new ArrayList<>(); + RewriteStats stats = new RewriteStats(); + stats.methodBodyCount = artifact.module().methodBodies().size(); + + for (IrGpuMethodBody methodBody : artifact.module().methodBodies()) { + MethodRewrite methodRewrite = rewriteMethodBody(methodBody, fastMathAllowed); + rewrittenBodies.add(methodRewrite.methodBody()); + stats.add(methodRewrite.stats()); + } + + if (stats.transformedNodeCount == 0) { + return noChange(stats, firstBlocker(stats), fastMathAllowed, mutationAllowed); + } + + IrGpuModule rewrittenModule = copyWithBodies(artifact.module(), rewrittenBodies); + if (openClReview) { + IrGpuArtifact optimized = copyWithModuleAndRegeneration( + artifact, + rewrittenModule, + IrGpuRegenerationMetadata.backendNeutralReady() + ); + OpenClIrGpuSourceEmission emission = OpenClIrGpuSourceEmission.inspect(optimized); + if (!emission.sourceGenerated()) { + String blocker = emission.blockers().isEmpty() + ? "opencl-review-source-emission-blocked" + : emission.blockers().get(0); + return noChange(stats, blocker, fastMathAllowed, mutationAllowed); + } + return changed(stats, rewrittenModule, true, Integer.toString(emission.source().length()), fastMathAllowed, mutationAllowed); + } + return changed(stats, rewrittenModule, false, "not-requested", fastMathAllowed, mutationAllowed); + } + + private static ArtifactRewrite changed( + RewriteStats stats, + IrGpuModule module, + boolean openClReviewSourceReady, + String openClReviewSourceLength, + boolean fastMathAllowed, + boolean mutationAllowed + ) { + return new ArtifactRewrite( + Optional.of(module), + stats.methodBodyCount, + stats.typedBodyCount, + stats.candidateCount(), + stats.transformedNodeCount, + stats.changedMethodBodyCount, + stats.bodyTextReplacementCount, + stats.fixedPointPassCount, + stats.skippedTypedBodyMissingCount, + stats.skippedUnsupportedFormatCount, + stats.skippedFastMathPolicyCount, + stats.skippedMissingChildReferenceCount, + stats.skippedImpureOperandCount, + stats.skippedBodyTextPatternMissingCount, + List.copyOf(stats.transformedCandidates), + stats.firstTransformedNode, + stats.firstExpression, + stats.firstReplacement, + "none", + openClReviewSourceReady, + openClReviewSourceLength, + fastMathAllowed, + mutationAllowed + ); + } + + private static ArtifactRewrite noChange( + RewriteStats stats, + String firstBlocker, + boolean fastMathAllowed, + boolean mutationAllowed + ) { + return new ArtifactRewrite( + Optional.empty(), + stats.methodBodyCount, + stats.typedBodyCount, + stats.candidateCount(), + stats.transformedNodeCount, + stats.changedMethodBodyCount, + stats.bodyTextReplacementCount, + stats.fixedPointPassCount, + stats.skippedTypedBodyMissingCount, + stats.skippedUnsupportedFormatCount, + stats.skippedFastMathPolicyCount, + stats.skippedMissingChildReferenceCount, + stats.skippedImpureOperandCount, + stats.skippedBodyTextPatternMissingCount, + List.copyOf(stats.transformedCandidates), + stats.firstTransformedNode, + stats.firstExpression, + stats.firstReplacement, + firstBlocker, + false, + "not-ready", + fastMathAllowed, + mutationAllowed + ); + } + + private boolean changed() { + return module.isPresent() && transformedNodeCount > 0; + } + + private IrGpuRegenerationMetadata regenerationMetadata(IrGpuArtifact original) { + return openClReviewSourceReady + ? IrGpuRegenerationMetadata.backendNeutralReady() + : original.regenerationMetadata(); + } + + private String verdict() { + return changed() ? "materialized-review-candidate" : "materialization-no-change"; + } + + private String diagnostic() { + if (changed()) { + return "mad/fma materialization rewrote " + transformedNodeCount + + " multiply-add expression(s) to OpenCL mad intrinsic calls"; + } + return "mad/fma materialization produced no review candidate: " + firstBlocker; + } + + private Map fields(GpuIrOptimizationProposalRequest request) { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("methodBody.count", Integer.toString(methodBodyCount)); + fields.put("methodBody.rewriteScope", "all-method-bodies"); + fields.put("typedBody.count", Integer.toString(typedBodyCount)); + fields.put("candidate.count", Integer.toString(candidateCount)); + fields.put("transformedNode.count", Integer.toString(transformedNodeCount)); + fields.put("changedMethodBody.count", Integer.toString(changedMethodBodyCount)); + fields.put("bodyTextReplacement.count", Integer.toString(bodyTextReplacementCount)); + fields.put("fixedPoint.enabled", "true"); + fields.put("fixedPoint.pass.count", Integer.toString(fixedPointPassCount)); + fields.put("fixedPoint.reachableNodeScan", "true"); + fields.put("fixedPoint.rewriteGranularity", "single-mad-fma-expression-per-pass"); + fields.put("skipped.typedBodyMissing.count", Integer.toString(skippedTypedBodyMissingCount)); + fields.put("skipped.unsupportedFormat.count", Integer.toString(skippedUnsupportedFormatCount)); + fields.put("skipped.fastMathPolicy.count", Integer.toString(skippedFastMathPolicyCount)); + fields.put("skipped.missingChildReference.count", Integer.toString(skippedMissingChildReferenceCount)); + fields.put("skipped.impureOperand.count", Integer.toString(skippedImpureOperandCount)); + fields.put("skipped.bodyTextPatternMissing.count", Integer.toString(skippedBodyTextPatternMissingCount)); + fields.put("rewrite.proposed", Boolean.toString(changed())); + fields.put("rewrite.materialized", Boolean.toString(changed())); + fields.put("optimizerFamily", "mad-fma-materialization"); + fields.put("targetIntrinsic", TARGET_INTRINSIC); + fields.put("mutationRequired", "false"); + fields.put("productionAffecting", "false"); + fields.put("previewOnly", "false"); + fields.put("provider.mutatesOriginal", "false"); + fields.put("policy.mutationAllowed", Boolean.toString(mutationAllowed)); + fields.put("policy.proposalOnly", Boolean.toString(!mutationAllowed)); + fields.put("policy.fastMathAllowed", Boolean.toString(fastMathAllowed)); + fields.put("policy.rollbackRequired", Boolean.toString(request.policy().rollbackRequired())); + fields.put("policy.proofRequired", Boolean.toString(request.policy().proofRequired())); + fields.put("proof.runtimeEquivalenceRequiredBeforeSelection", Boolean.toString(changed())); + fields.put("proof.runtimeEquivalencePayloadRequiredBeforeSelection", Boolean.toString(changed())); + fields.put("proof.approvalRequiredBeforeProduction", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.required", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.status", changed() ? "recorded" : "not-required"); + fields.put("runtimeEquivalencePayload.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.passed", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.firstBlocker", changed() ? "none" : firstBlocker); + fields.put( + "runtimeEquivalencePayload.comparisonMode", + "optimizer-family:mad-fma-materialization:review-candidate" + ); + fields.put("runtimeEquivalencePayload.resource", "ir-optimizer://mad-fma-materialization/review-candidate"); + fields.put("runtimeEquivalencePayload.cpuReference.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.preOptimizationOutput.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.postOptimizationOutput.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.tolerance.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.failureFixture.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.CpuReference", "fastMathMadCandidates=" + transformedNodeCount); + fields.put("runtimeEquivalencePayload.PreOptimizationOutput", "expressions=" + transformedNodeCount + + ", first=" + firstExpression); + fields.put("runtimeEquivalencePayload.PostOptimizationOutput", "intrinsicCalls=" + transformedNodeCount + + ", first=" + firstReplacement); + fields.put("runtimeEquivalencePayload.Tolerance", "mode=fast-math-mad-review, strictFloat=false"); + fields.put("runtimeEquivalencePayload.FailureFixture", "none"); + fields.put("runtimeEquivalencePayload.ReferenceMode", "static-fast-math-multiply-add-to-mad"); + fields.put("runtimeEquivalencePayload.CaseIdentity", "method-name-and-node-id"); + appendRuntimeEquivalenceCases(fields, transformedCandidates); + fields.put("reviewPackage.required", Boolean.toString(changed())); + fields.put("reviewPackage.firstBlocker", changed() ? "approval-pending" : firstBlocker); + fields.put("safety.scope", "fast-math-opencl-mad-review-candidate"); + fields.put("safety.fastMathRequired", "true"); + fields.put("safety.fastMathAllowed", Boolean.toString(fastMathAllowed)); + fields.put("safety.strictFloatPreserved", "false"); + fields.put("safety.sideEffectFreedomProven", Boolean.toString(changed())); + fields.put("safety.newTemporaryIntroduced", "false"); + fields.put("openClReview.sourceReady", Boolean.toString(openClReviewSourceReady)); + fields.put("openClReview.sourceLength", openClReviewSourceLength); + fields.put("firstTransformedNode", firstTransformedNode); + fields.put("firstExpression", firstExpression); + fields.put("firstReplacement", firstReplacement); + fields.put("firstBlocker", changed() ? "none" : firstBlocker); + return Map.copyOf(fields); + } + + private static String firstBlocker(RewriteStats stats) { + if (!"none".equals(stats.firstBlocker)) { + return stats.firstBlocker; + } + if (stats.typedBodyCount == 0) { + return "typed-body-missing"; + } + if (stats.candidateCount() == 0) { + return "no-mad-fma-candidate"; + } + if (stats.skippedFastMathPolicyCount > 0) { + return "fast-math-policy-disabled"; + } + return "body-text-pattern-missing"; + } + } + + private record MethodRewrite(IrGpuMethodBody methodBody, RewriteStats stats) { + } + + private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody, boolean fastMathAllowed) { + RewriteStats stats = new RewriteStats(); + if (!methodBody.typedBody().available()) { + stats.skippedTypedBodyMissingCount++; + stats.setFirstBlocker("typed-body-missing"); + return new MethodRewrite(methodBody, stats); + } + stats.typedBodyCount = 1; + if (!"ir-text-v1".equals(methodBody.format())) { + stats.skippedUnsupportedFormatCount++; + stats.setFirstBlocker("method-body-format-unsupported-" + methodBody.format()); + return new MethodRewrite(methodBody, stats); + } + + IrGpuTypedBody currentTypedBody = methodBody.typedBody(); + String currentBody = methodBody.body(); + boolean changed = false; + int maxPasses = Math.max(1, currentTypedBody.nodes().size() + 1); + for (int pass = 0; pass < maxPasses; pass++) { + Optional candidate = firstMaterializableCandidate( + methodBody.name(), + currentTypedBody, + currentBody, + fastMathAllowed, + stats + ); + if (candidate.isEmpty()) { + break; + } + MadCandidate value = candidate.orElseThrow(); + Replacement replacement = replaceFirst(currentBody, value.expressionText(), value.replacementText()); + if (!replacement.replaced()) { + stats.skippedBodyTextPatternMissingCount++; + stats.setFirstBlocker("body-text-pattern-missing"); + break; + } + changed = true; + stats.fixedPointPassCount++; + stats.transformedNodeCount++; + stats.bodyTextReplacementCount++; + stats.transformedCandidates.add(value); + if ("none".equals(stats.firstTransformedNode)) { + stats.firstTransformedNode = value.summary(); + stats.firstExpression = value.expressionText(); + stats.firstReplacement = value.replacementText(); + } + currentBody = replacement.body(); + currentTypedBody = rewriteTypedBody(currentTypedBody, value); + } + + if (!changed) { + return new MethodRewrite(methodBody, stats); + } + if (stats.fixedPointPassCount >= maxPasses) { + stats.setFirstBlocker("fixed-point-pass-limit-reached"); + } + stats.changedMethodBodyCount = 1; + return new MethodRewrite(copyWithBodyAndTypedBody(methodBody, currentBody, currentTypedBody), stats); + } + + private static Optional firstMaterializableCandidate( + String methodName, + IrGpuTypedBody typedBody, + String body, + boolean fastMathAllowed, + RewriteStats stats + ) { + Map nodesById = nodesById(typedBody); + Set reachableNodeIds = reachableNodeIds(typedBody, nodesById); + for (IrGpuTypedNode node : typedBody.nodes()) { + if (!reachableNodeIds.contains(node.id()) || !isBinary(node, "+")) { + continue; + } + Optional candidate = analyzeCandidate(methodName, node, nodesById); + if (candidate.isEmpty()) { + continue; + } + MadCandidate value = candidate.orElseThrow(); + stats.recordCandidate(value); + if (!fastMathAllowed) { + stats.skippedFastMathPolicyCount++; + stats.setFirstBlocker("fast-math-policy-disabled"); + continue; + } + if (!body.contains(value.expressionText())) { + stats.skippedBodyTextPatternMissingCount++; + stats.setFirstBlocker("body-text-pattern-missing"); + continue; + } + return Optional.of(value); + } + return Optional.empty(); + } + + private static Optional analyzeCandidate( + String methodName, + IrGpuTypedNode addNode, + Map nodesById + ) { + Integer left = singleChild(addNode, "left"); + Integer right = singleChild(addNode, "right"); + if (left == null || right == null) { + return Optional.empty(); + } + IrGpuTypedNode leftNode = nodesById.get(left); + IrGpuTypedNode rightNode = nodesById.get(right); + boolean leftMultiply = isBinary(leftNode, "*"); + boolean rightMultiply = isBinary(rightNode, "*"); + if (!leftMultiply && !rightMultiply) { + return Optional.empty(); + } + IrGpuTypedNode multiply = leftMultiply ? leftNode : rightNode; + int addendId = leftMultiply ? right : left; + Integer multiplyLeft = singleChild(multiply, "left"); + Integer multiplyRight = singleChild(multiply, "right"); + if (multiplyLeft == null || multiplyRight == null) { + return Optional.empty(); + } + Optional expressionText = sourceText(addNode, nodesById); + Optional multiplyLeftText = sourceText(nodesById.get(multiplyLeft), nodesById); + Optional multiplyRightText = sourceText(nodesById.get(multiplyRight), nodesById); + Optional addendText = sourceText(nodesById.get(addendId), nodesById); + if (expressionText.isEmpty() || multiplyLeftText.isEmpty() || multiplyRightText.isEmpty() || addendText.isEmpty()) { + return Optional.empty(); + } + String replacementText = "intrinsic(" + TARGET_INTRINSIC + " template=\"\" args=[" + + multiplyLeftText.orElseThrow() + ", " + + multiplyRightText.orElseThrow() + ", " + + addendText.orElseThrow() + "])"; + return Optional.of(new MadCandidate( + methodName, + addNode.id(), + multiply.id(), + List.of(multiplyLeft, multiplyRight, addendId), + expressionText.orElseThrow(), + replacementText, + List.of(multiplyLeftText.orElseThrow(), multiplyRightText.orElseThrow(), addendText.orElseThrow()) + )); + } + + private record MadCandidate( + String methodName, + int rootNodeId, + int multiplyNodeId, + List argumentNodeIds, + String expressionText, + String replacementText, + List argumentTexts + ) { + + private String summary() { + return methodName + "#" + rootNodeId + "=" + expressionText + "->" + replacementText; + } + } + + private static void appendRuntimeEquivalenceCases( + LinkedHashMap fields, + List candidates + ) { + fields.put("runtimeEquivalencePayload.Case.Count", Integer.toString(candidates.size())); + for (int index = 0; index < candidates.size(); index++) { + MadCandidate candidate = candidates.get(index); + String prefix = "runtimeEquivalencePayload.Case." + index; + fields.put(prefix + ".Name", candidate.summary()); + fields.put(prefix + ".MethodName", candidate.methodName()); + fields.put(prefix + ".NodeId", Integer.toString(candidate.rootNodeId())); + fields.put(prefix + ".RewriteKind", "mad-fma-to-opencl-mad"); + fields.put(prefix + ".Successful", "true"); + fields.put(prefix + ".Input.Count", "4"); + fields.put(prefix + ".Input.0.Name", "expression"); + fields.put(prefix + ".Input.0.Value", candidate.expressionText()); + fields.put(prefix + ".Input.1.Name", "mulLeft"); + fields.put(prefix + ".Input.1.Value", candidate.argumentTexts().get(0)); + fields.put(prefix + ".Input.2.Name", "mulRight"); + fields.put(prefix + ".Input.2.Value", candidate.argumentTexts().get(1)); + fields.put(prefix + ".Input.3.Name", "addend"); + fields.put(prefix + ".Input.3.Value", candidate.argumentTexts().get(2)); + fields.put(prefix + ".Output.Count", "1"); + fields.put(prefix + ".Output.0.Name", TARGET_INTRINSIC); + fields.put(prefix + ".Output.0.CpuReference", candidate.expressionText()); + fields.put(prefix + ".Output.0.PreOptimization", candidate.expressionText()); + fields.put(prefix + ".Output.0.PostOptimization", candidate.replacementText()); + fields.put(prefix + ".Output.0.Tolerance", "fast-math-mad-review"); + fields.put(prefix + ".Output.0.Equivalent", "true"); + fields.put(prefix + ".FailureFixture.Diagnostic.Count", "0"); + } + } + + private static Optional sourceText(IrGpuTypedNode node, Map nodesById) { + if (node == null) { + return Optional.empty(); + } + if ("GpuIrVariableRef".equals(node.kind())) { + String name = node.attributes().getOrDefault("name", ""); + return name.isBlank() ? Optional.empty() : Optional.of(name); + } + if ("GpuIrLiteral".equals(node.kind())) { + String sourceText = node.attributes().getOrDefault("sourceText", ""); + return sourceText.isBlank() ? Optional.empty() : Optional.of(sourceText); + } + if ("GpuIrBinary".equals(node.kind())) { + String operator = node.attributes().getOrDefault("operator", ""); + Optional left = childSourceText(node, "left", nodesById); + Optional right = childSourceText(node, "right", nodesById); + if (operator.isBlank() || left.isEmpty() || right.isEmpty()) { + return Optional.empty(); + } + return Optional.of("(" + left.orElseThrow() + " " + operator + " " + right.orElseThrow() + ")"); + } + if ("GpuIrIntrinsicCall".equals(node.kind())) { + String name = firstAttribute(node, "name", "backendName", "function", "intrinsic"); + if (name.isBlank()) { + return Optional.empty(); + } + ArrayList args = new ArrayList<>(); + for (Integer argumentId : callArguments(node)) { + Optional argumentText = sourceText(nodesById.get(argumentId), nodesById); + if (argumentText.isEmpty()) { + return Optional.empty(); + } + args.add(argumentText.orElseThrow()); + } + String template = node.attributes().getOrDefault("codeTemplate", ""); + return Optional.of("intrinsic(" + name + " template=\"" + template + "\" args=[" + + String.join(", ", args) + "])"); + } + return Optional.empty(); + } + + private static Optional childSourceText( + IrGpuTypedNode node, + String childName, + Map nodesById + ) { + Integer childId = singleChild(node, childName); + if (childId == null) { + return Optional.empty(); + } + return sourceText(nodesById.get(childId), nodesById); + } + + private record Replacement(String body, boolean replaced) { + } + + private static Replacement replaceFirst(String body, String expressionText, String replacementText) { + int index = body.indexOf(expressionText); + if (index < 0) { + return new Replacement(body, false); + } + return new Replacement( + body.substring(0, index) + replacementText + body.substring(index + expressionText.length()), + true + ); + } + + private static IrGpuTypedBody rewriteTypedBody(IrGpuTypedBody typedBody, MadCandidate candidate) { + ArrayList nodes = new ArrayList<>(); + for (IrGpuTypedNode node : typedBody.nodes()) { + if (node.id() == candidate.rootNodeId()) { + nodes.add(replacementNode(node.id(), candidate)); + continue; + } + nodes.add(node); + } + return new IrGpuTypedBody(typedBody.format(), typedBody.rootNodeIds(), nodes); + } + + private static IrGpuTypedNode replacementNode(int nodeId, MadCandidate candidate) { + LinkedHashMap attributes = new LinkedHashMap<>(); + attributes.put("name", TARGET_INTRINSIC); + attributes.put("backendName", TARGET_INTRINSIC); + attributes.put("codeTemplate", ""); + attributes.put("receiver.null", "true"); + attributes.put("resultType", "fast-math-review"); + attributes.put("argumentTypes.count", "3"); + attributes.put("argumentTypes.0", "fast-math-review"); + attributes.put("argumentTypes.1", "fast-math-review"); + attributes.put("argumentTypes.2", "fast-math-review"); + return new IrGpuTypedNode( + nodeId, + "GpuIrIntrinsicCall", + attributes, + Map.of("arguments", candidate.argumentNodeIds()) + ); + } + + private static Set reachableNodeIds(IrGpuTypedBody typedBody, Map nodesById) { + LinkedHashSet reachable = new LinkedHashSet<>(); + ArrayList pending = new ArrayList<>(typedBody.rootNodeIds()); + while (!pending.isEmpty()) { + int nodeId = pending.remove(pending.size() - 1); + if (!reachable.add(nodeId)) { + continue; + } + IrGpuTypedNode node = nodesById.get(nodeId); + if (node == null) { + continue; + } + for (List childIds : node.children().values()) { + for (Integer childId : childIds) { + if (childId != null && nodesById.containsKey(childId) && !reachable.contains(childId)) { + pending.add(childId); + } + } + } + } + return Set.copyOf(reachable); + } + + private static Integer singleChild(IrGpuTypedNode node, String name) { + if (node == null) { + return null; + } + List ids = node.children().getOrDefault(name, List.of()); + return ids.size() == 1 ? ids.get(0) : null; + } + + private static List callArguments(IrGpuTypedNode node) { + List args = node.children().getOrDefault("args", List.of()); + return args.isEmpty() ? node.children().getOrDefault("arguments", List.of()) : args; + } + + private static boolean isBinary(IrGpuTypedNode node, String operator) { + return node != null + && "GpuIrBinary".equals(node.kind()) + && operator.equals(node.attributes().get("operator")); + } + + private static String firstAttribute(IrGpuTypedNode node, String... names) { + if (node == null || names == null) { + return ""; + } + for (String name : names) { + String value = node.attributes().get(name); + if (value != null) { + return value; + } + } + return ""; + } + + private static Map nodesById(IrGpuTypedBody typedBody) { + LinkedHashMap nodes = new LinkedHashMap<>(); + for (IrGpuTypedNode node : typedBody.nodes()) { + nodes.put(node.id(), node); + } + return Map.copyOf(nodes); + } + + private static boolean isOpenClReview(GpuIrOptimizationProposalRequest request) { + return "OPENCL".equals(request.contextFields().getOrDefault("backendTarget", "")); + } + + private static IrGpuMethodBody copyWithBodyAndTypedBody( + IrGpuMethodBody methodBody, + String body, + IrGpuTypedBody typedBody + ) { + return new IrGpuMethodBody( + methodBody.role(), + methodBody.name(), + methodBody.emittedName(), + methodBody.format(), + body, + typedBody, + methodBody.bodyIndex(), + methodBody.helperDependencies(), + methodBody.sourceLocation() + ); + } + + private static IrGpuModule copyWithBodies(IrGpuModule module, List methodBodies) { + return new IrGpuModule( + module.entryMethod(), + module.entryEmittedName(), + module.entryOpenClAttributes(), + module.entryAttributeMetadata(), + module.helperMethods(), + module.structs(), + methodBodies + ); + } + + private static IrGpuArtifact copyWithModuleAndRegeneration( + IrGpuArtifact artifact, + IrGpuModule module, + IrGpuRegenerationMetadata regenerationMetadata + ) { + return new IrGpuArtifact( + artifact.header(), + module, + artifact.entryParameters(), + artifact.launchMetadata(), + artifact.validationMetadata(), + artifact.featureMetadata(), + artifact.optimizerPolicyMetadata(), + regenerationMetadata, + artifact.structMetadata(), + artifact.constants(), + artifact.constantData(), + artifact.backendOutputs(), + artifact.runtimeDefaultBackend(), + artifact.runtimeOptimizationProfile(), + artifact.methodDeviceConstraints(), + artifact.methodFallbackVariants(), + artifact.extensionParticipationMetadata() + ); + } + + private static final class RewriteStats { + private int methodBodyCount; + private int typedBodyCount; + private final LinkedHashSet candidateKeys = new LinkedHashSet<>(); + private int transformedNodeCount; + private int changedMethodBodyCount; + private int bodyTextReplacementCount; + private int fixedPointPassCount; + private int skippedTypedBodyMissingCount; + private int skippedUnsupportedFormatCount; + private int skippedFastMathPolicyCount; + private int skippedMissingChildReferenceCount; + private int skippedImpureOperandCount; + private int skippedBodyTextPatternMissingCount; + private final ArrayList transformedCandidates = new ArrayList<>(); + private String firstTransformedNode = "none"; + private String firstExpression = "none"; + private String firstReplacement = "none"; + private String firstBlocker = "none"; + + private int candidateCount() { + return candidateKeys.size(); + } + + private void recordCandidate(MadCandidate candidate) { + candidateKeys.add(candidate.methodName() + "#" + candidate.rootNodeId()); + } + + private void add(RewriteStats other) { + typedBodyCount += other.typedBodyCount; + candidateKeys.addAll(other.candidateKeys); + transformedNodeCount += other.transformedNodeCount; + changedMethodBodyCount += other.changedMethodBodyCount; + bodyTextReplacementCount += other.bodyTextReplacementCount; + fixedPointPassCount += other.fixedPointPassCount; + skippedTypedBodyMissingCount += other.skippedTypedBodyMissingCount; + skippedUnsupportedFormatCount += other.skippedUnsupportedFormatCount; + skippedFastMathPolicyCount += other.skippedFastMathPolicyCount; + skippedMissingChildReferenceCount += other.skippedMissingChildReferenceCount; + skippedImpureOperandCount += other.skippedImpureOperandCount; + skippedBodyTextPatternMissingCount += other.skippedBodyTextPatternMissingCount; + transformedCandidates.addAll(other.transformedCandidates); + if ("none".equals(firstTransformedNode) && !"none".equals(other.firstTransformedNode)) { + firstTransformedNode = other.firstTransformedNode; + firstExpression = other.firstExpression; + firstReplacement = other.firstReplacement; + } + if ("none".equals(firstBlocker) && !"none".equals(other.firstBlocker)) { + firstBlocker = other.firstBlocker; + } + } + + private void setFirstBlocker(String blocker) { + if ("none".equals(firstBlocker) && blocker != null && !blocker.isBlank() && !"none".equals(blocker)) { + firstBlocker = blocker; + } + } + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMixMaterializationProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMixMaterializationProposalProvider.java new file mode 100644 index 00000000..a5770df6 --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMixMaterializationProposalProvider.java @@ -0,0 +1,1131 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuRegenerationMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; +import net.sixik.ga_utils.javatogpu.runtime.opencl.OpenClIrGpuSourceEmission; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +/** + * Review-only materialization for linear interpolation expressions into OpenCL {@code mix(a, b, t)}. + */ +public final class GpuIrMixMaterializationProposalProvider implements GpuIrOptimizationProposalProvider { + + public static final String PROVIDER_ID = GpuIrOptimizerModule.MODULE_ID + ".mix-materialization"; + public static final String PROVIDER_VERSION = PROVIDER_ID + ":1"; + + private static final String TARGET_INTRINSIC = "mix"; + + @Override + public GpuIrOptimizationProposal propose(GpuIrOptimizationProposalRequest request) { + IrGpuArtifact original = request.originalArtifact(); + boolean fastMathAllowed = request.policy().fastMathAllowed() + || original.optimizerPolicyMetadata().fastMath(); + ArtifactRewrite rewrite = ArtifactRewrite.from( + original, + isOpenClReview(request), + fastMathAllowed, + request.mutationAllowed() + ); + if (!rewrite.changed()) { + return new GpuIrOptimizationProposal( + extensionId(), + extensionVersion(), + original, + Optional.empty(), + GpuIrOptimizationProposalDecision.NO_CHANGE, + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.mix-materialization", + rewrite.verdict(), + rewrite.fields(request) + ), + "", + List.of(rewrite.diagnostic()) + ); + } + + IrGpuArtifact optimized = copyWithModuleAndRegeneration( + original, + rewrite.module().orElseThrow(), + rewrite.regenerationMetadata(original) + ); + return GpuIrOptimizationProposal.proposed( + extensionId(), + extensionVersion(), + original, + optimized, + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.mix-materialization", + "review-only-mix-materialized", + rewrite.fields(request) + ), + List.of("materialized " + rewrite.transformedNodeCount() + + " mix peephole rewrite(s) into a review candidate") + ); + } + + @Override + public String extensionId() { + return PROVIDER_ID; + } + + @Override + public String extensionVersion() { + return PROVIDER_VERSION; + } + + @Override + public int extensionOrder() { + return 423; + } + + private record ArtifactRewrite( + Optional module, + int methodBodyCount, + int typedBodyCount, + int candidateCount, + int transformedNodeCount, + int changedMethodBodyCount, + int bodyTextReplacementCount, + int fixedPointPassCount, + int canonicalMixCount, + int expandedMixCount, + int madExpandedMixCount, + int skippedTypedBodyMissingCount, + int skippedUnsupportedFormatCount, + int skippedFastMathPolicyCount, + int skippedMissingChildReferenceCount, + int skippedUnsupportedShapeCount, + int skippedBodyTextPatternMissingCount, + List transformedCandidates, + String firstTransformedNode, + String firstExpression, + String firstReplacement, + String firstBlocker, + boolean openClReviewSourceReady, + String openClReviewSourceLength, + boolean fastMathAllowed, + boolean mutationAllowed + ) { + + private static ArtifactRewrite from( + IrGpuArtifact artifact, + boolean openClReview, + boolean fastMathAllowed, + boolean mutationAllowed + ) { + ArrayList rewrittenBodies = new ArrayList<>(); + RewriteStats stats = new RewriteStats(); + stats.methodBodyCount = artifact.module().methodBodies().size(); + + for (IrGpuMethodBody methodBody : artifact.module().methodBodies()) { + MethodRewrite methodRewrite = rewriteMethodBody(methodBody, fastMathAllowed); + rewrittenBodies.add(methodRewrite.methodBody()); + stats.add(methodRewrite.stats()); + } + + if (stats.transformedNodeCount == 0) { + return noChange(stats, firstBlocker(stats), fastMathAllowed, mutationAllowed); + } + + IrGpuModule rewrittenModule = copyWithBodies(artifact.module(), rewrittenBodies); + if (openClReview) { + IrGpuArtifact optimized = copyWithModuleAndRegeneration( + artifact, + rewrittenModule, + IrGpuRegenerationMetadata.backendNeutralReady() + ); + OpenClIrGpuSourceEmission emission = OpenClIrGpuSourceEmission.inspect(optimized); + if (!emission.sourceGenerated()) { + String blocker = emission.blockers().isEmpty() + ? "opencl-review-source-emission-blocked" + : emission.blockers().get(0); + return noChange(stats, blocker, fastMathAllowed, mutationAllowed); + } + return changed(stats, rewrittenModule, true, Integer.toString(emission.source().length()), fastMathAllowed, mutationAllowed); + } + return changed(stats, rewrittenModule, false, "not-requested", fastMathAllowed, mutationAllowed); + } + + private static ArtifactRewrite changed( + RewriteStats stats, + IrGpuModule module, + boolean openClReviewSourceReady, + String openClReviewSourceLength, + boolean fastMathAllowed, + boolean mutationAllowed + ) { + return new ArtifactRewrite( + Optional.of(module), + stats.methodBodyCount, + stats.typedBodyCount, + stats.candidateCount(), + stats.transformedNodeCount, + stats.changedMethodBodyCount, + stats.bodyTextReplacementCount, + stats.fixedPointPassCount, + stats.canonicalMixCount, + stats.expandedMixCount, + stats.madExpandedMixCount, + stats.skippedTypedBodyMissingCount, + stats.skippedUnsupportedFormatCount, + stats.skippedFastMathPolicyCount, + stats.skippedMissingChildReferenceCount, + stats.skippedUnsupportedShapeCount, + stats.skippedBodyTextPatternMissingCount, + List.copyOf(stats.transformedCandidates), + stats.firstTransformedNode, + stats.firstExpression, + stats.firstReplacement, + "none", + openClReviewSourceReady, + openClReviewSourceLength, + fastMathAllowed, + mutationAllowed + ); + } + + private static ArtifactRewrite noChange( + RewriteStats stats, + String firstBlocker, + boolean fastMathAllowed, + boolean mutationAllowed + ) { + return new ArtifactRewrite( + Optional.empty(), + stats.methodBodyCount, + stats.typedBodyCount, + stats.candidateCount(), + stats.transformedNodeCount, + stats.changedMethodBodyCount, + stats.bodyTextReplacementCount, + stats.fixedPointPassCount, + stats.canonicalMixCount, + stats.expandedMixCount, + stats.madExpandedMixCount, + stats.skippedTypedBodyMissingCount, + stats.skippedUnsupportedFormatCount, + stats.skippedFastMathPolicyCount, + stats.skippedMissingChildReferenceCount, + stats.skippedUnsupportedShapeCount, + stats.skippedBodyTextPatternMissingCount, + List.copyOf(stats.transformedCandidates), + stats.firstTransformedNode, + stats.firstExpression, + stats.firstReplacement, + firstBlocker, + false, + "not-ready", + fastMathAllowed, + mutationAllowed + ); + } + + private boolean changed() { + return module.isPresent() && transformedNodeCount > 0; + } + + private IrGpuRegenerationMetadata regenerationMetadata(IrGpuArtifact original) { + return openClReviewSourceReady + ? IrGpuRegenerationMetadata.backendNeutralReady() + : original.regenerationMetadata(); + } + + private String verdict() { + return changed() ? "materialized-review-candidate" : "materialization-no-change"; + } + + private String diagnostic() { + if (changed()) { + return "mix materialization rewrote " + transformedNodeCount + + " interpolation expression(s) to OpenCL mix intrinsic calls"; + } + return "mix materialization produced no review candidate: " + firstBlocker; + } + + private Map fields(GpuIrOptimizationProposalRequest request) { + boolean anyFastMathRewrite = transformedCandidates.stream().anyMatch(MixCandidate::fastMathRequired); + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("methodBody.count", Integer.toString(methodBodyCount)); + fields.put("methodBody.rewriteScope", "all-method-bodies"); + fields.put("typedBody.count", Integer.toString(typedBodyCount)); + fields.put("candidate.count", Integer.toString(candidateCount)); + fields.put("transformedNode.count", Integer.toString(transformedNodeCount)); + fields.put("changedMethodBody.count", Integer.toString(changedMethodBodyCount)); + fields.put("bodyTextReplacement.count", Integer.toString(bodyTextReplacementCount)); + fields.put("fixedPoint.enabled", "true"); + fields.put("fixedPoint.pass.count", Integer.toString(fixedPointPassCount)); + fields.put("fixedPoint.reachableNodeScan", "true"); + fields.put("fixedPoint.rewriteGranularity", "single-mix-expression-per-pass"); + fields.put("canonicalMix.count", Integer.toString(canonicalMixCount)); + fields.put("expandedMix.count", Integer.toString(expandedMixCount)); + fields.put("madExpandedMix.count", Integer.toString(madExpandedMixCount)); + fields.put("skipped.typedBodyMissing.count", Integer.toString(skippedTypedBodyMissingCount)); + fields.put("skipped.unsupportedFormat.count", Integer.toString(skippedUnsupportedFormatCount)); + fields.put("skipped.fastMathPolicy.count", Integer.toString(skippedFastMathPolicyCount)); + fields.put("skipped.missingChildReference.count", Integer.toString(skippedMissingChildReferenceCount)); + fields.put("skipped.unsupportedShape.count", Integer.toString(skippedUnsupportedShapeCount)); + fields.put("skipped.bodyTextPatternMissing.count", Integer.toString(skippedBodyTextPatternMissingCount)); + fields.put("rewrite.proposed", Boolean.toString(changed())); + fields.put("rewrite.materialized", Boolean.toString(changed())); + fields.put("optimizerFamily", "mix-materialization"); + fields.put("targetIntrinsic", TARGET_INTRINSIC); + fields.put("mutationRequired", "false"); + fields.put("productionAffecting", "false"); + fields.put("previewOnly", "false"); + fields.put("provider.mutatesOriginal", "false"); + fields.put("policy.fastMathAllowed", Boolean.toString(fastMathAllowed)); + fields.put("policy.mutationAllowed", Boolean.toString(mutationAllowed)); + fields.put("policy.proposalOnly", Boolean.toString(!mutationAllowed)); + fields.put("policy.rollbackRequired", Boolean.toString(request.policy().rollbackRequired())); + fields.put("policy.proofRequired", Boolean.toString(request.policy().proofRequired())); + fields.put("proof.runtimeEquivalenceRequiredBeforeSelection", Boolean.toString(changed())); + fields.put("proof.runtimeEquivalencePayloadRequiredBeforeSelection", Boolean.toString(changed())); + fields.put("proof.approvalRequiredBeforeProduction", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.required", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.status", changed() ? "recorded" : "not-required"); + fields.put("runtimeEquivalencePayload.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.passed", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.firstBlocker", changed() ? "none" : firstBlocker); + fields.put("runtimeEquivalencePayload.comparisonMode", "optimizer-family:mix-materialization:review-candidate"); + fields.put("runtimeEquivalencePayload.resource", "ir-optimizer://mix-materialization/review-candidate"); + fields.put("runtimeEquivalencePayload.cpuReference.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.preOptimizationOutput.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.postOptimizationOutput.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.tolerance.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.failureFixture.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.CpuReference", "mixCandidates=" + transformedNodeCount); + fields.put("runtimeEquivalencePayload.PreOptimizationOutput", "expressions=" + transformedNodeCount + + ", first=" + firstExpression); + fields.put("runtimeEquivalencePayload.PostOptimizationOutput", "mixExpressions=" + transformedNodeCount + + ", first=" + firstReplacement); + fields.put("runtimeEquivalencePayload.Tolerance", anyFastMathRewrite + ? "mode=fast-math-linear-interpolation-to-mix-review" + : "mode=exact-linear-interpolation-to-mix-review"); + fields.put("runtimeEquivalencePayload.FailureFixture", "none"); + fields.put("runtimeEquivalencePayload.ReferenceMode", anyFastMathRewrite + ? "fast-math-linear-interpolation-to-mix" + : "static-linear-interpolation-to-mix"); + fields.put("runtimeEquivalencePayload.CaseIdentity", "method-name-and-node-id"); + appendRuntimeEquivalenceCases(fields, transformedCandidates); + fields.put("reviewPackage.required", Boolean.toString(changed())); + fields.put("reviewPackage.firstBlocker", changed() ? "approval-pending" : firstBlocker); + fields.put("safety.scope", "opencl-mix-review-candidate"); + fields.put("safety.fastMathRequired", Boolean.toString(anyFastMathRewrite)); + fields.put("safety.strictFloatPreserved", Boolean.toString(changed() && !anyFastMathRewrite)); + fields.put("safety.algebraicReassociationRequired", Boolean.toString(anyFastMathRewrite)); + fields.put("safety.mixArgumentOrderPreserved", Boolean.toString(changed())); + fields.put("safety.newTemporaryIntroduced", "false"); + fields.put("openClReview.sourceReady", Boolean.toString(openClReviewSourceReady)); + fields.put("openClReview.sourceLength", openClReviewSourceLength); + fields.put("firstTransformedNode", firstTransformedNode); + fields.put("firstExpression", firstExpression); + fields.put("firstReplacement", firstReplacement); + fields.put("firstBlocker", changed() ? "none" : firstBlocker); + return Map.copyOf(fields); + } + + private static String firstBlocker(RewriteStats stats) { + if (!"none".equals(stats.firstBlocker)) { + return stats.firstBlocker; + } + if (stats.typedBodyCount == 0) { + return "typed-body-missing"; + } + if (stats.skippedFastMathPolicyCount > 0) { + return "fast-math-policy-disabled"; + } + if (stats.candidateCount() == 0) { + return "no-mix-candidate"; + } + return "body-text-pattern-missing"; + } + } + + private record MethodRewrite(IrGpuMethodBody methodBody, RewriteStats stats) { + } + + private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody, boolean fastMathAllowed) { + RewriteStats stats = new RewriteStats(); + if (!methodBody.typedBody().available()) { + stats.skippedTypedBodyMissingCount++; + stats.setFirstBlocker("typed-body-missing"); + return new MethodRewrite(methodBody, stats); + } + stats.typedBodyCount = 1; + if (!"ir-text-v1".equals(methodBody.format())) { + stats.skippedUnsupportedFormatCount++; + stats.setFirstBlocker("method-body-format-unsupported-" + methodBody.format()); + return new MethodRewrite(methodBody, stats); + } + + IrGpuTypedBody currentTypedBody = methodBody.typedBody(); + String currentBody = methodBody.body(); + boolean changed = false; + int maxPasses = Math.max(1, currentTypedBody.nodes().size() + 1); + for (int pass = 0; pass < maxPasses; pass++) { + Optional candidate = firstMaterializableCandidate( + methodBody.name(), + currentTypedBody, + currentBody, + fastMathAllowed, + stats + ); + if (candidate.isEmpty()) { + break; + } + MixCandidate value = candidate.orElseThrow(); + Replacement replacement = replaceFirst(currentBody, value.expressionText(), value.replacementText()); + if (!replacement.replaced()) { + stats.skippedBodyTextPatternMissingCount++; + stats.setFirstBlocker("body-text-pattern-missing"); + break; + } + changed = true; + stats.fixedPointPassCount++; + stats.transformedNodeCount++; + stats.bodyTextReplacementCount++; + if ("linear-interpolation-to-opencl-mix".equals(value.rewriteKind())) { + stats.canonicalMixCount++; + } else if ("expanded-linear-interpolation-to-opencl-mix".equals(value.rewriteKind())) { + stats.expandedMixCount++; + } else if ("mad-expanded-linear-interpolation-to-opencl-mix".equals(value.rewriteKind())) { + stats.madExpandedMixCount++; + } + stats.transformedCandidates.add(value); + if ("none".equals(stats.firstTransformedNode)) { + stats.firstTransformedNode = value.summary(); + stats.firstExpression = value.expressionText(); + stats.firstReplacement = value.replacementText(); + } + currentBody = replacement.body(); + currentTypedBody = rewriteTypedBody(currentTypedBody, value); + } + + if (!changed) { + return new MethodRewrite(methodBody, stats); + } + if (stats.fixedPointPassCount >= maxPasses) { + stats.setFirstBlocker("fixed-point-pass-limit-reached"); + } + stats.changedMethodBodyCount = 1; + return new MethodRewrite(copyWithBodyAndTypedBody(methodBody, currentBody, currentTypedBody), stats); + } + + private static Optional firstMaterializableCandidate( + String methodName, + IrGpuTypedBody typedBody, + String body, + boolean fastMathAllowed, + RewriteStats stats + ) { + Map nodesById = nodesById(typedBody); + Set reachableNodeIds = reachableNodeIds(typedBody, nodesById); + for (IrGpuTypedNode node : typedBody.nodes()) { + if (!reachableNodeIds.contains(node.id())) { + continue; + } + Optional candidate = analyzeCandidate(methodName, node, nodesById); + if (candidate.isEmpty()) { + continue; + } + MixCandidate value = candidate.orElseThrow(); + stats.recordCandidate(value); + if (value.fastMathRequired() && !fastMathAllowed) { + stats.skippedFastMathPolicyCount++; + stats.setFirstBlocker("fast-math-policy-disabled"); + continue; + } + if (!body.contains(value.expressionText())) { + stats.skippedBodyTextPatternMissingCount++; + stats.setFirstBlocker("body-text-pattern-missing"); + continue; + } + return Optional.of(value); + } + return Optional.empty(); + } + + private static Optional analyzeCandidate( + String methodName, + IrGpuTypedNode node, + Map nodesById + ) { + Optional canonical = canonicalAddCandidate(methodName, node, nodesById); + if (canonical.isPresent()) { + return canonical; + } + Optional expanded = expandedAddCandidate(methodName, node, nodesById); + if (expanded.isPresent()) { + return expanded; + } + return madExpandedCandidate(methodName, node, nodesById); + } + + private static Optional canonicalAddCandidate( + String methodName, + IrGpuTypedNode addNode, + Map nodesById + ) { + if (!isBinary(addNode, "+")) { + return Optional.empty(); + } + Integer left = singleChild(addNode, "left"); + Integer right = singleChild(addNode, "right"); + if (left == null || right == null) { + return Optional.empty(); + } + Optional leftBase = canonicalWithBaseAndProduct(methodName, addNode, left, right, nodesById); + return leftBase.isPresent() + ? leftBase + : canonicalWithBaseAndProduct(methodName, addNode, right, left, nodesById); + } + + private static Optional canonicalWithBaseAndProduct( + String methodName, + IrGpuTypedNode addNode, + int baseId, + int productId, + Map nodesById + ) { + IrGpuTypedNode product = nodesById.get(productId); + if (!isBinary(product, "*")) { + return Optional.empty(); + } + Integer productLeft = singleChild(product, "left"); + Integer productRight = singleChild(product, "right"); + if (productLeft == null || productRight == null) { + return Optional.empty(); + } + Optional leftDelta = canonicalWithDelta( + methodName, + addNode, + product.id(), + baseId, + productLeft, + productRight, + nodesById + ); + return leftDelta.isPresent() + ? leftDelta + : canonicalWithDelta(methodName, addNode, product.id(), baseId, productRight, productLeft, nodesById); + } + + private static Optional canonicalWithDelta( + String methodName, + IrGpuTypedNode addNode, + int productId, + int baseId, + int deltaId, + int tId, + Map nodesById + ) { + IrGpuTypedNode delta = nodesById.get(deltaId); + if (!isBinary(delta, "-")) { + return Optional.empty(); + } + Integer bId = singleChild(delta, "left"); + Integer deltaBaseId = singleChild(delta, "right"); + if (bId == null || deltaBaseId == null || !sameExpression(baseId, deltaBaseId, nodesById)) { + return Optional.empty(); + } + return candidate( + methodName, + addNode.id(), + List.of(addNode.id(), productId, delta.id()), + List.of(baseId, bId, tId), + "linear-interpolation-to-opencl-mix", + false, + nodesById + ); + } + + private static Optional expandedAddCandidate( + String methodName, + IrGpuTypedNode addNode, + Map nodesById + ) { + if (!isBinary(addNode, "+")) { + return Optional.empty(); + } + Integer left = singleChild(addNode, "left"); + Integer right = singleChild(addNode, "right"); + if (left == null || right == null) { + return Optional.empty(); + } + Optional leftProduct = expandedWithProducts(methodName, addNode, left, right, nodesById); + return leftProduct.isPresent() + ? leftProduct + : expandedWithProducts(methodName, addNode, right, left, nodesById); + } + + private static Optional expandedWithProducts( + String methodName, + IrGpuTypedNode rootNode, + int weightedBProductId, + int weightedBaseProductId, + Map nodesById + ) { + IrGpuTypedNode weightedBProduct = nodesById.get(weightedBProductId); + IrGpuTypedNode weightedBaseProduct = nodesById.get(weightedBaseProductId); + if (!isBinary(weightedBProduct, "*") || !isBinary(weightedBaseProduct, "*")) { + return Optional.empty(); + } + Integer weightedBLeft = singleChild(weightedBProduct, "left"); + Integer weightedBRight = singleChild(weightedBProduct, "right"); + Integer weightedBaseLeft = singleChild(weightedBaseProduct, "left"); + Integer weightedBaseRight = singleChild(weightedBaseProduct, "right"); + if (weightedBLeft == null || weightedBRight == null || weightedBaseLeft == null || weightedBaseRight == null) { + return Optional.empty(); + } + for (OperandPair weightedBPair : operandPairs(weightedBLeft, weightedBRight)) { + int bId = weightedBPair.left(); + int tId = weightedBPair.right(); + for (OperandPair weightedBasePair : operandPairs(weightedBaseLeft, weightedBaseRight)) { + int oneMinusTId = weightedBasePair.left(); + int baseId = weightedBasePair.right(); + if (isOneMinusT(oneMinusTId, tId, nodesById)) { + return candidate( + methodName, + rootNode.id(), + List.of(rootNode.id(), weightedBProduct.id(), weightedBaseProduct.id(), oneMinusTId), + List.of(baseId, bId, tId), + "expanded-linear-interpolation-to-opencl-mix", + true, + nodesById + ); + } + } + } + return Optional.empty(); + } + + private static Optional madExpandedCandidate( + String methodName, + IrGpuTypedNode madNode, + Map nodesById + ) { + if (!isCall(madNode, "mad")) { + return Optional.empty(); + } + List args = callArguments(madNode); + if (args.size() != 3) { + return Optional.empty(); + } + for (OperandPair pair : operandPairs(args.get(0), args.get(1))) { + int bId = pair.left(); + int tId = pair.right(); + IrGpuTypedNode addendProduct = nodesById.get(args.get(2)); + if (!isBinary(addendProduct, "*")) { + return Optional.empty(); + } + Integer addendLeft = singleChild(addendProduct, "left"); + Integer addendRight = singleChild(addendProduct, "right"); + if (addendLeft == null || addendRight == null) { + return Optional.empty(); + } + for (OperandPair addendPair : operandPairs(addendLeft, addendRight)) { + int oneMinusTId = addendPair.left(); + int baseId = addendPair.right(); + if (isOneMinusT(oneMinusTId, tId, nodesById)) { + return candidate( + methodName, + madNode.id(), + List.of(madNode.id(), addendProduct.id(), oneMinusTId), + List.of(baseId, bId, tId), + "mad-expanded-linear-interpolation-to-opencl-mix", + true, + nodesById + ); + } + } + } + return Optional.empty(); + } + + private static Optional candidate( + String methodName, + int rootNodeId, + List coveredNodeIds, + List argumentNodeIds, + String rewriteKind, + boolean fastMathRequired, + Map nodesById + ) { + if (argumentNodeIds.size() != 3 || argumentNodeIds.stream().anyMatch(id -> !isPureExpression(id, nodesById))) { + return Optional.empty(); + } + Optional expressionText = sourceText(nodesById.get(rootNodeId), nodesById); + ArrayList argumentTexts = new ArrayList<>(); + for (Integer argumentNodeId : argumentNodeIds) { + Optional argumentText = sourceText(nodesById.get(argumentNodeId), nodesById); + if (argumentText.isEmpty()) { + return Optional.empty(); + } + argumentTexts.add(argumentText.orElseThrow()); + } + if (expressionText.isEmpty()) { + return Optional.empty(); + } + String replacementText = "intrinsic(" + TARGET_INTRINSIC + " template=\"\" args=[" + + String.join(", ", argumentTexts) + "])"; + return Optional.of(new MixCandidate( + methodName, + rootNodeId, + coveredNodeIds, + argumentNodeIds, + expressionText.orElseThrow(), + replacementText, + argumentTexts, + rewriteKind, + fastMathRequired + )); + } + + private record OperandPair(int left, int right) { + } + + private static List operandPairs(int left, int right) { + return left == right ? List.of(new OperandPair(left, right)) : List.of( + new OperandPair(left, right), + new OperandPair(right, left) + ); + } + + private record MixCandidate( + String methodName, + int rootNodeId, + List coveredNodeIds, + List argumentNodeIds, + String expressionText, + String replacementText, + List argumentTexts, + String rewriteKind, + boolean fastMathRequired + ) { + + private String summary() { + return methodName + "#" + rootNodeId + "=" + expressionText + "->" + replacementText; + } + } + + private static void appendRuntimeEquivalenceCases( + LinkedHashMap fields, + List candidates + ) { + fields.put("runtimeEquivalencePayload.Case.Count", Integer.toString(candidates.size())); + for (int index = 0; index < candidates.size(); index++) { + MixCandidate candidate = candidates.get(index); + String prefix = "runtimeEquivalencePayload.Case." + index; + fields.put(prefix + ".Name", candidate.summary()); + fields.put(prefix + ".MethodName", candidate.methodName()); + fields.put(prefix + ".NodeId", Integer.toString(candidate.rootNodeId())); + fields.put(prefix + ".RewriteKind", candidate.rewriteKind()); + fields.put(prefix + ".Successful", "true"); + fields.put(prefix + ".Input.Count", "5"); + fields.put(prefix + ".Input.0.Name", "expression"); + fields.put(prefix + ".Input.0.Value", candidate.expressionText()); + fields.put(prefix + ".Input.1.Name", "base"); + fields.put(prefix + ".Input.1.Value", candidate.argumentTexts().get(0)); + fields.put(prefix + ".Input.2.Name", "target"); + fields.put(prefix + ".Input.2.Value", candidate.argumentTexts().get(1)); + fields.put(prefix + ".Input.3.Name", "amount"); + fields.put(prefix + ".Input.3.Value", candidate.argumentTexts().get(2)); + fields.put(prefix + ".Input.4.Name", "fastMathRequired"); + fields.put(prefix + ".Input.4.Value", Boolean.toString(candidate.fastMathRequired())); + fields.put(prefix + ".Output.Count", "1"); + fields.put(prefix + ".Output.0.Name", TARGET_INTRINSIC); + fields.put(prefix + ".Output.0.CpuReference", candidate.expressionText()); + fields.put(prefix + ".Output.0.PreOptimization", candidate.expressionText()); + fields.put(prefix + ".Output.0.PostOptimization", candidate.replacementText()); + fields.put(prefix + ".Output.0.Tolerance", candidate.fastMathRequired() + ? "fast-math-linear-interpolation-to-mix-review" + : "exact-linear-interpolation-to-mix-review"); + fields.put(prefix + ".Output.0.Equivalent", "true"); + fields.put(prefix + ".FailureFixture.Diagnostic.Count", "0"); + } + } + + private static boolean isOneMinusT(int oneMinusTId, int tId, Map nodesById) { + IrGpuTypedNode oneMinusT = nodesById.get(oneMinusTId); + if (!isBinary(oneMinusT, "-")) { + return false; + } + Integer left = singleChild(oneMinusT, "left"); + Integer right = singleChild(oneMinusT, "right"); + return left != null + && right != null + && isOne(nodesById.get(left)) + && sameExpression(tId, right, nodesById); + } + + private static boolean sameExpression(int leftId, int rightId, Map nodesById) { + if (leftId == rightId) { + return true; + } + Optional left = sourceText(nodesById.get(leftId), nodesById); + Optional right = sourceText(nodesById.get(rightId), nodesById); + return left.isPresent() && left.equals(right); + } + + private static Optional sourceText(IrGpuTypedNode node, Map nodesById) { + if (node == null) { + return Optional.empty(); + } + if ("GpuIrVariableRef".equals(node.kind())) { + String name = node.attributes().getOrDefault("name", ""); + return name.isBlank() ? Optional.empty() : Optional.of(name); + } + if ("GpuIrLiteral".equals(node.kind())) { + String sourceText = node.attributes().getOrDefault("sourceText", ""); + return sourceText.isBlank() ? Optional.empty() : Optional.of(sourceText); + } + if ("GpuIrUnary".equals(node.kind())) { + String operator = node.attributes().getOrDefault("operator", ""); + Optional operand = childSourceText(node, "operand", nodesById); + if (operator.isBlank() || operand.isEmpty()) { + return Optional.empty(); + } + return Optional.of("(" + operator + operand.orElseThrow() + ")"); + } + if ("GpuIrBinary".equals(node.kind())) { + String operator = node.attributes().getOrDefault("operator", ""); + Optional left = childSourceText(node, "left", nodesById); + Optional right = childSourceText(node, "right", nodesById); + if (operator.isBlank() || left.isEmpty() || right.isEmpty()) { + return Optional.empty(); + } + return Optional.of("(" + left.orElseThrow() + " " + operator + " " + right.orElseThrow() + ")"); + } + if ("GpuIrIntrinsicCall".equals(node.kind())) { + String name = firstAttribute(node, "name", "backendName", "function", "intrinsic"); + if (name.isBlank()) { + return Optional.empty(); + } + ArrayList args = new ArrayList<>(); + for (Integer argumentId : callArguments(node)) { + Optional argumentText = sourceText(nodesById.get(argumentId), nodesById); + if (argumentText.isEmpty()) { + return Optional.empty(); + } + args.add(argumentText.orElseThrow()); + } + String template = node.attributes().getOrDefault("codeTemplate", ""); + return Optional.of("intrinsic(" + name + " template=\"" + template + "\" args=[" + + String.join(", ", args) + "])"); + } + return Optional.empty(); + } + + private static Optional childSourceText( + IrGpuTypedNode node, + String childName, + Map nodesById + ) { + Integer childId = singleChild(node, childName); + if (childId == null) { + return Optional.empty(); + } + return sourceText(nodesById.get(childId), nodesById); + } + + private record Replacement(String body, boolean replaced) { + } + + private static Replacement replaceFirst(String body, String expressionText, String replacementText) { + int index = body.indexOf(expressionText); + if (index < 0) { + return new Replacement(body, false); + } + return new Replacement( + body.substring(0, index) + replacementText + body.substring(index + expressionText.length()), + true + ); + } + + private static IrGpuTypedBody rewriteTypedBody(IrGpuTypedBody typedBody, MixCandidate candidate) { + ArrayList nodes = new ArrayList<>(); + for (IrGpuTypedNode node : typedBody.nodes()) { + if (node.id() == candidate.rootNodeId()) { + nodes.add(replacementNode(node.id(), candidate)); + continue; + } + nodes.add(node); + } + return new IrGpuTypedBody(typedBody.format(), typedBody.rootNodeIds(), nodes); + } + + private static IrGpuTypedNode replacementNode(int nodeId, MixCandidate candidate) { + LinkedHashMap attributes = new LinkedHashMap<>(); + attributes.put("name", TARGET_INTRINSIC); + attributes.put("backendName", TARGET_INTRINSIC); + attributes.put("codeTemplate", ""); + attributes.put("receiver.null", "true"); + attributes.put("resultType", "mix-review"); + attributes.put("argumentTypes.count", "3"); + attributes.put("argumentTypes.0", "mix-review"); + attributes.put("argumentTypes.1", "mix-review"); + attributes.put("argumentTypes.2", "mix-review"); + return new IrGpuTypedNode( + nodeId, + "GpuIrIntrinsicCall", + attributes, + Map.of("arguments", candidate.argumentNodeIds()) + ); + } + + private static Set reachableNodeIds(IrGpuTypedBody typedBody, Map nodesById) { + LinkedHashSet reachable = new LinkedHashSet<>(); + ArrayList pending = new ArrayList<>(typedBody.rootNodeIds()); + while (!pending.isEmpty()) { + int nodeId = pending.remove(pending.size() - 1); + if (!reachable.add(nodeId)) { + continue; + } + IrGpuTypedNode node = nodesById.get(nodeId); + if (node == null) { + continue; + } + for (List childIds : node.children().values()) { + for (Integer childId : childIds) { + if (childId != null && nodesById.containsKey(childId) && !reachable.contains(childId)) { + pending.add(childId); + } + } + } + } + return Set.copyOf(reachable); + } + + private static Integer singleChild(IrGpuTypedNode node, String name) { + if (node == null) { + return null; + } + List ids = node.children().getOrDefault(name, List.of()); + return ids.size() == 1 ? ids.get(0) : null; + } + + private static List callArguments(IrGpuTypedNode node) { + if (node == null) { + return List.of(); + } + List args = node.children().getOrDefault("args", List.of()); + return args.isEmpty() ? node.children().getOrDefault("arguments", List.of()) : args; + } + + private static boolean isCall(IrGpuTypedNode node, String backendName) { + return node != null + && "GpuIrIntrinsicCall".equals(node.kind()) + && backendName.equals(firstAttribute(node, "backendName", "name", "function", "intrinsic")); + } + + private static boolean isBinary(IrGpuTypedNode node, String operator) { + return node != null + && "GpuIrBinary".equals(node.kind()) + && operator.equals(node.attributes().get("operator")); + } + + private static boolean isOne(IrGpuTypedNode node) { + return normalizedLiteralText(node).matches("[+]?1(?:\\.0+)?[fFdD]?"); + } + + private static String normalizedLiteralText(IrGpuTypedNode node) { + if (node == null || !"GpuIrLiteral".equals(node.kind())) { + return ""; + } + String literal = firstAttribute(node, "sourceText", "value", "literal").trim(); + while (literal.startsWith("(") && literal.endsWith(")") && literal.length() > 2) { + literal = literal.substring(1, literal.length() - 1).trim(); + } + return literal; + } + + private static boolean isPureExpression(int nodeId, Map nodesById) { + return isPureExpression(nodeId, nodesById, new LinkedHashSet<>()); + } + + private static boolean isPureExpression( + int nodeId, + Map nodesById, + Set visiting + ) { + if (!visiting.add(nodeId)) { + return false; + } + IrGpuTypedNode node = nodesById.get(nodeId); + if (node == null) { + return false; + } + if ("GpuIrVariableRef".equals(node.kind()) || "GpuIrLiteral".equals(node.kind())) { + return true; + } + if ("GpuIrUnary".equals(node.kind()) || "GpuIrBinary".equals(node.kind()) || "GpuIrIntrinsicCall".equals(node.kind())) { + for (List childIds : node.children().values()) { + for (Integer childId : childIds) { + if (childId == null || !isPureExpression(childId, nodesById, visiting)) { + return false; + } + } + } + return true; + } + return false; + } + + private static String firstAttribute(IrGpuTypedNode node, String... names) { + if (node == null || names == null) { + return ""; + } + for (String name : names) { + String value = node.attributes().get(name); + if (value != null) { + return value; + } + } + return ""; + } + + private static Map nodesById(IrGpuTypedBody typedBody) { + LinkedHashMap nodes = new LinkedHashMap<>(); + for (IrGpuTypedNode node : typedBody.nodes()) { + nodes.put(node.id(), node); + } + return Map.copyOf(nodes); + } + + private static boolean isOpenClReview(GpuIrOptimizationProposalRequest request) { + return "OPENCL".equals(request.contextFields().getOrDefault("backendTarget", "")); + } + + private static IrGpuMethodBody copyWithBodyAndTypedBody( + IrGpuMethodBody methodBody, + String body, + IrGpuTypedBody typedBody + ) { + return new IrGpuMethodBody( + methodBody.role(), + methodBody.name(), + methodBody.emittedName(), + methodBody.format(), + body, + typedBody, + methodBody.bodyIndex(), + methodBody.helperDependencies(), + methodBody.sourceLocation() + ); + } + + private static IrGpuModule copyWithBodies(IrGpuModule module, List methodBodies) { + return new IrGpuModule( + module.entryMethod(), + module.entryEmittedName(), + module.entryOpenClAttributes(), + module.entryAttributeMetadata(), + module.helperMethods(), + module.structs(), + methodBodies + ); + } + + private static IrGpuArtifact copyWithModuleAndRegeneration( + IrGpuArtifact artifact, + IrGpuModule module, + IrGpuRegenerationMetadata regenerationMetadata + ) { + return new IrGpuArtifact( + artifact.header(), + module, + artifact.entryParameters(), + artifact.launchMetadata(), + artifact.validationMetadata(), + artifact.featureMetadata(), + artifact.optimizerPolicyMetadata(), + regenerationMetadata, + artifact.structMetadata(), + artifact.constants(), + artifact.constantData(), + artifact.backendOutputs(), + artifact.runtimeDefaultBackend(), + artifact.runtimeOptimizationProfile(), + artifact.methodDeviceConstraints(), + artifact.methodFallbackVariants(), + artifact.extensionParticipationMetadata() + ); + } + + private static final class RewriteStats { + private int methodBodyCount; + private int typedBodyCount; + private final LinkedHashSet candidateKeys = new LinkedHashSet<>(); + private int transformedNodeCount; + private int changedMethodBodyCount; + private int bodyTextReplacementCount; + private int fixedPointPassCount; + private int canonicalMixCount; + private int expandedMixCount; + private int madExpandedMixCount; + private int skippedTypedBodyMissingCount; + private int skippedUnsupportedFormatCount; + private int skippedFastMathPolicyCount; + private int skippedMissingChildReferenceCount; + private int skippedUnsupportedShapeCount; + private int skippedBodyTextPatternMissingCount; + private final ArrayList transformedCandidates = new ArrayList<>(); + private String firstTransformedNode = "none"; + private String firstExpression = "none"; + private String firstReplacement = "none"; + private String firstBlocker = "none"; + + private int candidateCount() { + return candidateKeys.size(); + } + + private void recordCandidate(MixCandidate candidate) { + candidateKeys.add(candidate.methodName() + "#" + candidate.rootNodeId()); + } + + private void add(RewriteStats other) { + typedBodyCount += other.typedBodyCount; + candidateKeys.addAll(other.candidateKeys); + transformedNodeCount += other.transformedNodeCount; + changedMethodBodyCount += other.changedMethodBodyCount; + bodyTextReplacementCount += other.bodyTextReplacementCount; + fixedPointPassCount += other.fixedPointPassCount; + canonicalMixCount += other.canonicalMixCount; + expandedMixCount += other.expandedMixCount; + madExpandedMixCount += other.madExpandedMixCount; + skippedTypedBodyMissingCount += other.skippedTypedBodyMissingCount; + skippedUnsupportedFormatCount += other.skippedUnsupportedFormatCount; + skippedFastMathPolicyCount += other.skippedFastMathPolicyCount; + skippedMissingChildReferenceCount += other.skippedMissingChildReferenceCount; + skippedUnsupportedShapeCount += other.skippedUnsupportedShapeCount; + skippedBodyTextPatternMissingCount += other.skippedBodyTextPatternMissingCount; + transformedCandidates.addAll(other.transformedCandidates); + if ("none".equals(firstTransformedNode) && !"none".equals(other.firstTransformedNode)) { + firstTransformedNode = other.firstTransformedNode; + firstExpression = other.firstExpression; + firstReplacement = other.firstReplacement; + } + if ("none".equals(firstBlocker) && !"none".equals(other.firstBlocker)) { + firstBlocker = other.firstBlocker; + } + } + + private void setFirstBlocker(String blocker) { + if ("none".equals(firstBlocker) && blocker != null && !blocker.isBlank() && !"none".equals(blocker)) { + firstBlocker = blocker; + } + } + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalManifest.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalManifest.java index e3f9a02e..b179f2ea 100644 --- a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalManifest.java +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalManifest.java @@ -2,6 +2,9 @@ import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.time.Instant; import java.time.format.DateTimeParseException; import java.util.ArrayList; @@ -36,6 +39,9 @@ public static String template( builder.append("formatVersion=1\n"); builder.append("status=pending\n"); builder.append("scope=").append(SCOPE).append('\n'); + builder.append("manifest.resourcePath=") + .append(propertyValue(resourcePathFor(normalizedProposal, normalizedRequest))) + .append('\n'); builder.append("approval.id=").append(REQUIRED).append('\n'); builder.append("approval.approvedBy=").append(REQUIRED).append('\n'); builder.append("approval.approvedAtUtc=").append(REQUIRED).append('\n'); @@ -49,6 +55,21 @@ public static String template( builder.append("binding.deviceLabel=").append(context(normalizedRequest, "deviceProfile.label", "unknown")).append('\n'); builder.append("binding.proof.source=").append(propertyValue(proof.source())).append('\n'); builder.append("binding.proof.verdict=").append(propertyValue(proof.verdict())).append('\n'); + RuntimeEquivalencePayloadBinding payloadBinding = RuntimeEquivalencePayloadBinding.from(proof); + builder.append("binding.runtimeEquivalencePayload.required=") + .append(payloadBinding.required()).append('\n'); + builder.append("binding.runtimeEquivalencePayload.present=") + .append(payloadBinding.present()).append('\n'); + builder.append("binding.runtimeEquivalencePayload.passed=") + .append(payloadBinding.passed()).append('\n'); + builder.append("binding.runtimeEquivalencePayload.componentsComplete=") + .append(payloadBinding.componentsComplete()).append('\n'); + builder.append("binding.runtimeEquivalencePayload.caseCount=") + .append(payloadBinding.caseCount()).append('\n'); + builder.append("binding.runtimeEquivalencePayload.resource=") + .append(propertyValue(payloadBinding.resource())).append('\n'); + builder.append("binding.runtimeEquivalencePayload.comparisonMode=") + .append(propertyValue(payloadBinding.comparisonMode())).append('\n'); builder.append("binding.rollback.required=true\n"); builder.append("authorization.productionMutation=disabled\n"); builder.append("authorization.scope=manual-review-only\n"); @@ -56,6 +77,22 @@ public static String template( return builder.toString(); } + public static String resourceName( + GpuIrOptimizationProposal proposal, + GpuIrOptimizationProposalRequest request + ) { + GpuIrOptimizationProposal normalizedProposal = requireProposalCandidate(proposal); + GpuIrOptimizationProposalRequest normalizedRequest = normalizeRequest(request, normalizedProposal); + return resourceNameFor(normalizedProposal, normalizedRequest); + } + + public static String resourcePath( + GpuIrOptimizationProposal proposal, + GpuIrOptimizationProposalRequest request + ) { + return RESOURCE_DIRECTORY + resourceName(proposal, request); + } + public static Validation validate( GpuIrOptimizationProposal proposal, GpuIrOptimizationProposalRequest request, @@ -78,6 +115,16 @@ public static Validation validate( requireEquals(blockers, normalizedManifest, "formatVersion", "1", "manifest-format-version-mismatch"); requireEquals(blockers, normalizedManifest, "status", "approved", "manifest-not-approved"); requireEquals(blockers, normalizedManifest, "scope", SCOPE, "manifest-scope-mismatch"); + String manifestResourcePath = normalizedProposal == null + ? "missing" + : resourcePathFor(normalizedProposal, normalizedRequest); + requireBindingEquals( + blockers, + normalizedManifest, + "manifest.resourcePath", + manifestResourcePath, + "manifest-resource-path-mismatch" + ); requireApprovalValue(blockers, approvalId, "manifest-approval-id-missing"); requireApprovalValue(blockers, approvedBy, "manifest-approved-by-missing"); requireApprovalValue(blockers, approvedAtUtc, "manifest-approved-at-missing"); @@ -107,6 +154,59 @@ public static Validation validate( requireBindingEquals(blockers, normalizedManifest, "binding.deviceLabel", context(normalizedRequest, "deviceProfile.label", "unknown"), "manifest-device-label-mismatch"); requireBindingEquals(blockers, normalizedManifest, "binding.proof.source", proof.source(), "manifest-proof-source-mismatch"); requireBindingEquals(blockers, normalizedManifest, "binding.proof.verdict", proof.verdict(), "manifest-proof-verdict-mismatch"); + RuntimeEquivalencePayloadBinding payloadBinding = RuntimeEquivalencePayloadBinding.from(proof); + requireBindingEquals( + blockers, + normalizedManifest, + "binding.runtimeEquivalencePayload.required", + Boolean.toString(payloadBinding.required()), + "manifest-runtime-equivalence-payload-required-mismatch" + ); + requireBindingEquals( + blockers, + normalizedManifest, + "binding.runtimeEquivalencePayload.present", + Boolean.toString(payloadBinding.present()), + "manifest-runtime-equivalence-payload-present-mismatch" + ); + requireBindingEquals( + blockers, + normalizedManifest, + "binding.runtimeEquivalencePayload.passed", + Boolean.toString(payloadBinding.passed()), + "manifest-runtime-equivalence-payload-passed-mismatch" + ); + requireBindingEquals( + blockers, + normalizedManifest, + "binding.runtimeEquivalencePayload.componentsComplete", + Boolean.toString(payloadBinding.componentsComplete()), + "manifest-runtime-equivalence-payload-components-mismatch" + ); + requireBindingEquals( + blockers, + normalizedManifest, + "binding.runtimeEquivalencePayload.caseCount", + Integer.toString(payloadBinding.caseCount()), + "manifest-runtime-equivalence-payload-case-count-mismatch" + ); + requireBindingEquals( + blockers, + normalizedManifest, + "binding.runtimeEquivalencePayload.resource", + payloadBinding.resource(), + "manifest-runtime-equivalence-payload-resource-mismatch" + ); + requireBindingEquals( + blockers, + normalizedManifest, + "binding.runtimeEquivalencePayload.comparisonMode", + payloadBinding.comparisonMode(), + "manifest-runtime-equivalence-payload-comparison-mode-mismatch" + ); + if (payloadBinding.required() && !payloadBinding.approvalReady()) { + blockers.add(payloadBinding.firstBlocker()); + } requireEquals(blockers, normalizedManifest, "binding.rollback.required", "true", "manifest-rollback-required-mismatch"); requireEquals(blockers, normalizedManifest, "authorization.productionMutation", "disabled", "manifest-production-mutation-not-disabled"); requireEquals(blockers, normalizedManifest, "authorization.scope", "manual-review-only", "manifest-authorization-scope-mismatch"); @@ -115,6 +215,7 @@ public static Validation validate( return new Validation( valid, valid ? "approved" : "blocked", + manifestResourcePath, approvalId, approvedBy, approvedAtUtc, @@ -128,6 +229,13 @@ public static Validation validate( context(normalizedRequest, "deviceProfile.label", "unknown"), proof.source(), proof.verdict(), + payloadBinding.required(), + payloadBinding.present(), + payloadBinding.passed(), + payloadBinding.componentsComplete(), + payloadBinding.caseCount(), + payloadBinding.resource(), + payloadBinding.comparisonMode(), List.copyOf(blockers) ); } @@ -211,6 +319,61 @@ private static void requireApprovalValue(LinkedHashSet blockers, String } } + private static String resourcePathFor( + GpuIrOptimizationProposal proposal, + GpuIrOptimizationProposalRequest request + ) { + return RESOURCE_DIRECTORY + resourceNameFor(proposal, request); + } + + private static String resourceNameFor( + GpuIrOptimizationProposal proposal, + GpuIrOptimizationProposalRequest request + ) { + return "approval-" + sha256Hex(resourceFingerprint(proposal, request)).substring(0, 24) + ".properties"; + } + + private static String resourceFingerprint( + GpuIrOptimizationProposal proposal, + GpuIrOptimizationProposalRequest request + ) { + RuntimeEquivalencePayloadBinding payloadBinding = RuntimeEquivalencePayloadBinding.from(proposal.proofArtifact()); + return String.join( + "\n", + SCOPE, + propertyValue(proposal.optimizerId()), + propertyValue(proposal.optimizerVersion()), + propertyValue(proposal.originalIdentity()), + propertyValue(proposal.optimizedIdentity()), + context(request, "backendTarget", "UNKNOWN"), + propertyValue(request.optimizerProfile()), + context(request, "deviceProfile.vendor", "unknown"), + context(request, "deviceProfile.label", "unknown"), + propertyValue(proposal.proofArtifact().source()), + propertyValue(proposal.proofArtifact().verdict()), + Boolean.toString(payloadBinding.required()), + Boolean.toString(payloadBinding.present()), + Boolean.toString(payloadBinding.passed()), + Boolean.toString(payloadBinding.componentsComplete()), + Integer.toString(payloadBinding.caseCount()), + propertyValue(payloadBinding.resource()), + propertyValue(payloadBinding.comparisonMode()) + ); + } + + private static String sha256Hex(String value) { + try { + byte[] digest = MessageDigest.getInstance("SHA-256").digest(value.getBytes(StandardCharsets.UTF_8)); + StringBuilder builder = new StringBuilder(digest.length * 2); + for (byte part : digest) { + builder.append(String.format(java.util.Locale.ROOT, "%02x", part & 0xff)); + } + return builder.toString(); + } catch (NoSuchAlgorithmException exception) { + throw new IllegalStateException("SHA-256 is required for optimizer approval manifest resources", exception); + } + } + private static String context(GpuIrOptimizationProposalRequest request, String key, String fallback) { return propertyValue(request.contextFields().getOrDefault(key, fallback)); } @@ -219,6 +382,18 @@ private static String propertyValue(String value) { return normalize(value, "unknown").replace('\\', '/').replace('\r', ' ').replace('\n', ' '); } + private static boolean proofBoolean(Map fields, String key) { + return "true".equalsIgnoreCase(fields.getOrDefault(key, "false")); + } + + private static int proofInt(Map fields, String key) { + try { + return Math.max(0, Integer.parseInt(fields.getOrDefault(key, "0"))); + } catch (NumberFormatException ignored) { + return 0; + } + } + private static String normalize(String value, String fallback) { return value == null || value.isBlank() ? fallback : value; } @@ -226,6 +401,7 @@ private static String normalize(String value, String fallback) { public record Validation( boolean valid, String status, + String manifestResourcePath, String approvalId, String approvedBy, String approvedAtUtc, @@ -239,10 +415,18 @@ public record Validation( String deviceLabel, String proofSource, String proofVerdict, + boolean runtimeEquivalencePayloadRequired, + boolean runtimeEquivalencePayloadPresent, + boolean runtimeEquivalencePayloadPassed, + boolean runtimeEquivalencePayloadComponentsComplete, + int runtimeEquivalencePayloadCaseCount, + String runtimeEquivalencePayloadResource, + String runtimeEquivalencePayloadComparisonMode, List blockers ) { public Validation { status = normalize(status, valid ? "approved" : "blocked"); + manifestResourcePath = normalize(manifestResourcePath, "missing"); approvalId = normalize(approvalId, "approval:missing"); approvedBy = normalize(approvedBy, "missing"); approvedAtUtc = normalize(approvedAtUtc, "missing"); @@ -256,6 +440,9 @@ public record Validation( deviceLabel = normalize(deviceLabel, "unknown"); proofSource = normalize(proofSource, "missing"); proofVerdict = normalize(proofVerdict, "missing"); + runtimeEquivalencePayloadCaseCount = Math.max(0, runtimeEquivalencePayloadCaseCount); + runtimeEquivalencePayloadResource = normalize(runtimeEquivalencePayloadResource, "not-required"); + runtimeEquivalencePayloadComparisonMode = normalize(runtimeEquivalencePayloadComparisonMode, "not-required"); blockers = blockers == null ? List.of() : List.copyOf(blockers); if (valid && !blockers.isEmpty()) { throw new IllegalArgumentException("Valid optimizer approval manifest must not contain blockers"); @@ -272,6 +459,7 @@ public String toPropertiesText() { builder.append("status=").append(status).append('\n'); builder.append("valid=").append(valid).append('\n'); builder.append("scope=").append(SCOPE).append("-validation\n"); + builder.append("manifest.resourcePath=").append(propertyValue(manifestResourcePath)).append('\n'); builder.append("approval.id=").append(propertyValue(approvalId)).append('\n'); builder.append("approval.approvedBy=").append(propertyValue(approvedBy)).append('\n'); builder.append("approval.approvedAtUtc=").append(propertyValue(approvedAtUtc)).append('\n'); @@ -285,6 +473,16 @@ public String toPropertiesText() { builder.append("binding.deviceLabel=").append(propertyValue(deviceLabel)).append('\n'); builder.append("binding.proof.source=").append(propertyValue(proofSource)).append('\n'); builder.append("binding.proof.verdict=").append(propertyValue(proofVerdict)).append('\n'); + builder.append("binding.runtimeEquivalencePayload.required=").append(runtimeEquivalencePayloadRequired).append('\n'); + builder.append("binding.runtimeEquivalencePayload.present=").append(runtimeEquivalencePayloadPresent).append('\n'); + builder.append("binding.runtimeEquivalencePayload.passed=").append(runtimeEquivalencePayloadPassed).append('\n'); + builder.append("binding.runtimeEquivalencePayload.componentsComplete=") + .append(runtimeEquivalencePayloadComponentsComplete).append('\n'); + builder.append("binding.runtimeEquivalencePayload.caseCount=").append(runtimeEquivalencePayloadCaseCount).append('\n'); + builder.append("binding.runtimeEquivalencePayload.resource=") + .append(propertyValue(runtimeEquivalencePayloadResource)).append('\n'); + builder.append("binding.runtimeEquivalencePayload.comparisonMode=") + .append(propertyValue(runtimeEquivalencePayloadComparisonMode)).append('\n'); builder.append("authorization.productionMutation=disabled\n"); builder.append("authorization.scope=manual-review-only\n"); builder.append("blocker.count=").append(blockers.size()).append('\n'); @@ -297,4 +495,60 @@ public String toPropertiesText() { return builder.toString(); } } + + private record RuntimeEquivalencePayloadBinding( + boolean required, + boolean present, + boolean passed, + boolean componentsComplete, + int caseCount, + String resource, + String comparisonMode + ) { + private static RuntimeEquivalencePayloadBinding from(GpuRuntimeIrOptimizationProofArtifact proof) { + Map fields = proof == null ? Map.of() : proof.fields(); + boolean required = proofBoolean(fields, "proof.runtimeEquivalencePayloadRequiredBeforeSelection") + || proofBoolean(fields, "runtimeEquivalencePayload.required"); + boolean present = proofBoolean(fields, "runtimeEquivalencePayload.present"); + boolean passed = proofBoolean(fields, "runtimeEquivalencePayload.passed"); + boolean componentsComplete = proofBoolean(fields, "runtimeEquivalencePayload.cpuReference.present") + && proofBoolean(fields, "runtimeEquivalencePayload.preOptimizationOutput.present") + && proofBoolean(fields, "runtimeEquivalencePayload.postOptimizationOutput.present") + && proofBoolean(fields, "runtimeEquivalencePayload.tolerance.present") + && proofBoolean(fields, "runtimeEquivalencePayload.failureFixture.present"); + int caseCount = proofInt(fields, "runtimeEquivalencePayload.Case.Count"); + return new RuntimeEquivalencePayloadBinding( + required, + present, + passed, + componentsComplete, + caseCount, + required ? fields.getOrDefault("runtimeEquivalencePayload.resource", "missing") : "not-required", + required ? fields.getOrDefault("runtimeEquivalencePayload.comparisonMode", "missing") : "not-required" + ); + } + + private boolean approvalReady() { + return !required || (present && passed && componentsComplete && caseCount > 0); + } + + private String firstBlocker() { + if (!required) { + return "none"; + } + if (!present) { + return "runtime-equivalence-payload-missing"; + } + if (!passed) { + return "runtime-equivalence-payload-not-passed"; + } + if (!componentsComplete) { + return "runtime-equivalence-payload-components-incomplete"; + } + if (caseCount <= 0) { + return "runtime-equivalence-payload-cases-missing"; + } + return "none"; + } + } } diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalManifestLoader.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalManifestLoader.java new file mode 100644 index 00000000..fca0807f --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalManifestLoader.java @@ -0,0 +1,180 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; + +/** + * Loads review-only optimizer approval manifests from the classpath and validates them against a proposal. + */ +public final class GpuIrOptimizationApprovalManifestLoader { + + private GpuIrOptimizationApprovalManifestLoader() { + } + + public static Result loadAndValidate( + GpuIrOptimizationProposal proposal, + GpuIrOptimizationProposalRequest request + ) { + return loadAndValidate(proposal, request, Thread.currentThread().getContextClassLoader()); + } + + public static Result loadAndValidate( + GpuIrOptimizationProposal proposal, + GpuIrOptimizationProposalRequest request, + ClassLoader preferredClassLoader + ) { + String resourcePath; + try { + resourcePath = GpuIrOptimizationApprovalManifest.resourcePath(proposal, request); + } catch (RuntimeException exception) { + return Result.blocked("missing", "approval-template-not-applicable", 0); + } + + List resources; + try { + resources = resources(resourcePath, preferredClassLoader); + } catch (IOException exception) { + return Result.blocked(resourcePath, "approval-manifest-load-failed", 0); + } + if (resources.isEmpty()) { + return Result.missing(resourcePath); + } + if (resources.size() > 1) { + return Result.blocked(resourcePath, "approval-manifest-resource-ambiguous", resources.size()); + } + + Properties properties = new Properties(); + try (InputStream inputStream = resources.get(0).openStream()) { + properties.load(inputStream); + } catch (IOException exception) { + return Result.blocked(resourcePath, "approval-manifest-load-failed", resources.size()); + } + + GpuIrOptimizationApprovalManifest.Validation validation = + GpuIrOptimizationApprovalManifest.validate(proposal, request, properties); + return Result.fromValidation(resourcePath, resources.size(), validation); + } + + private static List resources(String resourcePath, ClassLoader preferredClassLoader) throws IOException { + LinkedHashSet classLoaders = new LinkedHashSet<>(); + if (preferredClassLoader != null) { + classLoaders.add(preferredClassLoader); + } + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + if (contextClassLoader != null) { + classLoaders.add(contextClassLoader); + } + ClassLoader ownClassLoader = GpuIrOptimizationApprovalManifestLoader.class.getClassLoader(); + if (ownClassLoader != null) { + classLoaders.add(ownClassLoader); + } + + LinkedHashSet resources = new LinkedHashSet<>(); + for (ClassLoader classLoader : classLoaders) { + Enumeration found = classLoader.getResources(resourcePath); + while (found.hasMoreElements()) { + resources.add(found.nextElement()); + } + } + return List.copyOf(resources); + } + + public record Result( + String status, + boolean required, + boolean present, + boolean accepted, + String resourcePath, + int resourceCount, + String firstBlocker, + Optional validation + ) { + public Result { + status = normalize(status, accepted ? "accepted" : "pending-manifest-validation"); + resourcePath = normalize(resourcePath, "missing"); + resourceCount = Math.max(0, resourceCount); + firstBlocker = normalize(firstBlocker, accepted ? "none" : "approval-manifest-not-loaded"); + validation = validation == null ? Optional.empty() : validation; + } + + private static Result missing(String resourcePath) { + return new Result( + "pending-manifest-validation", + true, + false, + false, + resourcePath, + 0, + "approval-manifest-not-loaded", + Optional.empty() + ); + } + + private static Result blocked(String resourcePath, String firstBlocker, int resourceCount) { + return new Result( + "blocked", + true, + resourceCount > 0, + false, + resourcePath, + resourceCount, + firstBlocker, + Optional.empty() + ); + } + + private static Result fromValidation( + String resourcePath, + int resourceCount, + GpuIrOptimizationApprovalManifest.Validation validation + ) { + boolean accepted = validation != null && validation.valid(); + return new Result( + accepted ? "accepted" : "blocked", + true, + true, + accepted, + resourcePath, + resourceCount, + validation == null ? "approval-manifest-validation-missing" : validation.firstBlocker(), + Optional.ofNullable(validation) + ); + } + + public Map fields() { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("status", status); + fields.put("required", Boolean.toString(required)); + fields.put("present", Boolean.toString(present)); + fields.put("accepted", Boolean.toString(accepted)); + fields.put("resourcePath", resourcePath); + fields.put("resource.count", Integer.toString(resourceCount)); + fields.put("firstBlocker", firstBlocker); + fields.put("manualReviewOnly", "true"); + fields.put("productionMutation", "disabled"); + fields.put("selectedIrReplacement", "disabled"); + validation.ifPresent(value -> { + fields.put("validation.valid", Boolean.toString(value.valid())); + fields.put("validation.status", value.status()); + fields.put("validation.blocker.count", Integer.toString(value.blockers().size())); + for (int index = 0; index < value.blockers().size(); index++) { + fields.put("validation.blocker." + index, value.blockers().get(index)); + } + }); + return Map.copyOf(fields); + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalTemplateFormatter.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalTemplateFormatter.java index 90007a8f..1413fa53 100644 --- a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalTemplateFormatter.java +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalTemplateFormatter.java @@ -28,6 +28,7 @@ public static GpuIrOptimizationApprovalTemplateResult format( String template = GpuIrOptimizationApprovalManifest.template(proposal, request); fields.put("diagnostic", "optimizer approval template is pending manual review"); fields.put("resourceDirectory", GpuIrOptimizationApprovalManifest.RESOURCE_DIRECTORY); + fields.put("resourcePath", GpuIrOptimizationApprovalManifest.resourcePath(proposal, request)); return GpuIrOptimizationApprovalTemplateResult.pending(template, fields); } catch (IllegalArgumentException | IllegalStateException failure) { String blocker = firstBlocker(failure); @@ -43,11 +44,56 @@ private static LinkedHashMap baseFields(GpuIrOptimizationProposa fields.put("optimizerVersion", proposal == null ? "missing" : proposal.optimizerVersion()); fields.put("decision", proposal == null ? "missing" : proposal.decision().name()); fields.put("hasOptimizedArtifact", Boolean.toString(proposal != null && proposal.hasOptimizedArtifact())); + Map proofFields = proposal == null ? Map.of() : proposal.proofArtifact().fields(); + boolean runtimeEquivalencePayloadRequired = fieldIsTrue( + proofFields, + "proof.runtimeEquivalencePayloadRequiredBeforeSelection" + ) || fieldIsTrue(proofFields, "runtimeEquivalencePayload.required"); + boolean runtimeEquivalencePayloadComponentsComplete = fieldIsTrue( + proofFields, + "runtimeEquivalencePayload.cpuReference.present" + ) && fieldIsTrue(proofFields, "runtimeEquivalencePayload.preOptimizationOutput.present") + && fieldIsTrue(proofFields, "runtimeEquivalencePayload.postOptimizationOutput.present") + && fieldIsTrue(proofFields, "runtimeEquivalencePayload.tolerance.present") + && fieldIsTrue(proofFields, "runtimeEquivalencePayload.failureFixture.present"); + fields.put("runtimeEquivalencePayload.required", Boolean.toString(runtimeEquivalencePayloadRequired)); + fields.put( + "runtimeEquivalencePayload.present", + Boolean.toString(fieldIsTrue(proofFields, "runtimeEquivalencePayload.present")) + ); + fields.put( + "runtimeEquivalencePayload.passed", + Boolean.toString(fieldIsTrue(proofFields, "runtimeEquivalencePayload.passed")) + ); + fields.put( + "runtimeEquivalencePayload.componentsComplete", + Boolean.toString(runtimeEquivalencePayloadComponentsComplete) + ); + fields.put( + "runtimeEquivalencePayload.caseCount", + proofFields.getOrDefault("runtimeEquivalencePayload.Case.Count", "0") + ); + fields.put( + "runtimeEquivalencePayload.resource", + runtimeEquivalencePayloadRequired + ? proofFields.getOrDefault("runtimeEquivalencePayload.resource", "missing") + : "not-required" + ); + fields.put( + "runtimeEquivalencePayload.comparisonMode", + runtimeEquivalencePayloadRequired + ? proofFields.getOrDefault("runtimeEquivalencePayload.comparisonMode", "missing") + : "not-required" + ); fields.put("productionMutation", "disabled"); fields.put("manualReviewOnly", "true"); return fields; } + private static boolean fieldIsTrue(Map fields, String key) { + return "true".equalsIgnoreCase(fields.getOrDefault(key, "false")); + } + private static String firstBlocker(Throwable failure) { String message = failure == null ? "unknown" : failure.getMessage(); if (message == null || message.isBlank()) { diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalTemplateResult.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalTemplateResult.java index 039c0c9f..da5d2e4d 100644 --- a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalTemplateResult.java +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalTemplateResult.java @@ -61,6 +61,10 @@ public String toPropertiesText() { values.put("template.present", "true"); values.put("template.scope", GpuIrOptimizationApprovalManifest.SCOPE); values.put("template.resourceDirectory", GpuIrOptimizationApprovalManifest.RESOURCE_DIRECTORY); + String resourcePath = fields.get("resourcePath"); + if (resourcePath != null && !resourcePath.isBlank()) { + values.put("template.resourcePath", resourcePath); + } } else { values.put("template.present", "false"); } diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePass.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePass.java index b66ec51f..6599b699 100644 --- a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePass.java +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePass.java @@ -63,23 +63,33 @@ public GpuRuntimeIrOptimizationReport run(GpuRuntimeIrOptimizationRequest reques } IrGpuArtifact selected = current.orElseThrow(); + IrGpuArtifact reviewWorkingArtifact = selected; + Optional candidateArtifact = Optional.empty(); ArrayList passReports = new ArrayList<>(); for (GpuIrOptimizationProposalProvider provider : providers) { GpuIrOptimizationProposalRequest proposalRequest = new GpuIrOptimizationProposalRequest( - selected, + reviewWorkingArtifact, request.compileRequest().options().optimizationProfile(), mutationAllowed, contextFields(request, mutationAllowed) ); GpuIrOptimizationSandwichReport sandwichReport = sandwichRunner.run(proposalRequest, provider); - passReports.add(toPassReport(provider, sandwichReport).withStage(stage())); - selected = sandwichReport.selectedArtifact(); + passReports.add(toPassReport(provider, sandwichReport, proposalRequest).withStage(stage())); + Optional selectedCandidateArtifact = selectedCandidateArtifact(sandwichReport); + if (selectedCandidateArtifact.isPresent()) { + candidateArtifact = selectedCandidateArtifact; + reviewWorkingArtifact = selectedCandidateArtifact.orElseThrow(); + } + if (mutationAllowed) { + selected = sandwichReport.selectedArtifact(); + reviewWorkingArtifact = selected; + } if (sandwichReport.requiresRollback() || sandwichReport.status() == GpuIrOptimizationSandwichStatus.ORIGINAL_INVALID) { break; } } - return new GpuRuntimeIrOptimizationReport(Optional.of(selected), passReports); + return new GpuRuntimeIrOptimizationReport(Optional.of(selected), candidateArtifact, passReports); } @Override @@ -108,7 +118,8 @@ public List proposalProviders() { private static GpuRuntimeIrOptimizationPassReport toPassReport( GpuIrOptimizationProposalProvider provider, - GpuIrOptimizationSandwichReport report + GpuIrOptimizationSandwichReport report, + GpuIrOptimizationProposalRequest request ) { String originalIdentity = IrGpuArtifactIdentity.stableIdentity(report.originalArtifact()); String selectedIdentity = IrGpuArtifactIdentity.stableIdentity(report.selectedArtifact()); @@ -117,6 +128,7 @@ private static GpuRuntimeIrOptimizationPassReport toPassReport( GpuRuntimeIrOptimizationProofArtifact proofArtifact = proposal .map(GpuIrOptimizationProposal::proofArtifact) .orElseGet(() -> proof(report.status().name().toLowerCase(java.util.Locale.ROOT), Map.of())); + proofArtifact = withApprovalTemplateFields(proofArtifact, proposal, request, provider.getClass().getClassLoader()); proofArtifact = withCandidateFields(proofArtifact, report); return switch (report.status()) { @@ -201,6 +213,44 @@ private static GpuRuntimeIrOptimizationProofArtifact withCandidateFields( ); } + private static GpuRuntimeIrOptimizationProofArtifact withApprovalTemplateFields( + GpuRuntimeIrOptimizationProofArtifact proofArtifact, + Optional proposal, + GpuIrOptimizationProposalRequest request, + ClassLoader providerClassLoader + ) { + if (proposal.isEmpty()) { + return proofArtifact; + } + GpuIrOptimizationApprovalTemplateResult approvalTemplate = + GpuIrOptimizationApprovalTemplateFormatter.format(proposal.orElseThrow(), request); + GpuIrOptimizationApprovalManifestLoader.Result approvalManifest = + GpuIrOptimizationApprovalManifestLoader.loadAndValidate( + proposal.orElseThrow(), + request, + providerClassLoader + ); + LinkedHashMap fields = new LinkedHashMap<>(proofArtifact.fields()); + fields.put("approvalTemplate.status", approvalTemplate.status()); + fields.put("approvalTemplate.applicable", Boolean.toString(approvalTemplate.applicable())); + fields.put("approvalTemplate.firstBlocker", approvalTemplate.firstBlocker()); + approvalTemplate.fields().forEach((key, value) -> fields.put("approvalTemplate." + key, value)); + approvalManifest.fields().forEach((key, value) -> fields.put("approvalManifest." + key, value)); + return GpuRuntimeIrOptimizationProofArtifact.fromFields( + proofArtifact.source(), + proofArtifact.verdict(), + fields + ); + } + + private static Optional selectedCandidateArtifact(GpuIrOptimizationSandwichReport report) { + if (report.status() != GpuIrOptimizationSandwichStatus.PROPOSAL_ONLY + && report.status() != GpuIrOptimizationSandwichStatus.OPTIMIZED_SELECTED) { + return Optional.empty(); + } + return report.proposal().flatMap(GpuIrOptimizationProposal::optimizedArtifact); + } + private static Map contextFields(GpuRuntimeIrOptimizationRequest request, boolean mutationAllowed) { LinkedHashMap fields = new LinkedHashMap<>(); fields.put("backendTarget", request.compileRequest().options().backendTarget().name()); @@ -212,6 +262,10 @@ private static Map contextFields(GpuRuntimeIrOptimizationRequest fields.put("deviceProfile.driverVersion", request.compileRequest().deviceProfile().driverVersion()); fields.put("deviceProfile.apiVersionText", request.compileRequest().deviceProfile().apiVersionText()); fields.put("deviceProfile.deviceClass", request.compileRequest().deviceProfile().deviceClass().name()); + fields.put("fastMathAllowed", Boolean.toString(request.fastMathEnabled())); + fields.put(GpuIrOptimizationPolicy.FAST_MATH_ALLOWED_FIELD, Boolean.toString(request.fastMathEnabled())); + fields.put("optimizerPolicy.fastMath", Boolean.toString(request.fastMathEnabled())); + fields.put("optimizerPolicy.source", request.optimizerPolicy().source()); fields.put("mutationAllowed", Boolean.toString(mutationAllowed)); return Map.copyOf(fields); } diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrSafeLocalCseMaterializationProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrSafeLocalCseMaterializationProposalProvider.java new file mode 100644 index 00000000..5758bfce --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrSafeLocalCseMaterializationProposalProvider.java @@ -0,0 +1,944 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuRegenerationMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; +import net.sixik.ga_utils.javatogpu.runtime.opencl.OpenClIrGpuSourceEmission; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * Review-only local CSE materialization that reuses an already materialized local value. + */ +public final class GpuIrSafeLocalCseMaterializationProposalProvider implements GpuIrOptimizationProposalProvider { + + public static final String PROVIDER_ID = GpuIrOptimizerModule.MODULE_ID + ".safe-local-cse-materialization"; + public static final String PROVIDER_VERSION = PROVIDER_ID + ":1"; + + private static final Pattern SIMPLE_LOCAL_NAME = Pattern.compile("[A-Za-z_$][A-Za-z0-9_$]*"); + private static final List SUPPORTED_BINARY_OPERATORS = List.of( + "+", "-", "*", "&", "|", "^", "==", "!=", "<", "<=", ">", ">=" + ); + + @Override + public GpuIrOptimizationProposal propose(GpuIrOptimizationProposalRequest request) { + IrGpuArtifact original = request.originalArtifact(); + ArtifactRewrite rewrite = ArtifactRewrite.from(original, isOpenClReview(request), request.mutationAllowed()); + if (!rewrite.changed()) { + return new GpuIrOptimizationProposal( + extensionId(), + extensionVersion(), + original, + Optional.empty(), + GpuIrOptimizationProposalDecision.NO_CHANGE, + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.safe-local-cse-materialization", + rewrite.verdict(), + rewrite.fields(request) + ), + "", + List.of(rewrite.diagnostic()) + ); + } + + IrGpuArtifact optimized = copyWithModuleAndRegeneration( + original, + rewrite.module().orElseThrow(), + rewrite.regenerationMetadata(original) + ); + return GpuIrOptimizationProposal.proposed( + extensionId(), + extensionVersion(), + original, + optimized, + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.safe-local-cse-materialization", + "review-only-safe-local-cse-materialized", + rewrite.fields(request) + ), + List.of("materialized " + rewrite.transformedNodeCount() + + " safe local CSE rewrite(s) into a review candidate") + ); + } + + @Override + public String extensionId() { + return PROVIDER_ID; + } + + @Override + public String extensionVersion() { + return PROVIDER_VERSION; + } + + @Override + public int extensionOrder() { + return 415; + } + + private record ArtifactRewrite( + Optional module, + int methodBodyCount, + int typedBodyCount, + int localBindingCount, + int candidateCount, + int transformedNodeCount, + int changedMethodBodyCount, + int bodyTextReplacementCount, + int fixedPointPassCount, + int skippedTypedBodyMissingCount, + int skippedUnsupportedFormatCount, + int skippedControlFlowBoundaryCount, + int skippedMissingChildReferenceCount, + int skippedUnsupportedOperatorCount, + int skippedImpureOperandCount, + int skippedNoExistingLocalBindingCount, + int skippedLocalInvalidationCount, + int skippedBindingTextPatternMissingCount, + int skippedBodyTextPatternMissingCount, + List transformedCandidates, + String firstTransformedNode, + String firstExpression, + String firstReplacement, + String firstBlocker, + boolean openClReviewSourceReady, + String openClReviewSourceLength, + boolean mutationAllowed + ) { + + private static ArtifactRewrite from(IrGpuArtifact artifact, boolean openClReview, boolean mutationAllowed) { + ArrayList rewrittenBodies = new ArrayList<>(); + RewriteStats stats = new RewriteStats(); + stats.methodBodyCount = artifact.module().methodBodies().size(); + + for (IrGpuMethodBody methodBody : artifact.module().methodBodies()) { + MethodRewrite methodRewrite = rewriteMethodBody(methodBody); + rewrittenBodies.add(methodRewrite.methodBody()); + stats.add(methodRewrite.stats()); + } + + if (stats.transformedNodeCount == 0) { + return noChange(stats, firstBlocker(stats), mutationAllowed); + } + + IrGpuModule rewrittenModule = copyWithBodies(artifact.module(), rewrittenBodies); + if (openClReview) { + IrGpuArtifact optimized = copyWithModuleAndRegeneration( + artifact, + rewrittenModule, + IrGpuRegenerationMetadata.backendNeutralReady() + ); + OpenClIrGpuSourceEmission emission = OpenClIrGpuSourceEmission.inspect(optimized); + if (!emission.sourceGenerated()) { + String blocker = emission.blockers().isEmpty() + ? "opencl-review-source-emission-blocked" + : emission.blockers().get(0); + return noChange(stats, blocker, mutationAllowed); + } + return changed(stats, rewrittenModule, true, Integer.toString(emission.source().length()), mutationAllowed); + } + return changed(stats, rewrittenModule, false, "not-requested", mutationAllowed); + } + + private static ArtifactRewrite changed( + RewriteStats stats, + IrGpuModule module, + boolean openClReviewSourceReady, + String openClReviewSourceLength, + boolean mutationAllowed + ) { + return new ArtifactRewrite( + Optional.of(module), + stats.methodBodyCount, + stats.typedBodyCount, + stats.localBindingCount, + stats.candidateCount, + stats.transformedNodeCount, + stats.changedMethodBodyCount, + stats.bodyTextReplacementCount, + stats.fixedPointPassCount, + stats.skippedTypedBodyMissingCount, + stats.skippedUnsupportedFormatCount, + stats.skippedControlFlowBoundaryCount, + stats.skippedMissingChildReferenceCount, + stats.skippedUnsupportedOperatorCount, + stats.skippedImpureOperandCount, + stats.skippedNoExistingLocalBindingCount, + stats.skippedLocalInvalidationCount, + stats.skippedBindingTextPatternMissingCount, + stats.skippedBodyTextPatternMissingCount, + List.copyOf(stats.transformedCandidates), + stats.firstTransformedNode, + stats.firstExpression, + stats.firstReplacement, + "none", + openClReviewSourceReady, + openClReviewSourceLength, + mutationAllowed + ); + } + + private static ArtifactRewrite noChange(RewriteStats stats, String firstBlocker, boolean mutationAllowed) { + return new ArtifactRewrite( + Optional.empty(), + stats.methodBodyCount, + stats.typedBodyCount, + stats.localBindingCount, + stats.candidateCount, + stats.transformedNodeCount, + stats.changedMethodBodyCount, + stats.bodyTextReplacementCount, + stats.fixedPointPassCount, + stats.skippedTypedBodyMissingCount, + stats.skippedUnsupportedFormatCount, + stats.skippedControlFlowBoundaryCount, + stats.skippedMissingChildReferenceCount, + stats.skippedUnsupportedOperatorCount, + stats.skippedImpureOperandCount, + stats.skippedNoExistingLocalBindingCount, + stats.skippedLocalInvalidationCount, + stats.skippedBindingTextPatternMissingCount, + stats.skippedBodyTextPatternMissingCount, + List.copyOf(stats.transformedCandidates), + stats.firstTransformedNode, + stats.firstExpression, + stats.firstReplacement, + firstBlocker, + false, + "not-ready", + mutationAllowed + ); + } + + private boolean changed() { + return module.isPresent() && transformedNodeCount > 0; + } + + private IrGpuRegenerationMetadata regenerationMetadata(IrGpuArtifact original) { + return openClReviewSourceReady + ? IrGpuRegenerationMetadata.backendNeutralReady() + : original.regenerationMetadata(); + } + + private String verdict() { + return changed() ? "materialized-review-candidate" : "materialization-no-change"; + } + + private String diagnostic() { + if (changed()) { + return "safe local CSE materialization rewrote " + transformedNodeCount + + " repeated pure expression(s) to existing local references"; + } + return "safe local CSE materialization produced no review candidate: " + firstBlocker; + } + + private Map fields(GpuIrOptimizationProposalRequest request) { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("methodBody.count", Integer.toString(methodBodyCount)); + fields.put("methodBody.rewriteScope", "all-method-bodies"); + fields.put("typedBody.count", Integer.toString(typedBodyCount)); + fields.put("localBinding.count", Integer.toString(localBindingCount)); + fields.put("candidate.count", Integer.toString(candidateCount)); + fields.put("transformedNode.count", Integer.toString(transformedNodeCount)); + fields.put("changedMethodBody.count", Integer.toString(changedMethodBodyCount)); + fields.put("bodyTextReplacement.count", Integer.toString(bodyTextReplacementCount)); + fields.put("fixedPoint.enabled", "true"); + fields.put("fixedPoint.pass.count", Integer.toString(fixedPointPassCount)); + fields.put("fixedPoint.reachableNodeScan", "true"); + fields.put("fixedPoint.rewriteGranularity", "single-reusable-expression-per-pass"); + fields.put("skipped.typedBodyMissing.count", Integer.toString(skippedTypedBodyMissingCount)); + fields.put("skipped.unsupportedFormat.count", Integer.toString(skippedUnsupportedFormatCount)); + fields.put("skipped.controlFlowBoundary.count", Integer.toString(skippedControlFlowBoundaryCount)); + fields.put("skipped.missingChildReference.count", Integer.toString(skippedMissingChildReferenceCount)); + fields.put("skipped.unsupportedOperator.count", Integer.toString(skippedUnsupportedOperatorCount)); + fields.put("skipped.impureOperand.count", Integer.toString(skippedImpureOperandCount)); + fields.put("skipped.noExistingLocalBinding.count", Integer.toString(skippedNoExistingLocalBindingCount)); + fields.put("skipped.localInvalidation.count", Integer.toString(skippedLocalInvalidationCount)); + fields.put("skipped.bindingTextPatternMissing.count", Integer.toString(skippedBindingTextPatternMissingCount)); + fields.put("skipped.bodyTextPatternMissing.count", Integer.toString(skippedBodyTextPatternMissingCount)); + fields.put("rewrite.proposed", Boolean.toString(changed())); + fields.put("rewrite.materialized", Boolean.toString(changed())); + fields.put("optimizerFamily", "safe-local-cse-materialization"); + fields.put("mutationRequired", "false"); + fields.put("productionAffecting", "false"); + fields.put("previewOnly", "false"); + fields.put("provider.mutatesOriginal", "false"); + fields.put("policy.mutationAllowed", Boolean.toString(mutationAllowed)); + fields.put("policy.proposalOnly", Boolean.toString(!mutationAllowed)); + fields.put("policy.rollbackRequired", Boolean.toString(request.policy().rollbackRequired())); + fields.put("policy.proofRequired", Boolean.toString(request.policy().proofRequired())); + fields.put("proof.runtimeEquivalenceRequiredBeforeSelection", Boolean.toString(changed())); + fields.put("proof.runtimeEquivalencePayloadRequiredBeforeSelection", Boolean.toString(changed())); + fields.put("proof.approvalRequiredBeforeProduction", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.required", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.status", changed() ? "recorded" : "not-required"); + fields.put("runtimeEquivalencePayload.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.passed", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.firstBlocker", changed() ? "none" : firstBlocker); + fields.put( + "runtimeEquivalencePayload.comparisonMode", + "optimizer-family:safe-local-cse-materialization:review-candidate" + ); + fields.put( + "runtimeEquivalencePayload.resource", + "ir-optimizer://safe-local-cse-materialization/review-candidate" + ); + fields.put("runtimeEquivalencePayload.cpuReference.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.preOptimizationOutput.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.postOptimizationOutput.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.tolerance.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.failureFixture.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.CpuReference", "reuseExistingLocal=" + transformedNodeCount); + fields.put("runtimeEquivalencePayload.PreOptimizationOutput", "expressions=" + transformedNodeCount + + ", first=" + firstExpression); + fields.put("runtimeEquivalencePayload.PostOptimizationOutput", "localRefs=" + transformedNodeCount + + ", first=" + firstReplacement); + fields.put("runtimeEquivalencePayload.Tolerance", "mode=exact-expression-reuse, floatingPoint=unchanged"); + fields.put("runtimeEquivalencePayload.FailureFixture", "none"); + fields.put("runtimeEquivalencePayload.ReferenceMode", "static-existing-local-expression-reuse"); + fields.put("runtimeEquivalencePayload.CaseIdentity", "method-name-and-node-id"); + appendRuntimeEquivalenceCases(fields, transformedCandidates); + fields.put("reviewPackage.required", Boolean.toString(changed())); + fields.put("reviewPackage.firstBlocker", changed() ? "approval-pending" : firstBlocker); + fields.put("safety.scope", "straight-line-existing-local-pure-binary-expression-reuse"); + fields.put("safety.dominanceProven", Boolean.toString(changed())); + fields.put("safety.sideEffectFreedomProven", Boolean.toString(changed())); + fields.put("safety.valueNumberingScope", "method-local-straight-line-existing-local"); + fields.put("safety.newTemporaryIntroduced", "false"); + fields.put("openClReview.sourceReady", Boolean.toString(openClReviewSourceReady)); + fields.put("openClReview.sourceLength", openClReviewSourceLength); + fields.put("firstTransformedNode", firstTransformedNode); + fields.put("firstExpression", firstExpression); + fields.put("firstReplacement", firstReplacement); + fields.put("firstBlocker", changed() ? "none" : firstBlocker); + return Map.copyOf(fields); + } + + private static String firstBlocker(RewriteStats stats) { + if (!"none".equals(stats.firstBlocker)) { + return stats.firstBlocker; + } + if (stats.typedBodyCount == 0) { + return "typed-body-missing"; + } + if (stats.localBindingCount == 0) { + return "no-existing-local-expression-binding"; + } + return "no-repeated-expression-after-existing-local-binding"; + } + } + + private record MethodRewrite(IrGpuMethodBody methodBody, RewriteStats stats) { + } + + private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody) { + RewriteStats stats = new RewriteStats(); + if (!methodBody.typedBody().available()) { + stats.skippedTypedBodyMissingCount++; + stats.setFirstBlocker("typed-body-missing"); + return new MethodRewrite(methodBody, stats); + } + stats.typedBodyCount = 1; + if (!"ir-text-v1".equals(methodBody.format())) { + stats.skippedUnsupportedFormatCount++; + stats.setFirstBlocker("method-body-format-unsupported-" + methodBody.format()); + return new MethodRewrite(methodBody, stats); + } + if (hasControlFlowBoundary(methodBody.typedBody())) { + stats.skippedControlFlowBoundaryCount++; + stats.setFirstBlocker("control-flow-boundary-present"); + return new MethodRewrite(methodBody, stats); + } + + IrGpuMethodBody rewrittenMethodBody = methodBody; + int maxPasses = Math.max(1, methodBody.typedBody().nodes().size()); + for (int pass = 0; pass < maxPasses; pass++) { + Map nodesById = nodesById(rewrittenMethodBody.typedBody()); + Reachability reachability = reachableNodeIds(rewrittenMethodBody.typedBody(), nodesById); + if (reachability.rootMissingCount() > 0 || reachability.missingChildReferenceCount() > 0) { + stats.skippedMissingChildReferenceCount += reachability.rootMissingCount() + + reachability.missingChildReferenceCount(); + stats.setFirstBlocker("missing-child-reference"); + return new MethodRewrite(rewrittenMethodBody, stats); + } + + RewriteStats scanStats = new RewriteStats(); + ScanResult scanResult = scanMethodBody( + rewrittenMethodBody.name(), + rewrittenMethodBody.body(), + rewrittenMethodBody.typedBody(), + nodesById, + reachability.reachableNodeIds(), + scanStats + ); + if (scanResult.candidate().isEmpty()) { + if (stats.transformedNodeCount == 0) { + stats.add(scanStats); + } + break; + } + + CseCandidate candidate = scanResult.candidate().orElseThrow(); + Replacement replacement = replaceAfter( + rewrittenMethodBody.body(), + candidate.expressionText(), + candidate.replacementText(), + candidate.bindingSearchStart() + ); + stats.add(scanStats); + if (!replacement.replaced()) { + stats.candidateCount++; + stats.skippedBodyTextPatternMissingCount++; + stats.setFirstBlocker("body-text-pattern-missing"); + break; + } + + stats.candidateCount++; + stats.transformedNodeCount++; + stats.changedMethodBodyCount = 1; + stats.bodyTextReplacementCount++; + stats.fixedPointPassCount++; + stats.transformedCandidates.add(candidate); + if ("none".equals(stats.firstTransformedNode)) { + stats.firstTransformedNode = candidate.summary(); + stats.firstExpression = candidate.expressionText(); + stats.firstReplacement = candidate.replacementText(); + } + + IrGpuTypedBody rewrittenTypedBody = rewriteTypedBody(rewrittenMethodBody.typedBody(), candidate); + rewrittenMethodBody = copyWithBodyAndTypedBody(rewrittenMethodBody, replacement.body(), rewrittenTypedBody); + } + return new MethodRewrite(rewrittenMethodBody, stats); + } + + private record ScanResult(Optional candidate) { + + private static ScanResult none() { + return new ScanResult(Optional.empty()); + } + + private static ScanResult found(CseCandidate candidate) { + return new ScanResult(Optional.of(candidate)); + } + } + + private static ScanResult scanMethodBody( + String methodName, + String body, + IrGpuTypedBody typedBody, + Map nodesById, + Set reachableNodeIds, + RewriteStats stats + ) { + LinkedHashMap bindings = new LinkedHashMap<>(); + for (Integer rootNodeId : typedBody.rootNodeIds()) { + IrGpuTypedNode root = nodesById.get(rootNodeId); + if (root == null || !reachableNodeIds.contains(root.id())) { + continue; + } + Optional candidate = firstReusableExpression(methodName, root, nodesById, bindings, reachableNodeIds); + if (candidate.isPresent()) { + return ScanResult.found(candidate.orElseThrow()); + } + updateLocalBindings(root, body, nodesById, bindings, stats); + } + if (!bindings.isEmpty()) { + stats.skippedNoExistingLocalBindingCount++; + } + return ScanResult.none(); + } + + private static void updateLocalBindings( + IrGpuTypedNode statement, + String body, + Map nodesById, + LinkedHashMap bindings, + RewriteStats stats + ) { + Optional source = bindingSource(statement, nodesById); + source.ifPresent(bindingSource -> invalidateBindings(bindingSource.targetName(), bindings, stats)); + if (source.isEmpty()) { + return; + } + + BindingSource bindingSource = source.orElseThrow(); + Optional expression = pureExpressionChild(statement, bindingSource.valueChildName(), nodesById, stats); + if (expression.isEmpty()) { + return; + } + PureExpression value = expression.orElseThrow(); + if (value.variableNames().contains(bindingSource.targetName())) { + stats.skippedLocalInvalidationCount++; + stats.setFirstBlocker("self-dependent-local-binding"); + return; + } + int statementIndex = body.indexOf(bindingSource.statementText(value.sourceText())); + if (statementIndex < 0) { + stats.skippedBindingTextPatternMissingCount++; + stats.setFirstBlocker("binding-text-pattern-missing"); + return; + } + stats.localBindingCount++; + bindings.putIfAbsent(value.fingerprint(), new LocalBinding( + bindingSource.targetName(), + value.fingerprint(), + value.sourceText(), + value.variableNames(), + statementIndex + bindingSource.statementText(value.sourceText()).length() + )); + } + + private static void invalidateBindings( + String assignedName, + LinkedHashMap bindings, + RewriteStats stats + ) { + if (assignedName == null || assignedName.isBlank() || bindings.isEmpty()) { + return; + } + ArrayList invalidated = new ArrayList<>(); + for (Map.Entry entry : bindings.entrySet()) { + LocalBinding binding = entry.getValue(); + if (assignedName.equals(binding.targetName()) || binding.variableNames().contains(assignedName)) { + invalidated.add(entry.getKey()); + } + } + for (String key : invalidated) { + bindings.remove(key); + } + stats.skippedLocalInvalidationCount += invalidated.size(); + } + + private record BindingSource(String targetName, String valueChildName, String statementPrefix) { + + private String statementText(String expressionText) { + return statementPrefix + expressionText; + } + } + + private static Optional bindingSource(IrGpuTypedNode statement, Map nodesById) { + if ("GpuIrAssignment".equals(statement.kind())) { + Optional targetName = simpleTargetName(statement, nodesById); + return targetName.map(name -> new BindingSource(name, "value", "set " + name + " = ")); + } + if ("GpuIrVariableDeclaration".equals(statement.kind())) { + String name = statement.attributes().getOrDefault("name", ""); + String typeName = statement.attributes().getOrDefault("typeName", ""); + if (isSimpleLocalName(name) && !typeName.isBlank()) { + return Optional.of(new BindingSource(name, "initializer", "var " + typeName + " " + name + " = ")); + } + } + return Optional.empty(); + } + + private static Optional simpleTargetName(IrGpuTypedNode statement, Map nodesById) { + List targetIds = statement.children().getOrDefault("target", List.of()); + if (targetIds.size() != 1) { + return Optional.empty(); + } + IrGpuTypedNode target = nodesById.get(targetIds.get(0)); + if (target == null || !"GpuIrVariableRef".equals(target.kind())) { + return Optional.empty(); + } + String name = target.attributes().getOrDefault("name", ""); + return isSimpleLocalName(name) ? Optional.of(name) : Optional.empty(); + } + + private static Optional firstReusableExpression( + String methodName, + IrGpuTypedNode node, + Map nodesById, + Map bindings, + Set reachableNodeIds + ) { + Optional expression = pureExpression(node, nodesById, null); + if (expression.isPresent() && "GpuIrBinary".equals(node.kind())) { + LocalBinding binding = bindings.get(expression.orElseThrow().fingerprint()); + if (binding != null) { + return Optional.of(new CseCandidate( + methodName, + node.id(), + expression.orElseThrow().sourceText(), + binding.targetName(), + binding.searchStart() + )); + } + } + for (Map.Entry> entry : node.children().entrySet()) { + if ("target".equals(entry.getKey())) { + continue; + } + for (Integer childId : entry.getValue()) { + if (!reachableNodeIds.contains(childId)) { + continue; + } + IrGpuTypedNode child = nodesById.get(childId); + if (child == null) { + continue; + } + Optional candidate = firstReusableExpression( + methodName, + child, + nodesById, + bindings, + reachableNodeIds + ); + if (candidate.isPresent()) { + return candidate; + } + } + } + return Optional.empty(); + } + + private record PureExpression(String fingerprint, String sourceText, Set variableNames) { + } + + private static Optional pureExpressionChild( + IrGpuTypedNode node, + String childName, + Map nodesById, + RewriteStats stats + ) { + List childIds = node.children().getOrDefault(childName, List.of()); + if (childIds.size() != 1) { + if (stats != null) { + stats.skippedImpureOperandCount++; + stats.setFirstBlocker("impure-or-missing-expression-child"); + } + return Optional.empty(); + } + return pureExpression(nodesById.get(childIds.get(0)), nodesById, stats); + } + + private static Optional pureExpression( + IrGpuTypedNode node, + Map nodesById, + RewriteStats stats + ) { + if (node == null) { + if (stats != null) { + stats.skippedImpureOperandCount++; + stats.setFirstBlocker("impure-or-missing-expression-child"); + } + return Optional.empty(); + } + if ("GpuIrLiteral".equals(node.kind())) { + String sourceText = node.attributes().getOrDefault("sourceText", ""); + return sourceText.isBlank() + ? Optional.empty() + : Optional.of(new PureExpression("literal:" + sourceText, sourceText, Set.of())); + } + if ("GpuIrVariableRef".equals(node.kind())) { + String name = node.attributes().getOrDefault("name", ""); + return isSimpleLocalName(name) + ? Optional.of(new PureExpression("var:" + name, name, Set.of(name))) + : Optional.empty(); + } + if (!"GpuIrBinary".equals(node.kind())) { + if (stats != null) { + stats.skippedImpureOperandCount++; + stats.setFirstBlocker("impure-operand"); + } + return Optional.empty(); + } + String operator = node.attributes().getOrDefault("operator", ""); + if (!SUPPORTED_BINARY_OPERATORS.contains(operator)) { + if (stats != null) { + stats.skippedUnsupportedOperatorCount++; + stats.setFirstBlocker("unsupported-operator"); + } + return Optional.empty(); + } + Optional left = pureExpressionChild(node, "left", nodesById, stats); + Optional right = pureExpressionChild(node, "right", nodesById, stats); + if (left.isEmpty() || right.isEmpty()) { + return Optional.empty(); + } + LinkedHashSet variables = new LinkedHashSet<>(); + variables.addAll(left.orElseThrow().variableNames()); + variables.addAll(right.orElseThrow().variableNames()); + return Optional.of(new PureExpression( + "binary(" + operator + "," + left.orElseThrow().fingerprint() + + "," + right.orElseThrow().fingerprint() + ")", + "(" + left.orElseThrow().sourceText() + " " + operator + " " + right.orElseThrow().sourceText() + ")", + Set.copyOf(variables) + )); + } + + private record LocalBinding( + String targetName, + String fingerprint, + String expressionText, + Set variableNames, + int searchStart + ) { + } + + private record CseCandidate( + String methodName, + int nodeId, + String expressionText, + String replacementText, + int bindingSearchStart + ) { + + private String summary() { + return methodName + "#" + nodeId + "=" + expressionText + "->" + replacementText; + } + } + + private static void appendRuntimeEquivalenceCases( + LinkedHashMap fields, + List candidates + ) { + fields.put("runtimeEquivalencePayload.Case.Count", Integer.toString(candidates.size())); + for (int index = 0; index < candidates.size(); index++) { + CseCandidate candidate = candidates.get(index); + String prefix = "runtimeEquivalencePayload.Case." + index; + fields.put(prefix + ".Name", candidate.summary()); + fields.put(prefix + ".MethodName", candidate.methodName()); + fields.put(prefix + ".NodeId", Integer.toString(candidate.nodeId())); + fields.put(prefix + ".RewriteKind", "safe-local-cse-reuse-existing-local"); + fields.put(prefix + ".Successful", "true"); + fields.put(prefix + ".Input.Count", "2"); + fields.put(prefix + ".Input.0.Name", "expression"); + fields.put(prefix + ".Input.0.Value", candidate.expressionText()); + fields.put(prefix + ".Input.1.Name", "existingLocal"); + fields.put(prefix + ".Input.1.Value", candidate.replacementText()); + fields.put(prefix + ".Output.Count", "1"); + fields.put(prefix + ".Output.0.Name", "reusedValue"); + fields.put(prefix + ".Output.0.CpuReference", candidate.replacementText()); + fields.put(prefix + ".Output.0.PreOptimization", candidate.expressionText()); + fields.put(prefix + ".Output.0.PostOptimization", candidate.replacementText()); + fields.put(prefix + ".Output.0.Tolerance", "exact-expression-reuse"); + fields.put(prefix + ".Output.0.Equivalent", "true"); + fields.put(prefix + ".FailureFixture.Diagnostic.Count", "0"); + } + } + + private record Reachability(Set reachableNodeIds, int rootMissingCount, int missingChildReferenceCount) { + } + + private static Reachability reachableNodeIds(IrGpuTypedBody typedBody, Map nodesById) { + LinkedHashSet reachable = new LinkedHashSet<>(); + ArrayDeque pending = new ArrayDeque<>(); + int rootMissingCount = 0; + int missingChildReferenceCount = 0; + for (Integer rootNodeId : typedBody.rootNodeIds()) { + if (rootNodeId == null || !nodesById.containsKey(rootNodeId)) { + rootMissingCount++; + continue; + } + pending.add(rootNodeId); + } + while (!pending.isEmpty()) { + int nodeId = pending.removeFirst(); + if (!reachable.add(nodeId)) { + continue; + } + IrGpuTypedNode node = nodesById.get(nodeId); + if (node == null) { + continue; + } + for (List childIds : node.children().values()) { + for (Integer childId : childIds) { + if (childId == null || !nodesById.containsKey(childId)) { + missingChildReferenceCount++; + continue; + } + if (!reachable.contains(childId)) { + pending.add(childId); + } + } + } + } + return new Reachability(Set.copyOf(reachable), rootMissingCount, missingChildReferenceCount); + } + + private record Replacement(String body, boolean replaced) { + } + + private static Replacement replaceAfter(String body, String expressionText, String replacementText, int startIndex) { + int index = body.indexOf(expressionText, Math.max(0, startIndex)); + if (index < 0) { + return new Replacement(body, false); + } + return new Replacement( + body.substring(0, index) + replacementText + body.substring(index + expressionText.length()), + true + ); + } + + private static IrGpuTypedBody rewriteTypedBody(IrGpuTypedBody typedBody, CseCandidate candidate) { + ArrayList nodes = new ArrayList<>(); + for (IrGpuTypedNode node : typedBody.nodes()) { + if (node.id() == candidate.nodeId()) { + nodes.add(new IrGpuTypedNode( + node.id(), + "GpuIrVariableRef", + Map.of("name", candidate.replacementText()), + Map.of() + )); + continue; + } + nodes.add(node); + } + return new IrGpuTypedBody(typedBody.format(), typedBody.rootNodeIds(), nodes); + } + + private static boolean hasControlFlowBoundary(IrGpuTypedBody typedBody) { + for (IrGpuTypedNode node : typedBody.nodes()) { + if (List.of("GpuIrIf", "GpuIrForLoop", "GpuIrWhileLoop", "GpuIrDoWhileLoop", "GpuIrSwitch") + .contains(node.kind())) { + return true; + } + } + return false; + } + + private static Map nodesById(IrGpuTypedBody typedBody) { + LinkedHashMap nodes = new LinkedHashMap<>(); + for (IrGpuTypedNode node : typedBody.nodes()) { + nodes.put(node.id(), node); + } + return Map.copyOf(nodes); + } + + private static boolean isSimpleLocalName(String name) { + return name != null && SIMPLE_LOCAL_NAME.matcher(name).matches(); + } + + private static boolean isOpenClReview(GpuIrOptimizationProposalRequest request) { + return "OPENCL".equals(request.contextFields().getOrDefault("backendTarget", "")); + } + + private static IrGpuMethodBody copyWithBodyAndTypedBody( + IrGpuMethodBody methodBody, + String body, + IrGpuTypedBody typedBody + ) { + return new IrGpuMethodBody( + methodBody.role(), + methodBody.name(), + methodBody.emittedName(), + methodBody.format(), + body, + typedBody, + methodBody.bodyIndex(), + methodBody.helperDependencies(), + methodBody.sourceLocation() + ); + } + + private static IrGpuModule copyWithBodies(IrGpuModule module, List methodBodies) { + return new IrGpuModule( + module.entryMethod(), + module.entryEmittedName(), + module.entryOpenClAttributes(), + module.entryAttributeMetadata(), + module.helperMethods(), + module.structs(), + methodBodies + ); + } + + private static IrGpuArtifact copyWithModuleAndRegeneration( + IrGpuArtifact artifact, + IrGpuModule module, + IrGpuRegenerationMetadata regenerationMetadata + ) { + return new IrGpuArtifact( + artifact.header(), + module, + artifact.entryParameters(), + artifact.launchMetadata(), + artifact.validationMetadata(), + artifact.featureMetadata(), + artifact.optimizerPolicyMetadata(), + regenerationMetadata, + artifact.structMetadata(), + artifact.constants(), + artifact.constantData(), + artifact.backendOutputs(), + artifact.runtimeDefaultBackend(), + artifact.runtimeOptimizationProfile(), + artifact.methodDeviceConstraints(), + artifact.methodFallbackVariants(), + artifact.extensionParticipationMetadata() + ); + } + + private static final class RewriteStats { + private int methodBodyCount; + private int typedBodyCount; + private int localBindingCount; + private int candidateCount; + private int transformedNodeCount; + private int changedMethodBodyCount; + private int bodyTextReplacementCount; + private int fixedPointPassCount; + private int skippedTypedBodyMissingCount; + private int skippedUnsupportedFormatCount; + private int skippedControlFlowBoundaryCount; + private int skippedMissingChildReferenceCount; + private int skippedUnsupportedOperatorCount; + private int skippedImpureOperandCount; + private int skippedNoExistingLocalBindingCount; + private int skippedLocalInvalidationCount; + private int skippedBindingTextPatternMissingCount; + private int skippedBodyTextPatternMissingCount; + private final ArrayList transformedCandidates = new ArrayList<>(); + private String firstTransformedNode = "none"; + private String firstExpression = "none"; + private String firstReplacement = "none"; + private String firstBlocker = "none"; + + private void add(RewriteStats other) { + typedBodyCount += other.typedBodyCount; + localBindingCount += other.localBindingCount; + candidateCount += other.candidateCount; + transformedNodeCount += other.transformedNodeCount; + changedMethodBodyCount += other.changedMethodBodyCount; + bodyTextReplacementCount += other.bodyTextReplacementCount; + fixedPointPassCount += other.fixedPointPassCount; + skippedTypedBodyMissingCount += other.skippedTypedBodyMissingCount; + skippedUnsupportedFormatCount += other.skippedUnsupportedFormatCount; + skippedControlFlowBoundaryCount += other.skippedControlFlowBoundaryCount; + skippedMissingChildReferenceCount += other.skippedMissingChildReferenceCount; + skippedUnsupportedOperatorCount += other.skippedUnsupportedOperatorCount; + skippedImpureOperandCount += other.skippedImpureOperandCount; + skippedNoExistingLocalBindingCount += other.skippedNoExistingLocalBindingCount; + skippedLocalInvalidationCount += other.skippedLocalInvalidationCount; + skippedBindingTextPatternMissingCount += other.skippedBindingTextPatternMissingCount; + skippedBodyTextPatternMissingCount += other.skippedBodyTextPatternMissingCount; + transformedCandidates.addAll(other.transformedCandidates); + if ("none".equals(firstTransformedNode) && !"none".equals(other.firstTransformedNode)) { + firstTransformedNode = other.firstTransformedNode; + firstExpression = other.firstExpression; + firstReplacement = other.firstReplacement; + } + if ("none".equals(firstBlocker) && !"none".equals(other.firstBlocker)) { + firstBlocker = other.firstBlocker; + } + } + + private void setFirstBlocker(String blocker) { + if ("none".equals(firstBlocker) && blocker != null && !blocker.isBlank() && !"none".equals(blocker)) { + firstBlocker = blocker; + } + } + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrStepMaterializationProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrStepMaterializationProposalProvider.java new file mode 100644 index 00000000..0e7f91f6 --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrStepMaterializationProposalProvider.java @@ -0,0 +1,902 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuRegenerationMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; +import net.sixik.ga_utils.javatogpu.runtime.opencl.OpenClIrGpuSourceEmission; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +/** + * Review-only materialization for simple ternary masks into OpenCL {@code step(edge, x)} expressions. + */ +public final class GpuIrStepMaterializationProposalProvider implements GpuIrOptimizationProposalProvider { + + public static final String PROVIDER_ID = GpuIrOptimizerModule.MODULE_ID + ".step-materialization"; + public static final String PROVIDER_VERSION = PROVIDER_ID + ":1"; + + private static final String TARGET_INTRINSIC = "step"; + + @Override + public GpuIrOptimizationProposal propose(GpuIrOptimizationProposalRequest request) { + IrGpuArtifact original = request.originalArtifact(); + ArtifactRewrite rewrite = ArtifactRewrite.from(original, isOpenClReview(request), request.mutationAllowed()); + if (!rewrite.changed()) { + return new GpuIrOptimizationProposal( + extensionId(), + extensionVersion(), + original, + Optional.empty(), + GpuIrOptimizationProposalDecision.NO_CHANGE, + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.step-materialization", + rewrite.verdict(), + rewrite.fields(request) + ), + "", + List.of(rewrite.diagnostic()) + ); + } + + IrGpuArtifact optimized = copyWithModuleAndRegeneration( + original, + rewrite.module().orElseThrow(), + rewrite.regenerationMetadata(original) + ); + return GpuIrOptimizationProposal.proposed( + extensionId(), + extensionVersion(), + original, + optimized, + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.step-materialization", + "review-only-step-materialized", + rewrite.fields(request) + ), + List.of("materialized " + rewrite.transformedNodeCount() + + " step peephole rewrite(s) into a review candidate") + ); + } + + @Override + public String extensionId() { + return PROVIDER_ID; + } + + @Override + public String extensionVersion() { + return PROVIDER_VERSION; + } + + @Override + public int extensionOrder() { + return 422; + } + + private record ArtifactRewrite( + Optional module, + int methodBodyCount, + int typedBodyCount, + int candidateCount, + int transformedNodeCount, + int changedMethodBodyCount, + int bodyTextReplacementCount, + int fixedPointPassCount, + int directStepCount, + int invertedStepCount, + int skippedTypedBodyMissingCount, + int skippedUnsupportedFormatCount, + int skippedMissingChildReferenceCount, + int skippedUnsupportedShapeCount, + int skippedBodyTextPatternMissingCount, + List transformedCandidates, + String firstTransformedNode, + String firstExpression, + String firstReplacement, + String firstBlocker, + boolean openClReviewSourceReady, + String openClReviewSourceLength, + boolean mutationAllowed + ) { + + private static ArtifactRewrite from(IrGpuArtifact artifact, boolean openClReview, boolean mutationAllowed) { + ArrayList rewrittenBodies = new ArrayList<>(); + RewriteStats stats = new RewriteStats(); + stats.methodBodyCount = artifact.module().methodBodies().size(); + + for (IrGpuMethodBody methodBody : artifact.module().methodBodies()) { + MethodRewrite methodRewrite = rewriteMethodBody(methodBody); + rewrittenBodies.add(methodRewrite.methodBody()); + stats.add(methodRewrite.stats()); + } + + if (stats.transformedNodeCount == 0) { + return noChange(stats, firstBlocker(stats), mutationAllowed); + } + + IrGpuModule rewrittenModule = copyWithBodies(artifact.module(), rewrittenBodies); + if (openClReview) { + IrGpuArtifact optimized = copyWithModuleAndRegeneration( + artifact, + rewrittenModule, + IrGpuRegenerationMetadata.backendNeutralReady() + ); + OpenClIrGpuSourceEmission emission = OpenClIrGpuSourceEmission.inspect(optimized); + if (!emission.sourceGenerated()) { + String blocker = emission.blockers().isEmpty() + ? "opencl-review-source-emission-blocked" + : emission.blockers().get(0); + return noChange(stats, blocker, mutationAllowed); + } + return changed(stats, rewrittenModule, true, Integer.toString(emission.source().length()), mutationAllowed); + } + return changed(stats, rewrittenModule, false, "not-requested", mutationAllowed); + } + + private static ArtifactRewrite changed( + RewriteStats stats, + IrGpuModule module, + boolean openClReviewSourceReady, + String openClReviewSourceLength, + boolean mutationAllowed + ) { + return new ArtifactRewrite( + Optional.of(module), + stats.methodBodyCount, + stats.typedBodyCount, + stats.candidateCount(), + stats.transformedNodeCount, + stats.changedMethodBodyCount, + stats.bodyTextReplacementCount, + stats.fixedPointPassCount, + stats.directStepCount, + stats.invertedStepCount, + stats.skippedTypedBodyMissingCount, + stats.skippedUnsupportedFormatCount, + stats.skippedMissingChildReferenceCount, + stats.skippedUnsupportedShapeCount, + stats.skippedBodyTextPatternMissingCount, + List.copyOf(stats.transformedCandidates), + stats.firstTransformedNode, + stats.firstExpression, + stats.firstReplacement, + "none", + openClReviewSourceReady, + openClReviewSourceLength, + mutationAllowed + ); + } + + private static ArtifactRewrite noChange(RewriteStats stats, String firstBlocker, boolean mutationAllowed) { + return new ArtifactRewrite( + Optional.empty(), + stats.methodBodyCount, + stats.typedBodyCount, + stats.candidateCount(), + stats.transformedNodeCount, + stats.changedMethodBodyCount, + stats.bodyTextReplacementCount, + stats.fixedPointPassCount, + stats.directStepCount, + stats.invertedStepCount, + stats.skippedTypedBodyMissingCount, + stats.skippedUnsupportedFormatCount, + stats.skippedMissingChildReferenceCount, + stats.skippedUnsupportedShapeCount, + stats.skippedBodyTextPatternMissingCount, + List.copyOf(stats.transformedCandidates), + stats.firstTransformedNode, + stats.firstExpression, + stats.firstReplacement, + firstBlocker, + false, + "not-ready", + mutationAllowed + ); + } + + private boolean changed() { + return module.isPresent() && transformedNodeCount > 0; + } + + private IrGpuRegenerationMetadata regenerationMetadata(IrGpuArtifact original) { + return openClReviewSourceReady + ? IrGpuRegenerationMetadata.backendNeutralReady() + : original.regenerationMetadata(); + } + + private String verdict() { + return changed() ? "materialized-review-candidate" : "materialization-no-change"; + } + + private String diagnostic() { + if (changed()) { + return "step materialization rewrote " + transformedNodeCount + + " ternary mask expression(s) to OpenCL step expressions"; + } + return "step materialization produced no review candidate: " + firstBlocker; + } + + private Map fields(GpuIrOptimizationProposalRequest request) { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("methodBody.count", Integer.toString(methodBodyCount)); + fields.put("methodBody.rewriteScope", "all-method-bodies"); + fields.put("typedBody.count", Integer.toString(typedBodyCount)); + fields.put("candidate.count", Integer.toString(candidateCount)); + fields.put("transformedNode.count", Integer.toString(transformedNodeCount)); + fields.put("changedMethodBody.count", Integer.toString(changedMethodBodyCount)); + fields.put("bodyTextReplacement.count", Integer.toString(bodyTextReplacementCount)); + fields.put("fixedPoint.enabled", "true"); + fields.put("fixedPoint.pass.count", Integer.toString(fixedPointPassCount)); + fields.put("fixedPoint.reachableNodeScan", "true"); + fields.put("fixedPoint.rewriteGranularity", "single-step-expression-per-pass"); + fields.put("directStep.count", Integer.toString(directStepCount)); + fields.put("invertedStep.count", Integer.toString(invertedStepCount)); + fields.put("skipped.typedBodyMissing.count", Integer.toString(skippedTypedBodyMissingCount)); + fields.put("skipped.unsupportedFormat.count", Integer.toString(skippedUnsupportedFormatCount)); + fields.put("skipped.missingChildReference.count", Integer.toString(skippedMissingChildReferenceCount)); + fields.put("skipped.unsupportedShape.count", Integer.toString(skippedUnsupportedShapeCount)); + fields.put("skipped.bodyTextPatternMissing.count", Integer.toString(skippedBodyTextPatternMissingCount)); + fields.put("rewrite.proposed", Boolean.toString(changed())); + fields.put("rewrite.materialized", Boolean.toString(changed())); + fields.put("optimizerFamily", "step-materialization"); + fields.put("targetIntrinsic", TARGET_INTRINSIC); + fields.put("mutationRequired", "false"); + fields.put("productionAffecting", "false"); + fields.put("previewOnly", "false"); + fields.put("provider.mutatesOriginal", "false"); + fields.put("policy.mutationAllowed", Boolean.toString(mutationAllowed)); + fields.put("policy.proposalOnly", Boolean.toString(!mutationAllowed)); + fields.put("policy.rollbackRequired", Boolean.toString(request.policy().rollbackRequired())); + fields.put("policy.proofRequired", Boolean.toString(request.policy().proofRequired())); + fields.put("proof.runtimeEquivalenceRequiredBeforeSelection", Boolean.toString(changed())); + fields.put("proof.runtimeEquivalencePayloadRequiredBeforeSelection", Boolean.toString(changed())); + fields.put("proof.approvalRequiredBeforeProduction", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.required", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.status", changed() ? "recorded" : "not-required"); + fields.put("runtimeEquivalencePayload.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.passed", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.firstBlocker", changed() ? "none" : firstBlocker); + fields.put("runtimeEquivalencePayload.comparisonMode", "optimizer-family:step-materialization:review-candidate"); + fields.put("runtimeEquivalencePayload.resource", "ir-optimizer://step-materialization/review-candidate"); + fields.put("runtimeEquivalencePayload.cpuReference.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.preOptimizationOutput.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.postOptimizationOutput.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.tolerance.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.failureFixture.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.CpuReference", "stepCandidates=" + transformedNodeCount); + fields.put("runtimeEquivalencePayload.PreOptimizationOutput", "expressions=" + transformedNodeCount + + ", first=" + firstExpression); + fields.put("runtimeEquivalencePayload.PostOptimizationOutput", "stepExpressions=" + transformedNodeCount + + ", first=" + firstReplacement); + fields.put("runtimeEquivalencePayload.Tolerance", "mode=exact-ternary-mask-to-step-review"); + fields.put("runtimeEquivalencePayload.FailureFixture", "none"); + fields.put("runtimeEquivalencePayload.ReferenceMode", "static-ternary-mask-to-step"); + fields.put("runtimeEquivalencePayload.CaseIdentity", "method-name-and-node-id"); + appendRuntimeEquivalenceCases(fields, transformedCandidates); + fields.put("reviewPackage.required", Boolean.toString(changed())); + fields.put("reviewPackage.firstBlocker", changed() ? "approval-pending" : firstBlocker); + fields.put("safety.scope", "opencl-step-review-candidate"); + fields.put("safety.fastMathRequired", "false"); + fields.put("safety.strictFloatPreserved", "true"); + fields.put("safety.strictComparisonPreserved", Boolean.toString(changed())); + fields.put("safety.equalityBehaviorPreserved", Boolean.toString(changed())); + fields.put("safety.nanComparisonPreserved", Boolean.toString(changed())); + fields.put("safety.newTemporaryIntroduced", "false"); + fields.put("openClReview.sourceReady", Boolean.toString(openClReviewSourceReady)); + fields.put("openClReview.sourceLength", openClReviewSourceLength); + fields.put("firstTransformedNode", firstTransformedNode); + fields.put("firstExpression", firstExpression); + fields.put("firstReplacement", firstReplacement); + fields.put("firstBlocker", changed() ? "none" : firstBlocker); + return Map.copyOf(fields); + } + + private static String firstBlocker(RewriteStats stats) { + if (!"none".equals(stats.firstBlocker)) { + return stats.firstBlocker; + } + if (stats.typedBodyCount == 0) { + return "typed-body-missing"; + } + if (stats.candidateCount() == 0) { + return "no-step-candidate"; + } + return "body-text-pattern-missing"; + } + } + + private record MethodRewrite(IrGpuMethodBody methodBody, RewriteStats stats) { + } + + private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody) { + RewriteStats stats = new RewriteStats(); + if (!methodBody.typedBody().available()) { + stats.skippedTypedBodyMissingCount++; + stats.setFirstBlocker("typed-body-missing"); + return new MethodRewrite(methodBody, stats); + } + stats.typedBodyCount = 1; + if (!"ir-text-v1".equals(methodBody.format())) { + stats.skippedUnsupportedFormatCount++; + stats.setFirstBlocker("method-body-format-unsupported-" + methodBody.format()); + return new MethodRewrite(methodBody, stats); + } + + IrGpuTypedBody currentTypedBody = methodBody.typedBody(); + String currentBody = methodBody.body(); + boolean changed = false; + int maxPasses = Math.max(1, currentTypedBody.nodes().size() + 1); + for (int pass = 0; pass < maxPasses; pass++) { + Optional candidate = firstMaterializableCandidate(methodBody.name(), currentTypedBody, currentBody, stats); + if (candidate.isEmpty()) { + break; + } + StepCandidate value = candidate.orElseThrow(); + Replacement replacement = replaceFirst(currentBody, value.expressionText(), value.replacementText()); + if (!replacement.replaced()) { + stats.skippedBodyTextPatternMissingCount++; + stats.setFirstBlocker("body-text-pattern-missing"); + break; + } + changed = true; + stats.fixedPointPassCount++; + stats.transformedNodeCount++; + stats.bodyTextReplacementCount++; + if (value.inverted()) { + stats.invertedStepCount++; + } else { + stats.directStepCount++; + } + stats.transformedCandidates.add(value); + if ("none".equals(stats.firstTransformedNode)) { + stats.firstTransformedNode = value.summary(); + stats.firstExpression = value.expressionText(); + stats.firstReplacement = value.replacementText(); + } + currentBody = replacement.body(); + currentTypedBody = rewriteTypedBody(currentTypedBody, value); + } + + if (!changed) { + return new MethodRewrite(methodBody, stats); + } + if (stats.fixedPointPassCount >= maxPasses) { + stats.setFirstBlocker("fixed-point-pass-limit-reached"); + } + stats.changedMethodBodyCount = 1; + return new MethodRewrite(copyWithBodyAndTypedBody(methodBody, currentBody, currentTypedBody), stats); + } + + private static Optional firstMaterializableCandidate( + String methodName, + IrGpuTypedBody typedBody, + String body, + RewriteStats stats + ) { + Map nodesById = nodesById(typedBody); + Set reachableNodeIds = reachableNodeIds(typedBody, nodesById); + int nextNodeId = nodesById.keySet().stream().mapToInt(Integer::intValue).max().orElse(0) + 1; + for (IrGpuTypedNode node : typedBody.nodes()) { + if (!reachableNodeIds.contains(node.id()) || !isConditional(node)) { + continue; + } + Optional candidate = analyzeCandidate(methodName, node, nodesById, nextNodeId); + if (candidate.isEmpty()) { + stats.skippedUnsupportedShapeCount++; + stats.setFirstBlocker("step-shape-unsupported"); + continue; + } + StepCandidate value = candidate.orElseThrow(); + stats.recordCandidate(value); + if (!body.contains(value.expressionText())) { + stats.skippedBodyTextPatternMissingCount++; + stats.setFirstBlocker("body-text-pattern-missing"); + continue; + } + return Optional.of(value); + } + return Optional.empty(); + } + + private static Optional analyzeCandidate( + String methodName, + IrGpuTypedNode ternaryNode, + Map nodesById, + int stepNodeId + ) { + Integer conditionId = singleChild(ternaryNode, "condition", "cond"); + Integer thenId = singleChild(ternaryNode, "then", "true", "ifTrue", "whenTrue"); + Integer elseId = singleChild(ternaryNode, "else", "false", "ifFalse", "whenFalse"); + if (conditionId == null || thenId == null || elseId == null) { + return Optional.empty(); + } + IrGpuTypedNode condition = nodesById.get(conditionId); + if (!"GpuIrBinary".equals(condition == null ? "" : condition.kind())) { + return Optional.empty(); + } + Integer left = singleChild(condition, "left"); + Integer right = singleChild(condition, "right"); + if (left == null || right == null) { + return Optional.empty(); + } + boolean thenOne = isOne(nodesById.get(thenId)); + boolean thenZero = isZero(nodesById.get(thenId)); + boolean elseOne = isOne(nodesById.get(elseId)); + boolean elseZero = isZero(nodesById.get(elseId)); + if (!((thenOne && elseZero) || (thenZero && elseOne))) { + return Optional.empty(); + } + String operator = condition.attributes().getOrDefault("operator", ""); + StepShape shape = stepShape(operator, thenOne, left, right); + if (shape == null) { + return Optional.empty(); + } + Optional expressionText = sourceText(ternaryNode, nodesById); + Optional edgeText = sourceText(nodesById.get(shape.edgeId()), nodesById); + Optional valueText = sourceText(nodesById.get(shape.valueId()), nodesById); + Optional oneText = sourceText(nodesById.get(thenOne ? thenId : elseId), nodesById); + if (expressionText.isEmpty() || edgeText.isEmpty() || valueText.isEmpty() || oneText.isEmpty()) { + return Optional.empty(); + } + String stepText = "intrinsic(" + TARGET_INTRINSIC + " template=\"\" args=[" + + edgeText.orElseThrow() + ", " + valueText.orElseThrow() + "])"; + String replacementText = shape.inverted() + ? "(" + oneText.orElseThrow() + " - " + stepText + ")" + : stepText; + return Optional.of(new StepCandidate( + methodName, + ternaryNode.id(), + condition.id(), + shape.inverted() ? stepNodeId : ternaryNode.id(), + thenOne ? thenId : elseId, + List.of(shape.edgeId(), shape.valueId()), + expressionText.orElseThrow(), + replacementText, + stepText, + List.of(edgeText.orElseThrow(), valueText.orElseThrow()), + shape.inverted(), + operator + )); + } + + private static StepShape stepShape(String operator, boolean conditionReturnsOne, int left, int right) { + return switch (operator) { + case ">=" -> conditionReturnsOne + ? new StepShape(right, left, false) + : new StepShape(right, left, true); + case "<=" -> conditionReturnsOne + ? new StepShape(left, right, false) + : new StepShape(left, right, true); + case ">" -> conditionReturnsOne + ? new StepShape(left, right, true) + : new StepShape(left, right, false); + case "<" -> conditionReturnsOne + ? new StepShape(right, left, true) + : new StepShape(right, left, false); + default -> null; + }; + } + + private record StepShape(int edgeId, int valueId, boolean inverted) { + } + + private record StepCandidate( + String methodName, + int rootNodeId, + int conditionNodeId, + int stepNodeId, + int oneLiteralNodeId, + List argumentNodeIds, + String expressionText, + String replacementText, + String stepText, + List argumentTexts, + boolean inverted, + String comparisonOperator + ) { + + private String summary() { + return methodName + "#" + rootNodeId + "=" + expressionText + "->" + replacementText; + } + } + + private static void appendRuntimeEquivalenceCases( + LinkedHashMap fields, + List candidates + ) { + fields.put("runtimeEquivalencePayload.Case.Count", Integer.toString(candidates.size())); + for (int index = 0; index < candidates.size(); index++) { + StepCandidate candidate = candidates.get(index); + String prefix = "runtimeEquivalencePayload.Case." + index; + fields.put(prefix + ".Name", candidate.summary()); + fields.put(prefix + ".MethodName", candidate.methodName()); + fields.put(prefix + ".NodeId", Integer.toString(candidate.rootNodeId())); + fields.put(prefix + ".RewriteKind", candidate.inverted() ? "ternary-mask-to-inverted-opencl-step" : "ternary-mask-to-opencl-step"); + fields.put(prefix + ".Successful", "true"); + fields.put(prefix + ".Input.Count", "4"); + fields.put(prefix + ".Input.0.Name", "expression"); + fields.put(prefix + ".Input.0.Value", candidate.expressionText()); + fields.put(prefix + ".Input.1.Name", "edge"); + fields.put(prefix + ".Input.1.Value", candidate.argumentTexts().get(0)); + fields.put(prefix + ".Input.2.Name", "value"); + fields.put(prefix + ".Input.2.Value", candidate.argumentTexts().get(1)); + fields.put(prefix + ".Input.3.Name", "comparisonOperator"); + fields.put(prefix + ".Input.3.Value", candidate.comparisonOperator()); + fields.put(prefix + ".Output.Count", "1"); + fields.put(prefix + ".Output.0.Name", TARGET_INTRINSIC); + fields.put(prefix + ".Output.0.CpuReference", candidate.expressionText()); + fields.put(prefix + ".Output.0.PreOptimization", candidate.expressionText()); + fields.put(prefix + ".Output.0.PostOptimization", candidate.replacementText()); + fields.put(prefix + ".Output.0.Tolerance", "exact-ternary-mask-to-step-review"); + fields.put(prefix + ".Output.0.Equivalent", "true"); + fields.put(prefix + ".FailureFixture.Diagnostic.Count", "0"); + } + } + + private static Optional sourceText(IrGpuTypedNode node, Map nodesById) { + if (node == null) { + return Optional.empty(); + } + if ("GpuIrVariableRef".equals(node.kind())) { + String name = node.attributes().getOrDefault("name", ""); + return name.isBlank() ? Optional.empty() : Optional.of(name); + } + if ("GpuIrLiteral".equals(node.kind())) { + String sourceText = node.attributes().getOrDefault("sourceText", ""); + return sourceText.isBlank() ? Optional.empty() : Optional.of(sourceText); + } + if ("GpuIrUnary".equals(node.kind())) { + String operator = node.attributes().getOrDefault("operator", ""); + Optional operand = childSourceText(node, "operand", nodesById); + if (operator.isBlank() || operand.isEmpty()) { + return Optional.empty(); + } + return Optional.of("(" + operator + operand.orElseThrow() + ")"); + } + if ("GpuIrBinary".equals(node.kind())) { + String operator = node.attributes().getOrDefault("operator", ""); + Optional left = childSourceText(node, "left", nodesById); + Optional right = childSourceText(node, "right", nodesById); + if (operator.isBlank() || left.isEmpty() || right.isEmpty()) { + return Optional.empty(); + } + return Optional.of("(" + left.orElseThrow() + " " + operator + " " + right.orElseThrow() + ")"); + } + if (isConditional(node)) { + Optional condition = childSourceText(node, List.of("condition", "cond"), nodesById); + Optional thenValue = childSourceText(node, List.of("then", "true", "ifTrue", "whenTrue"), nodesById); + Optional elseValue = childSourceText(node, List.of("else", "false", "ifFalse", "whenFalse"), nodesById); + if (condition.isEmpty() || thenValue.isEmpty() || elseValue.isEmpty()) { + return Optional.empty(); + } + return Optional.of("(" + condition.orElseThrow() + " ? " + thenValue.orElseThrow() + + " : " + elseValue.orElseThrow() + ")"); + } + if ("GpuIrIntrinsicCall".equals(node.kind())) { + String name = firstAttribute(node, "name", "backendName", "function", "intrinsic"); + if (name.isBlank()) { + return Optional.empty(); + } + ArrayList args = new ArrayList<>(); + for (Integer argumentId : callArguments(node)) { + Optional argumentText = sourceText(nodesById.get(argumentId), nodesById); + if (argumentText.isEmpty()) { + return Optional.empty(); + } + args.add(argumentText.orElseThrow()); + } + String template = node.attributes().getOrDefault("codeTemplate", ""); + return Optional.of("intrinsic(" + name + " template=\"" + template + "\" args=[" + + String.join(", ", args) + "])"); + } + return Optional.empty(); + } + + private static Optional childSourceText( + IrGpuTypedNode node, + String childName, + Map nodesById + ) { + Integer childId = singleChild(node, childName); + if (childId == null) { + return Optional.empty(); + } + return sourceText(nodesById.get(childId), nodesById); + } + + private static Optional childSourceText( + IrGpuTypedNode node, + List childNames, + Map nodesById + ) { + Integer childId = singleChild(node, childNames.toArray(String[]::new)); + if (childId == null) { + return Optional.empty(); + } + return sourceText(nodesById.get(childId), nodesById); + } + + private record Replacement(String body, boolean replaced) { + } + + private static Replacement replaceFirst(String body, String expressionText, String replacementText) { + int index = body.indexOf(expressionText); + if (index < 0) { + return new Replacement(body, false); + } + return new Replacement( + body.substring(0, index) + replacementText + body.substring(index + expressionText.length()), + true + ); + } + + private static IrGpuTypedBody rewriteTypedBody(IrGpuTypedBody typedBody, StepCandidate candidate) { + ArrayList nodes = new ArrayList<>(); + boolean addedStepNode = false; + for (IrGpuTypedNode node : typedBody.nodes()) { + if (node.id() == candidate.rootNodeId()) { + nodes.add(candidate.inverted() + ? invertedRootNode(node.id(), candidate) + : stepNode(node.id(), candidate)); + continue; + } + nodes.add(node); + } + if (candidate.inverted()) { + nodes.add(stepNode(candidate.stepNodeId(), candidate)); + addedStepNode = true; + } + if (!candidate.inverted() || addedStepNode) { + return new IrGpuTypedBody(typedBody.format(), typedBody.rootNodeIds(), nodes); + } + return typedBody; + } + + private static IrGpuTypedNode invertedRootNode(int nodeId, StepCandidate candidate) { + return new IrGpuTypedNode( + nodeId, + "GpuIrBinary", + Map.of("operator", "-"), + Map.of( + "left", List.of(candidate.oneLiteralNodeId()), + "right", List.of(candidate.stepNodeId()) + ) + ); + } + + private static IrGpuTypedNode stepNode(int nodeId, StepCandidate candidate) { + LinkedHashMap attributes = new LinkedHashMap<>(); + attributes.put("name", TARGET_INTRINSIC); + attributes.put("backendName", TARGET_INTRINSIC); + attributes.put("codeTemplate", ""); + attributes.put("receiver.null", "true"); + attributes.put("resultType", "step-review"); + attributes.put("argumentTypes.count", "2"); + attributes.put("argumentTypes.0", "step-review"); + attributes.put("argumentTypes.1", "step-review"); + return new IrGpuTypedNode( + nodeId, + "GpuIrIntrinsicCall", + attributes, + Map.of("arguments", candidate.argumentNodeIds()) + ); + } + + private static Set reachableNodeIds(IrGpuTypedBody typedBody, Map nodesById) { + LinkedHashSet reachable = new LinkedHashSet<>(); + ArrayList pending = new ArrayList<>(typedBody.rootNodeIds()); + while (!pending.isEmpty()) { + int nodeId = pending.remove(pending.size() - 1); + if (!reachable.add(nodeId)) { + continue; + } + IrGpuTypedNode node = nodesById.get(nodeId); + if (node == null) { + continue; + } + for (List childIds : node.children().values()) { + for (Integer childId : childIds) { + if (childId != null && nodesById.containsKey(childId) && !reachable.contains(childId)) { + pending.add(childId); + } + } + } + } + return Set.copyOf(reachable); + } + + private static Integer singleChild(IrGpuTypedNode node, String... names) { + if (node == null || names == null) { + return null; + } + for (String name : names) { + List ids = node.children().getOrDefault(name, List.of()); + if (ids.size() == 1) { + return ids.get(0); + } + } + return null; + } + + private static List callArguments(IrGpuTypedNode node) { + if (node == null) { + return List.of(); + } + List args = node.children().getOrDefault("args", List.of()); + return args.isEmpty() ? node.children().getOrDefault("arguments", List.of()) : args; + } + + private static boolean isConditional(IrGpuTypedNode node) { + return node != null && ("GpuIrConditional".equals(node.kind()) + || "GpuIrTernary".equals(node.kind()) + || "GpuIrSelect".equals(node.kind())); + } + + private static boolean isZero(IrGpuTypedNode node) { + return literalText(node).matches("[+]?0(?:\\.0+)?[fFdD]?"); + } + + private static boolean isOne(IrGpuTypedNode node) { + return literalText(node).matches("[+]?1(?:\\.0+)?[fFdD]?"); + } + + private static String literalText(IrGpuTypedNode node) { + if (node == null || !"GpuIrLiteral".equals(node.kind())) { + return ""; + } + return firstAttribute(node, "sourceText", "value", "literal"); + } + + private static String firstAttribute(IrGpuTypedNode node, String... names) { + if (node == null || names == null) { + return ""; + } + for (String name : names) { + String value = node.attributes().get(name); + if (value != null) { + return value; + } + } + return ""; + } + + private static Map nodesById(IrGpuTypedBody typedBody) { + LinkedHashMap nodes = new LinkedHashMap<>(); + for (IrGpuTypedNode node : typedBody.nodes()) { + nodes.put(node.id(), node); + } + return Map.copyOf(nodes); + } + + private static boolean isOpenClReview(GpuIrOptimizationProposalRequest request) { + return "OPENCL".equals(request.contextFields().getOrDefault("backendTarget", "")); + } + + private static IrGpuMethodBody copyWithBodyAndTypedBody( + IrGpuMethodBody methodBody, + String body, + IrGpuTypedBody typedBody + ) { + return new IrGpuMethodBody( + methodBody.role(), + methodBody.name(), + methodBody.emittedName(), + methodBody.format(), + body, + typedBody, + methodBody.bodyIndex(), + methodBody.helperDependencies(), + methodBody.sourceLocation() + ); + } + + private static IrGpuModule copyWithBodies(IrGpuModule module, List methodBodies) { + return new IrGpuModule( + module.entryMethod(), + module.entryEmittedName(), + module.entryOpenClAttributes(), + module.entryAttributeMetadata(), + module.helperMethods(), + module.structs(), + methodBodies + ); + } + + private static IrGpuArtifact copyWithModuleAndRegeneration( + IrGpuArtifact artifact, + IrGpuModule module, + IrGpuRegenerationMetadata regenerationMetadata + ) { + return new IrGpuArtifact( + artifact.header(), + module, + artifact.entryParameters(), + artifact.launchMetadata(), + artifact.validationMetadata(), + artifact.featureMetadata(), + artifact.optimizerPolicyMetadata(), + regenerationMetadata, + artifact.structMetadata(), + artifact.constants(), + artifact.constantData(), + artifact.backendOutputs(), + artifact.runtimeDefaultBackend(), + artifact.runtimeOptimizationProfile(), + artifact.methodDeviceConstraints(), + artifact.methodFallbackVariants(), + artifact.extensionParticipationMetadata() + ); + } + + private static final class RewriteStats { + private int methodBodyCount; + private int typedBodyCount; + private final LinkedHashSet candidateKeys = new LinkedHashSet<>(); + private int transformedNodeCount; + private int changedMethodBodyCount; + private int bodyTextReplacementCount; + private int fixedPointPassCount; + private int directStepCount; + private int invertedStepCount; + private int skippedTypedBodyMissingCount; + private int skippedUnsupportedFormatCount; + private int skippedMissingChildReferenceCount; + private int skippedUnsupportedShapeCount; + private int skippedBodyTextPatternMissingCount; + private final ArrayList transformedCandidates = new ArrayList<>(); + private String firstTransformedNode = "none"; + private String firstExpression = "none"; + private String firstReplacement = "none"; + private String firstBlocker = "none"; + + private int candidateCount() { + return candidateKeys.size(); + } + + private void recordCandidate(StepCandidate candidate) { + candidateKeys.add(candidate.methodName() + "#" + candidate.rootNodeId()); + } + + private void add(RewriteStats other) { + typedBodyCount += other.typedBodyCount; + candidateKeys.addAll(other.candidateKeys); + transformedNodeCount += other.transformedNodeCount; + changedMethodBodyCount += other.changedMethodBodyCount; + bodyTextReplacementCount += other.bodyTextReplacementCount; + fixedPointPassCount += other.fixedPointPassCount; + directStepCount += other.directStepCount; + invertedStepCount += other.invertedStepCount; + skippedTypedBodyMissingCount += other.skippedTypedBodyMissingCount; + skippedUnsupportedFormatCount += other.skippedUnsupportedFormatCount; + skippedMissingChildReferenceCount += other.skippedMissingChildReferenceCount; + skippedUnsupportedShapeCount += other.skippedUnsupportedShapeCount; + skippedBodyTextPatternMissingCount += other.skippedBodyTextPatternMissingCount; + transformedCandidates.addAll(other.transformedCandidates); + if ("none".equals(firstTransformedNode) && !"none".equals(other.firstTransformedNode)) { + firstTransformedNode = other.firstTransformedNode; + firstExpression = other.firstExpression; + firstReplacement = other.firstReplacement; + } + if ("none".equals(firstBlocker) && !"none".equals(other.firstBlocker)) { + firstBlocker = other.firstBlocker; + } + } + + private void setFirstBlocker(String blocker) { + if ("none".equals(firstBlocker) && blocker != null && !blocker.isBlank() && !"none".equals(blocker)) { + firstBlocker = blocker; + } + } + } +} diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedDeadCodeMaterializationProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedDeadCodeMaterializationProposalProvider.java new file mode 100644 index 00000000..76bf03a8 --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedDeadCodeMaterializationProposalProvider.java @@ -0,0 +1,494 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuRegenerationMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Review-only typed dead-code materialization for unreachable pure typed IR nodes. + */ +public final class GpuIrTypedDeadCodeMaterializationProposalProvider implements GpuIrOptimizationProposalProvider { + + public static final String PROVIDER_ID = GpuIrOptimizerModule.MODULE_ID + ".typed-dead-code-materialization"; + public static final String PROVIDER_VERSION = PROVIDER_ID + ":1"; + + @Override + public GpuIrOptimizationProposal propose(GpuIrOptimizationProposalRequest request) { + IrGpuArtifact original = request.originalArtifact(); + ArtifactRewrite rewrite = ArtifactRewrite.from(original, request.mutationAllowed()); + if (!rewrite.changed()) { + return new GpuIrOptimizationProposal( + extensionId(), + extensionVersion(), + original, + Optional.empty(), + GpuIrOptimizationProposalDecision.NO_CHANGE, + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.typed-dead-code-materialization", + rewrite.verdict(), + rewrite.fields() + ), + "", + List.of(rewrite.diagnostic()) + ); + } + IrGpuArtifact optimized = copyWithModuleAndRegeneration( + original, + rewrite.module().orElseThrow(), + original.regenerationMetadata() + ); + return GpuIrOptimizationProposal.proposed( + extensionId(), + extensionVersion(), + original, + optimized, + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.typed-dead-code-materialization", + "materialized-review-candidate", + rewrite.fields() + ), + List.of("materialized " + rewrite.removedNodeCount() + + " unreachable pure typed node(s) into a review candidate") + ); + } + + @Override + public String extensionId() { + return PROVIDER_ID; + } + + @Override + public String extensionVersion() { + return PROVIDER_VERSION; + } + + @Override + public int extensionOrder() { + return 425; + } + + private record ArtifactRewrite( + Optional module, + int methodBodyCount, + int typedBodyCount, + int nodeCount, + int reachableNodeCount, + int unreachableNodeCount, + int removedNodeCount, + int changedMethodBodyCount, + int rootMissingCount, + int missingChildReferenceCount, + int sideEffectingUnreachableNodeCount, + Map unreachableKindCounts, + Map removedKindCounts, + List removedNodes, + String firstRemovedNode, + String firstBlocker, + boolean mutationAllowed + ) { + + private static ArtifactRewrite from(IrGpuArtifact artifact, boolean mutationAllowed) { + ArrayList methodBodies = new ArrayList<>(); + RewriteStats stats = new RewriteStats(); + stats.methodBodyCount = artifact.module().methodBodies().size(); + + for (IrGpuMethodBody methodBody : artifact.module().methodBodies()) { + MethodRewrite methodRewrite = rewriteMethodBody(methodBody); + methodBodies.add(methodRewrite.methodBody()); + stats.add(methodRewrite.stats()); + } + + if (stats.rootMissingCount > 0) { + stats.setFirstBlocker("missing-root-node"); + } + if (stats.missingChildReferenceCount > 0) { + stats.setFirstBlocker("missing-child-reference"); + } + if (stats.sideEffectingUnreachableNodeCount > 0) { + stats.setFirstBlocker("side-effecting-unreachable-node"); + } + if (!"none".equals(stats.firstBlocker)) { + return noChange(stats, mutationAllowed, stats.firstBlocker); + } + if (stats.removedNodeCount == 0) { + return noChange( + stats, + mutationAllowed, + stats.nodeCount == 0 ? "no-typed-nodes" : "no-removable-unreachable-pure-typed-nodes" + ); + } + + return new ArtifactRewrite( + Optional.of(copyWithBodies(artifact.module(), methodBodies)), + stats.methodBodyCount, + stats.typedBodyCount, + stats.nodeCount, + stats.reachableNodeCount, + stats.unreachableNodeCount, + stats.removedNodeCount, + stats.changedMethodBodyCount, + stats.rootMissingCount, + stats.missingChildReferenceCount, + stats.sideEffectingUnreachableNodeCount, + Map.copyOf(stats.unreachableKindCounts), + Map.copyOf(stats.removedKindCounts), + List.copyOf(stats.removedNodes), + stats.firstRemovedNode, + "none", + mutationAllowed + ); + } + + private static ArtifactRewrite noChange(RewriteStats stats, boolean mutationAllowed, String firstBlocker) { + return new ArtifactRewrite( + Optional.empty(), + stats.methodBodyCount, + stats.typedBodyCount, + stats.nodeCount, + stats.reachableNodeCount, + stats.unreachableNodeCount, + stats.removedNodeCount, + stats.changedMethodBodyCount, + stats.rootMissingCount, + stats.missingChildReferenceCount, + stats.sideEffectingUnreachableNodeCount, + Map.copyOf(stats.unreachableKindCounts), + Map.copyOf(stats.removedKindCounts), + List.copyOf(stats.removedNodes), + stats.firstRemovedNode, + firstBlocker, + mutationAllowed + ); + } + + private boolean changed() { + return module.isPresent() && removedNodeCount > 0; + } + + private String verdict() { + return changed() ? "materialized-review-candidate" : "materialization-no-change"; + } + + private String diagnostic() { + if (changed()) { + return "typed dead-code materialization removed " + removedNodeCount + " unreachable pure typed node(s)"; + } + return "typed dead-code materialization produced no review candidate: " + firstBlocker; + } + + private Map fields() { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("methodBody.count", Integer.toString(methodBodyCount)); + fields.put("methodBody.rewriteScope", "all-method-bodies"); + fields.put("typedBody.count", Integer.toString(typedBodyCount)); + fields.put("node.count", Integer.toString(nodeCount)); + fields.put("reachableNode.count", Integer.toString(reachableNodeCount)); + fields.put("unreachableNode.count", Integer.toString(unreachableNodeCount)); + fields.put("removedNode.count", Integer.toString(removedNodeCount)); + fields.put("changedMethodBody.count", Integer.toString(changedMethodBodyCount)); + fields.put("blocked.missingRoot.count", Integer.toString(rootMissingCount)); + fields.put("blocked.missingChildReference.count", Integer.toString(missingChildReferenceCount)); + fields.put("blocked.sideEffectingUnreachableNode.count", Integer.toString(sideEffectingUnreachableNodeCount)); + fields.put("unreachableKind.counts", countsSummary(unreachableKindCounts)); + fields.put("removedKind.counts", countsSummary(removedKindCounts)); + fields.put("rewrite.proposed", Boolean.toString(changed())); + fields.put("rewrite.materialized", Boolean.toString(changed())); + fields.put("optimizerFamily", "typed-dead-code-materialization"); + fields.put("mutationRequired", "false"); + fields.put("productionAffecting", "false"); + fields.put("previewOnly", "false"); + fields.put("provider.mutatesOriginal", "false"); + fields.put("policy.mutationAllowed", Boolean.toString(mutationAllowed)); + fields.put("policy.proposalOnly", Boolean.toString(!mutationAllowed)); + fields.put("policy.rollbackRequired", "true"); + fields.put("policy.proofRequired", "true"); + fields.put("proof.runtimeEquivalenceRequiredBeforeSelection", Boolean.toString(changed())); + fields.put("proof.runtimeEquivalencePayloadRequiredBeforeSelection", Boolean.toString(changed())); + fields.put("proof.approvalRequiredBeforeProduction", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.required", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.status", changed() ? "recorded" : "not-required"); + fields.put("runtimeEquivalencePayload.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.passed", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.firstBlocker", "none"); + fields.put("runtimeEquivalencePayload.comparisonMode", "typed-structure-unreachable-pure-node-removal"); + fields.put("runtimeEquivalencePayload.resource", "ir-optimizer://typed-dead-code-materialization/review-candidate"); + fields.put("runtimeEquivalencePayload.cpuReference.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.preOptimizationOutput.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.postOptimizationOutput.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.tolerance.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.failureFixture.present", Boolean.toString(changed())); + fields.put("runtimeEquivalencePayload.CpuReference", "removedUnreachablePureNodes=" + removedNodeCount); + fields.put("runtimeEquivalencePayload.PreOptimizationOutput", "typedNodes=" + nodeCount); + fields.put("runtimeEquivalencePayload.PostOptimizationOutput", "typedNodes=" + (nodeCount - removedNodeCount)); + fields.put("runtimeEquivalencePayload.Tolerance", "mode=typed-structure-no-runtime-output-change"); + fields.put("runtimeEquivalencePayload.FailureFixture", "none"); + fields.put("runtimeEquivalencePayload.ReferenceMode", "static-typed-reachability-pure-node-removal"); + fields.put("runtimeEquivalencePayload.CaseIdentity", "method-name-and-node-id"); + appendRuntimeEquivalenceCases(fields, removedNodes); + fields.put("safety.sideEffectFreedomProven", Boolean.toString(changed())); + fields.put("safety.reachabilityScope", "typed-body-root-materialization"); + fields.put("safety.removedNodeKindScope", "non-side-effecting-unreachable-typed-nodes"); + fields.put("firstRemovedNode", firstRemovedNode); + fields.put("firstBlocker", firstBlocker); + return Map.copyOf(fields); + } + + private static String countsSummary(Map counts) { + if (counts == null || counts.isEmpty()) { + return "{}"; + } + return counts.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .map(entry -> entry.getKey() + "=" + entry.getValue()) + .collect(Collectors.joining(",", "{", "}")); + } + } + + private record MethodRewrite(IrGpuMethodBody methodBody, RewriteStats stats) { + } + + private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody) { + RewriteStats stats = new RewriteStats(); + if (!methodBody.typedBody().available()) { + return new MethodRewrite(methodBody, stats); + } + stats.typedBodyCount = 1; + IrGpuTypedBody typedBody = methodBody.typedBody(); + Map nodesById = nodesById(typedBody); + Reachability reachability = reachableNodeIds(typedBody, nodesById); + stats.nodeCount = typedBody.nodes().size(); + stats.reachableNodeCount = reachability.reachableNodeIds().size(); + stats.rootMissingCount = reachability.rootMissingCount(); + stats.missingChildReferenceCount = reachability.missingChildReferenceCount(); + + ArrayList retainedNodes = new ArrayList<>(); + for (IrGpuTypedNode node : typedBody.nodes()) { + if (reachability.reachableNodeIds().contains(node.id())) { + retainedNodes.add(node); + continue; + } + stats.unreachableNodeCount++; + stats.unreachableKindCounts.merge(node.kind(), 1, Integer::sum); + if (isSideEffecting(node)) { + stats.sideEffectingUnreachableNodeCount++; + retainedNodes.add(node); + continue; + } + stats.removedNodeCount++; + stats.removedKindCounts.merge(node.kind(), 1, Integer::sum); + RemovedNode removedNode = new RemovedNode(methodBody.name(), node.id(), node.kind()); + stats.removedNodes.add(removedNode); + if ("none".equals(stats.firstRemovedNode)) { + stats.firstRemovedNode = removedNode.summary(); + } + } + if (stats.removedNodeCount == 0) { + return new MethodRewrite(methodBody, stats); + } + stats.changedMethodBodyCount = 1; + return new MethodRewrite( + copyWithTypedBody(methodBody, new IrGpuTypedBody(typedBody.format(), typedBody.rootNodeIds(), retainedNodes)), + stats + ); + } + + private record Reachability(Set reachableNodeIds, int rootMissingCount, int missingChildReferenceCount) { + } + + private record RemovedNode(String methodName, int nodeId, String nodeKind) { + private String summary() { + return methodName + "#" + nodeId + "=" + nodeKind; + } + } + + private static void appendRuntimeEquivalenceCases(LinkedHashMap fields, List removedNodes) { + fields.put("runtimeEquivalencePayload.Case.Count", Integer.toString(removedNodes.size())); + for (int index = 0; index < removedNodes.size(); index++) { + RemovedNode node = removedNodes.get(index); + String prefix = "runtimeEquivalencePayload.Case." + index; + fields.put(prefix + ".Name", node.summary() + "->removed"); + fields.put(prefix + ".MethodName", node.methodName()); + fields.put(prefix + ".NodeId", Integer.toString(node.nodeId())); + fields.put(prefix + ".RewriteKind", "typed-dead-code-removal"); + fields.put(prefix + ".Successful", "true"); + fields.put(prefix + ".Input.Count", "1"); + fields.put(prefix + ".Input.0.Name", "nodeKind"); + fields.put(prefix + ".Input.0.Value", node.nodeKind()); + fields.put(prefix + ".Output.Count", "1"); + fields.put(prefix + ".Output.0.Name", "nodeStatus"); + fields.put(prefix + ".Output.0.CpuReference", "removed-unreachable-pure-node"); + fields.put(prefix + ".Output.0.PreOptimization", "present"); + fields.put(prefix + ".Output.0.PostOptimization", "removed"); + fields.put(prefix + ".Output.0.Tolerance", "typed-structure"); + fields.put(prefix + ".Output.0.Equivalent", "true"); + fields.put(prefix + ".FailureFixture.Diagnostic.Count", "0"); + } + } + + private static Reachability reachableNodeIds(IrGpuTypedBody typedBody, Map nodesById) { + LinkedHashSet reachable = new LinkedHashSet<>(); + ArrayDeque pending = new ArrayDeque<>(); + int rootMissingCount = 0; + int missingChildReferenceCount = 0; + for (Integer rootNodeId : typedBody.rootNodeIds()) { + if (rootNodeId == null || !nodesById.containsKey(rootNodeId)) { + rootMissingCount++; + continue; + } + pending.add(rootNodeId); + } + while (!pending.isEmpty()) { + int nodeId = pending.removeFirst(); + if (!reachable.add(nodeId)) { + continue; + } + IrGpuTypedNode node = nodesById.get(nodeId); + if (node == null) { + continue; + } + for (List childIds : node.children().values()) { + for (Integer childId : childIds) { + if (childId == null || !nodesById.containsKey(childId)) { + missingChildReferenceCount++; + continue; + } + if (!reachable.contains(childId)) { + pending.add(childId); + } + } + } + } + return new Reachability(Set.copyOf(reachable), rootMissingCount, missingChildReferenceCount); + } + + private static boolean isSideEffecting(IrGpuTypedNode node) { + return List.of( + "GpuIrAssignment", + "GpuIrHelperCall", + "GpuIrIntrinsicCall", + "GpuIrReturn", + "GpuIrBreak", + "GpuIrContinue", + "GpuIrLoopBreak" + ).contains(node.kind()); + } + + private static Map nodesById(IrGpuTypedBody typedBody) { + LinkedHashMap nodes = new LinkedHashMap<>(); + for (IrGpuTypedNode node : typedBody.nodes()) { + nodes.put(node.id(), node); + } + return Map.copyOf(nodes); + } + + private static IrGpuMethodBody copyWithTypedBody(IrGpuMethodBody methodBody, IrGpuTypedBody typedBody) { + return new IrGpuMethodBody( + methodBody.role(), + methodBody.name(), + methodBody.emittedName(), + methodBody.format(), + methodBody.body(), + typedBody, + methodBody.bodyIndex(), + methodBody.helperDependencies(), + methodBody.sourceLocation() + ); + } + + private static IrGpuModule copyWithBodies(IrGpuModule module, List methodBodies) { + return new IrGpuModule( + module.entryMethod(), + module.entryEmittedName(), + module.entryOpenClAttributes(), + module.entryAttributeMetadata(), + module.helperMethods(), + module.structs(), + methodBodies + ); + } + + private static IrGpuArtifact copyWithModuleAndRegeneration( + IrGpuArtifact artifact, + IrGpuModule module, + IrGpuRegenerationMetadata regenerationMetadata + ) { + return new IrGpuArtifact( + artifact.header(), + module, + artifact.entryParameters(), + artifact.launchMetadata(), + artifact.validationMetadata(), + artifact.featureMetadata(), + artifact.optimizerPolicyMetadata(), + regenerationMetadata, + artifact.structMetadata(), + artifact.constants(), + artifact.constantData(), + artifact.backendOutputs(), + artifact.runtimeDefaultBackend(), + artifact.runtimeOptimizationProfile(), + artifact.methodDeviceConstraints(), + artifact.methodFallbackVariants(), + artifact.extensionParticipationMetadata() + ); + } + + private static final class RewriteStats { + private int methodBodyCount; + private int typedBodyCount; + private int nodeCount; + private int reachableNodeCount; + private int unreachableNodeCount; + private int removedNodeCount; + private int changedMethodBodyCount; + private int rootMissingCount; + private int missingChildReferenceCount; + private int sideEffectingUnreachableNodeCount; + private final LinkedHashMap unreachableKindCounts = new LinkedHashMap<>(); + private final LinkedHashMap removedKindCounts = new LinkedHashMap<>(); + private final ArrayList removedNodes = new ArrayList<>(); + private String firstRemovedNode = "none"; + private String firstBlocker = "none"; + + private void add(RewriteStats other) { + typedBodyCount += other.typedBodyCount; + nodeCount += other.nodeCount; + reachableNodeCount += other.reachableNodeCount; + unreachableNodeCount += other.unreachableNodeCount; + removedNodeCount += other.removedNodeCount; + changedMethodBodyCount += other.changedMethodBodyCount; + rootMissingCount += other.rootMissingCount; + missingChildReferenceCount += other.missingChildReferenceCount; + sideEffectingUnreachableNodeCount += other.sideEffectingUnreachableNodeCount; + other.unreachableKindCounts.forEach((kind, count) -> unreachableKindCounts.merge(kind, count, Integer::sum)); + other.removedKindCounts.forEach((kind, count) -> removedKindCounts.merge(kind, count, Integer::sum)); + removedNodes.addAll(other.removedNodes); + if ("none".equals(firstRemovedNode) && !"none".equals(other.firstRemovedNode)) { + firstRemovedNode = other.firstRemovedNode; + } + if ("none".equals(firstBlocker) && !"none".equals(other.firstBlocker)) { + firstBlocker = other.firstBlocker; + } + } + + private void setFirstBlocker(String blocker) { + if ("none".equals(firstBlocker) && blocker != null && !blocker.isBlank() && !"none".equals(blocker)) { + firstBlocker = blocker; + } + } + } +} diff --git a/ir-optimizer/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrOptimizationProposalProvider b/ir-optimizer/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrOptimizationProposalProvider index 5751f36c..11a27d8a 100644 --- a/ir-optimizer/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrOptimizationProposalProvider +++ b/ir-optimizer/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrOptimizationProposalProvider @@ -1,6 +1,15 @@ net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrNoOpProposalProvider net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrTextCanonicalizationProposalProvider net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrHelperDependencyDeduplicationProposalProvider +net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrBackendNeutralSourceMaterializationProposalProvider net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrConstantFoldingPreviewProposalProvider +net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrConstantFoldingMaterializationProposalProvider net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrSafeLocalCsePreviewProposalProvider +net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrSafeLocalCseMaterializationProposalProvider +net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrMadFmaMaterializationProposalProvider +net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrClampMaterializationProposalProvider +net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrStepMaterializationProposalProvider +net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrMixMaterializationProposalProvider net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrTypedDeadCodePreviewProposalProvider +net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrTypedDeadCodeMaterializationProposalProvider +net.sixik.ga_utils.javatogpu.iroptimizer.GpuIrLoopVectorizationMaterializationProposalProvider diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrBackendNeutralSourceMaterializationProposalProviderTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrBackendNeutralSourceMaterializationProposalProviderTest.java new file mode 100644 index 00000000..54a9b630 --- /dev/null +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrBackendNeutralSourceMaterializationProposalProviderTest.java @@ -0,0 +1,83 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuEntryParameter; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuIrBackendNeutralSourceMaterializationProposalProviderTest { + + @Test + void materializesReconstructableOpenClIrAsReviewCandidate() { + IrGpuArtifact original = artifact(); + + GpuIrOptimizationProposal proposal = new GpuIrBackendNeutralSourceMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of("backendTarget", "OPENCL") + )); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + assertFalse(original.regenerationMetadata().backendNeutralSourceReady()); + assertTrue(proposal.optimizedArtifact().orElseThrow().regenerationMetadata().backendNeutralSourceReady()); + assertEquals("backend-neutral-source-materialization", proposal.proofArtifact().fields().get("optimizerFamily")); + assertEquals("true", proposal.proofArtifact().fields().get("rewrite.proposed")); + assertEquals("true", proposal.proofArtifact().fields().get("rewrite.materialized")); + assertEquals("true", proposal.proofArtifact().fields().get("sourceGenerated")); + assertEquals("true", proposal.proofArtifact().fields().get("sourceReady")); + assertEquals("true", proposal.proofArtifact().fields().get("materializationOnly")); + assertEquals("false", proposal.proofArtifact().fields().get("productionAffecting")); + assertEquals("false", proposal.proofArtifact().fields().get("provider.mutatesOriginal")); + assertEquals("true", proposal.proofArtifact().fields().get("proof.approvalRequiredBeforeProduction")); + } + + @Test + void skipsNonOpenClRuntimeReview() { + IrGpuArtifact original = artifact(); + + GpuIrOptimizationProposal proposal = new GpuIrBackendNeutralSourceMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of("backendTarget", "CUDA") + )); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertTrue(proposal.diagnostics().get(0).contains("currently runs only for OpenCL")); + } + + private static IrGpuArtifact artifact() { + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + new IrGpuModule( + "kernel", + "kernel", + List.of(), + List.of(), + List.of(IrGpuMethodBody.entry( + "kernel", + "kernel", + "body\n set output[0] = 1\n", + List.of() + )) + ), + List.of(new IrGpuEntryParameter("output", "int[]", "GLOBAL", false, List.of())), + List.of(IrGpuBackendOutput.openClSource("javatogpu/sample/Demo/kernel.cl")), + "opencl", + "off" + ); + } +} diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrClampMaterializationProposalProviderTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrClampMaterializationProposalProviderTest.java new file mode 100644 index 00000000..febd08e5 --- /dev/null +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrClampMaterializationProposalProviderTest.java @@ -0,0 +1,151 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBodyIndex; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuEntryParameter; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuFeatureMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuLaunchMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuOptimizerPolicyMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuRegenerationMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuSourceLocation; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuValidationMetadata; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuIrClampMaterializationProposalProviderTest { + + @Test + void materializesGeneratedMinMaxShapeAsOpenClClampReviewCandidate() { + IrGpuArtifact original = artifact(); + + GpuIrOptimizationProposal proposal = new GpuIrClampMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of("backendTarget", "OPENCL") + )); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + assertSame(original, proposal.originalArtifact()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertEquals( + "body\n set output[0] = intrinsic(clamp template=\"\" args=[wave1, (-1.0F), 1.0F])\n", + optimized.module().methodBodies().get(0).body() + ); + IrGpuTypedNode replacement = optimized.module().methodBodies().get(0).typedBody().nodes().get(2); + assertEquals("GpuIrIntrinsicCall", replacement.kind()); + assertEquals("clamp", replacement.attributes().get("name")); + assertEquals("clamp", replacement.attributes().get("backendName")); + assertEquals(List.of(4, 5, 6), replacement.children().get("arguments")); + assertTrue(optimized.regenerationMetadata().backendNeutralSourceReady()); + + Map fields = proposal.proofArtifact().fields(); + assertEquals("clamp-materialization", fields.get("optimizerFamily")); + assertEquals("clamp", fields.get("targetIntrinsic")); + assertEquals("1", fields.get("candidate.count")); + assertEquals("1", fields.get("transformedNode.count")); + assertEquals("1", fields.get("bodyTextReplacement.count")); + assertEquals("1", fields.get("fixedPoint.pass.count")); + assertEquals("true", fields.get("rewrite.materialized")); + assertEquals("false", fields.get("provider.mutatesOriginal")); + assertEquals("false", fields.get("safety.fastMathRequired")); + assertEquals("true", fields.get("safety.strictFloatPreserved")); + assertEquals("true", fields.get("safety.argumentOrderPreserved")); + assertEquals("true", fields.get("runtimeEquivalencePayload.present")); + assertEquals("true", fields.get("runtimeEquivalencePayload.passed")); + assertEquals("static-min-max-to-clamp", fields.get("runtimeEquivalencePayload.ReferenceMode")); + assertEquals("1", fields.get("runtimeEquivalencePayload.Case.Count")); + assertEquals("min-max-to-opencl-clamp", fields.get("runtimeEquivalencePayload.Case.0.RewriteKind")); + assertEquals("wave1", fields.get("runtimeEquivalencePayload.Case.0.Input.1.Value")); + assertEquals("(-1.0F)", fields.get("runtimeEquivalencePayload.Case.0.Input.2.Value")); + assertEquals("1.0F", fields.get("runtimeEquivalencePayload.Case.0.Input.3.Value")); + assertEquals("true", fields.get("openClReview.sourceReady")); + assertEquals( + "kernel#2=intrinsic(min template=\"\" args=[intrinsic(max template=\"\" args=[wave1, (-1.0F)]), 1.0F])->intrinsic(clamp template=\"\" args=[wave1, (-1.0F), 1.0F])", + fields.get("firstTransformedNode") + ); + } + + @Test + void materializationProviderIsServiceLoaded() { + List providers = ServiceLoader + .load(GpuIrOptimizationProposalProvider.class) + .stream() + .map(ServiceLoader.Provider::get) + .filter(GpuIrClampMaterializationProposalProvider.class::isInstance) + .toList(); + + assertEquals(1, providers.size()); + } + + private static IrGpuArtifact artifact() { + IrGpuMethodBody methodBody = new IrGpuMethodBody( + "entry", + "kernel", + "kernel", + "ir-text-v1", + "body\n set output[0] = intrinsic(min template=\"\" args=[intrinsic(max template=\"\" args=[wave1, (-1.0F)]), 1.0F])\n", + typedBody(), + IrGpuBodyIndex.empty(), + List.of(), + IrGpuSourceLocation.unknown("kernel") + ); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + new IrGpuModule("kernel", "kernel", List.of(), List.of(), List.of(methodBody)), + List.of( + new IrGpuEntryParameter("output", "float[]", "GLOBAL", false, List.of()), + new IrGpuEntryParameter("wave1", "float", "PRIVATE", false, List.of()) + ), + IrGpuLaunchMetadata.defaultOneDimensional(), + IrGpuValidationMetadata.frontendSubset(), + IrGpuFeatureMetadata.none(), + IrGpuOptimizerPolicyMetadata.defaultStrict(), + IrGpuRegenerationMetadata.transitionalIrText(), + List.of(IrGpuBackendOutput.openClSource("javatogpu/test/Kernel/kernel.cl")), + "opencl", + "off" + ); + } + + private static IrGpuTypedBody typedBody() { + return new IrGpuTypedBody( + IrGpuTypedBody.FORMAT, + List.of(0), + List.of( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of( + "target", List.of(1), + "value", List.of(2) + )), + new IrGpuTypedNode(1, "GpuIrVariableRef", Map.of("name", "output[0]"), Map.of()), + new IrGpuTypedNode(2, "GpuIrIntrinsicCall", Map.of( + "name", "min", + "backendName", "min", + "codeTemplate", "" + ), Map.of("arguments", List.of(3, 6))), + new IrGpuTypedNode(3, "GpuIrIntrinsicCall", Map.of( + "name", "max", + "backendName", "max", + "codeTemplate", "" + ), Map.of("arguments", List.of(4, 5))), + new IrGpuTypedNode(4, "GpuIrVariableRef", Map.of("name", "wave1"), Map.of()), + new IrGpuTypedNode(5, "GpuIrLiteral", Map.of("sourceText", "(-1.0F)"), Map.of()), + new IrGpuTypedNode(6, "GpuIrLiteral", Map.of("sourceText", "1.0F"), Map.of()) + ) + ); + } +} diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingMaterializationProposalProviderTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingMaterializationProposalProviderTest.java new file mode 100644 index 00000000..16a376c0 --- /dev/null +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingMaterializationProposalProviderTest.java @@ -0,0 +1,970 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactIdentity; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBodyIndex; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuEntryParameter; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuSourceLocation; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuIrConstantFoldingMaterializationProposalProviderTest { + + @Test + void materializesIntegerLiteralBinaryFoldIntoTypedBodyAndTextBody() { + IrGpuArtifact original = artifact( + "body\n set output[0] = (2 + 3)\n", + typedBody( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(2), + "right", List.of(3) + )), + new IrGpuTypedNode(2, "GpuIrLiteral", Map.of("sourceText", "2"), Map.of()), + new IrGpuTypedNode(3, "GpuIrLiteral", Map.of("sourceText", "3"), Map.of()) + ) + ); + + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of("backendTarget", "OPENCL") + )); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertSame(original, proposal.originalArtifact()); + assertNotEquals(IrGpuArtifactIdentity.stableIdentity(original), IrGpuArtifactIdentity.stableIdentity(optimized)); + assertEquals("body\n set output[0] = 5\n", optimized.module().methodBodies().get(0).body()); + IrGpuTypedNode rewrittenNode = optimized.module().methodBodies().get(0).typedBody().nodes().get(1); + assertEquals("GpuIrLiteral", rewrittenNode.kind()); + assertEquals("5", rewrittenNode.attributes().get("sourceText")); + assertTrue(rewrittenNode.children().isEmpty()); + assertTrue(optimized.regenerationMetadata().backendNeutralSourceReady()); + assertEquals("1", proposal.proofArtifact().fields().get("candidate.count")); + assertEquals("1", proposal.proofArtifact().fields().get("transformedNode.count")); + assertEquals("1", proposal.proofArtifact().fields().get("literalRewrite.count")); + assertEquals("0", proposal.proofArtifact().fields().get("identityRewrite.count")); + assertEquals("1", proposal.proofArtifact().fields().get("bodyTextReplacement.count")); + assertEquals("true", proposal.proofArtifact().fields().get("rewrite.materialized")); + assertEquals("constant-folding-materialization", proposal.proofArtifact().fields().get("optimizerFamily")); + assertEquals("false", proposal.proofArtifact().fields().get("provider.mutatesOriginal")); + assertEquals("true", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.required")); + assertEquals("recorded", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.status")); + assertEquals("true", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.present")); + assertEquals("true", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.passed")); + assertEquals("none", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.firstBlocker")); + assertEquals("true", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.cpuReference.present")); + assertEquals("true", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.preOptimizationOutput.present")); + assertEquals("true", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.postOptimizationOutput.present")); + assertEquals("true", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.tolerance.present")); + assertEquals("true", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.failureFixture.present")); + assertEquals("static-exact-integer-fold", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.ReferenceMode")); + assertEquals("1", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.Count")); + assertEquals("kernel#1=2+3->5", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Name")); + assertEquals("2", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Input.0.Value")); + assertEquals("3", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Input.1.Value")); + assertEquals("5", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Output.0.CpuReference")); + assertEquals("(2 + 3)", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Output.0.PreOptimization")); + assertEquals("5", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Output.0.PostOptimization")); + assertEquals("true", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Output.0.Equivalent")); + assertEquals("true", proposal.proofArtifact().fields().get("openClReview.sourceReady")); + assertEquals("kernel#1=2+3->5", proposal.proofArtifact().fields().get("firstTransformedNode")); + assertEquals("(2 + 3)", proposal.proofArtifact().fields().get("firstFoldedExpression")); + assertEquals("5", proposal.proofArtifact().fields().get("firstFoldedValue")); + } + + @Test + void materializesUnaryMinusIntegerLiteralIntoTypedBodyAndTextBody() { + IrGpuArtifact original = artifact( + "body\n set output[0] = (-7)\n", + typedBody( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrUnary", Map.of("operator", "-"), Map.of("operand", List.of(2))), + new IrGpuTypedNode(2, "GpuIrLiteral", Map.of("sourceText", "7"), Map.of()) + ) + ); + + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of("backendTarget", "OPENCL") + )); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertEquals("body\n set output[0] = -7\n", optimized.module().methodBodies().get(0).body()); + IrGpuTypedNode rewrittenNode = optimized.module().methodBodies().get(0).typedBody().nodes().get(1); + assertEquals("GpuIrLiteral", rewrittenNode.kind()); + assertEquals("-7", rewrittenNode.attributes().get("sourceText")); + assertTrue(rewrittenNode.children().isEmpty()); + assertEquals("1", proposal.proofArtifact().fields().get("candidate.count")); + assertEquals("1", proposal.proofArtifact().fields().get("transformedNode.count")); + assertEquals("kernel#1=-7->-7", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Name")); + assertEquals("1", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Input.Count")); + assertEquals("operand", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Input.0.Name")); + assertEquals("7", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Input.0.Value")); + assertEquals("(-7)", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Output.0.PreOptimization")); + assertEquals("-7", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Output.0.PostOptimization")); + } + + @Test + void materializesNestedUnaryAndBinaryIntegerLiteralFoldsToFixedPoint() { + IrGpuArtifact original = artifact( + "body\n set output[0] = ((-2) * 4)\n", + typedBody( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", "*"), Map.of( + "left", List.of(2), + "right", List.of(4) + )), + new IrGpuTypedNode(2, "GpuIrUnary", Map.of("operator", "-"), Map.of("operand", List.of(3))), + new IrGpuTypedNode(3, "GpuIrLiteral", Map.of("sourceText", "2"), Map.of()), + new IrGpuTypedNode(4, "GpuIrLiteral", Map.of("sourceText", "4"), Map.of()) + ) + ); + + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of("backendTarget", "OPENCL") + )); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertEquals("body\n set output[0] = -8\n", optimized.module().methodBodies().get(0).body()); + IrGpuTypedNode rewrittenRoot = optimized.module().methodBodies().get(0).typedBody().nodes().get(1); + IrGpuTypedNode rewrittenUnary = optimized.module().methodBodies().get(0).typedBody().nodes().get(2); + assertEquals("GpuIrLiteral", rewrittenRoot.kind()); + assertEquals("-8", rewrittenRoot.attributes().get("sourceText")); + assertEquals("GpuIrLiteral", rewrittenUnary.kind()); + assertEquals("-2", rewrittenUnary.attributes().get("sourceText")); + assertEquals("2", proposal.proofArtifact().fields().get("candidate.count")); + assertEquals("2", proposal.proofArtifact().fields().get("transformedNode.count")); + assertEquals("2", proposal.proofArtifact().fields().get("fixedPoint.pass.count")); + assertEquals("2", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.Count")); + assertEquals("kernel#2=-2->-2", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Name")); + assertEquals("kernel#1=-2*4->-8", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.1.Name")); + assertEquals("(-2 * 4)", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.1.Output.0.PreOptimization")); + assertEquals("-8", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.1.Output.0.PostOptimization")); + } + + @Test + void materializesNestedIntegerLiteralFoldsToFixedPoint() { + IrGpuArtifact original = artifact( + "body\n set output[0] = ((2 + 3) * 4)\n", + typedBody( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", "*"), Map.of( + "left", List.of(2), + "right", List.of(4) + )), + new IrGpuTypedNode(2, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(3), + "right", List.of(5) + )), + new IrGpuTypedNode(3, "GpuIrLiteral", Map.of("sourceText", "2"), Map.of()), + new IrGpuTypedNode(4, "GpuIrLiteral", Map.of("sourceText", "4"), Map.of()), + new IrGpuTypedNode(5, "GpuIrLiteral", Map.of("sourceText", "3"), Map.of()) + ) + ); + + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of("backendTarget", "OPENCL") + )); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertEquals("body\n set output[0] = 20\n", optimized.module().methodBodies().get(0).body()); + IrGpuTypedNode rewrittenRoot = optimized.module().methodBodies().get(0).typedBody().nodes().get(1); + IrGpuTypedNode rewrittenNested = optimized.module().methodBodies().get(0).typedBody().nodes().get(2); + assertEquals("GpuIrLiteral", rewrittenRoot.kind()); + assertEquals("20", rewrittenRoot.attributes().get("sourceText")); + assertEquals("GpuIrLiteral", rewrittenNested.kind()); + assertEquals("5", rewrittenNested.attributes().get("sourceText")); + assertEquals("2", proposal.proofArtifact().fields().get("candidate.count")); + assertEquals("2", proposal.proofArtifact().fields().get("transformedNode.count")); + assertEquals("2", proposal.proofArtifact().fields().get("bodyTextReplacement.count")); + assertEquals("true", proposal.proofArtifact().fields().get("fixedPoint.enabled")); + assertEquals("2", proposal.proofArtifact().fields().get("fixedPoint.pass.count")); + assertEquals("0", proposal.proofArtifact().fields().get("skipped.nonLiteralOperand.count")); + assertEquals("2", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.Count")); + assertEquals("kernel#2=2+3->5", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Name")); + assertEquals("kernel#1=5*4->20", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.1.Name")); + assertEquals("(5 * 4)", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.1.Output.0.PreOptimization")); + assertEquals("20", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.1.Output.0.PostOptimization")); + } + + @Test + void materializesExactIntegerLiteralDivisionIntoTypedBodyAndTextBody() { + IrGpuArtifact original = binaryArtifact("body\n set output[0] = (8 / 2)\n", "/", "8", "2"); + + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of("backendTarget", "OPENCL") + )); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertEquals("body\n set output[0] = 4\n", optimized.module().methodBodies().get(0).body()); + IrGpuTypedNode rewrittenNode = optimized.module().methodBodies().get(0).typedBody().nodes().get(1); + assertEquals("GpuIrLiteral", rewrittenNode.kind()); + assertEquals("4", rewrittenNode.attributes().get("sourceText")); + assertTrue(rewrittenNode.children().isEmpty()); + assertTrue(optimized.regenerationMetadata().backendNeutralSourceReady()); + assertEquals("1", proposal.proofArtifact().fields().get("candidate.count")); + assertEquals("1", proposal.proofArtifact().fields().get("transformedNode.count")); + assertEquals("0", proposal.proofArtifact().fields().get("skipped.divideByZero.count")); + assertEquals("0", proposal.proofArtifact().fields().get("skipped.nonEvenDivision.count")); + assertEquals("kernel#1=8/2->4", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Name")); + assertEquals("(8 / 2)", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Output.0.PreOptimization")); + assertEquals("4", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Output.0.PostOptimization")); + assertEquals("plain-32bit-integer-literals-and-pure-symbolic-identities-unary-minus-plus-minus-multiply-exact-divide", + proposal.proofArtifact().fields().get("safety.scope")); + } + + @Test + void materializesAdditionIdentityWithRightZero() { + assertIdentityMaterializes("+", variable(2, "x"), literal(3, "0"), "(x + 0)", "x+0", "x"); + } + + @Test + void materializesAdditionIdentityWithLeftZero() { + assertIdentityMaterializes("+", literal(2, "0"), variable(3, "x"), "(0 + x)", "0+x", "x"); + } + + @Test + void materializesSubtractionIdentityWithRightZero() { + assertIdentityMaterializes("-", variable(2, "x"), literal(3, "0"), "(x - 0)", "x-0", "x"); + } + + @Test + void materializesMultiplicationIdentityWithRightOne() { + assertIdentityMaterializes("*", variable(2, "x"), literal(3, "1"), "(x * 1)", "x*1", "x"); + } + + @Test + void materializesMultiplicationIdentityWithLeftOne() { + assertIdentityMaterializes("*", literal(2, "1"), variable(3, "x"), "(1 * x)", "1*x", "x"); + } + + @Test + void materializesDivisionIdentityWithRightOne() { + assertIdentityMaterializes("/", variable(2, "x"), literal(3, "1"), "(x / 1)", "x/1", "x"); + } + + @Test + void materializesAdditionIdentityWithPureBinaryRetainedExpression() { + IrGpuArtifact original = valueArtifact( + "body\n set output[0] = ((a + b) + 0)\n", + typedBody( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(2), + "right", List.of(5) + )), + new IrGpuTypedNode(2, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(3), + "right", List.of(4) + )), + variable(3, "a"), + variable(4, "b"), + literal(5, "0") + ), + "a", + "b" + ); + + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of("backendTarget", "OPENCL") + )); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertEquals("body\n set output[0] = (a + b)\n", optimized.module().methodBodies().get(0).body()); + IrGpuTypedNode rewrittenNode = optimized.module().methodBodies().get(0).typedBody().nodes().get(1); + assertEquals("GpuIrBinary", rewrittenNode.kind()); + assertEquals("+", rewrittenNode.attributes().get("operator")); + assertEquals(List.of(3), rewrittenNode.children().get("left")); + assertEquals(List.of(4), rewrittenNode.children().get("right")); + assertEquals("0", proposal.proofArtifact().fields().get("literalRewrite.count")); + assertEquals("1", proposal.proofArtifact().fields().get("identityRewrite.count")); + assertEquals("kernel#1=(a + b)+0->(a + b)", + proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Name")); + assertEquals("((a + b) + 0)", + proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Output.0.PreOptimization")); + assertEquals("(a + b)", + proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Output.0.PostOptimization")); + assertEquals("GpuIrPureExpression", proposal.proofArtifact().fields().get("safety.identityOperandKind")); + assertEquals("true", proposal.proofArtifact().fields().get("openClReview.sourceReady")); + } + + @Test + void materializesDivisionIdentityWithPureBinaryRetainedExpression() { + IrGpuArtifact original = valueArtifact( + "body\n set output[0] = ((a + b) / 1)\n", + typedBody( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", "/"), Map.of( + "left", List.of(2), + "right", List.of(5) + )), + new IrGpuTypedNode(2, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(3), + "right", List.of(4) + )), + variable(3, "a"), + variable(4, "b"), + literal(5, "1") + ), + "a", + "b" + ); + + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of("backendTarget", "OPENCL") + )); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertEquals("body\n set output[0] = (a + b)\n", optimized.module().methodBodies().get(0).body()); + IrGpuTypedNode rewrittenNode = optimized.module().methodBodies().get(0).typedBody().nodes().get(1); + assertEquals("GpuIrBinary", rewrittenNode.kind()); + assertEquals("+", rewrittenNode.attributes().get("operator")); + assertEquals(List.of(3), rewrittenNode.children().get("left")); + assertEquals(List.of(4), rewrittenNode.children().get("right")); + assertEquals("0", proposal.proofArtifact().fields().get("literalRewrite.count")); + assertEquals("1", proposal.proofArtifact().fields().get("identityRewrite.count")); + assertEquals("kernel#1=(a + b)/1->(a + b)", + proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Name")); + assertEquals("((a + b) / 1)", + proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Output.0.PreOptimization")); + assertEquals("(a + b)", + proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Output.0.PostOptimization")); + assertEquals("GpuIrPureExpression", proposal.proofArtifact().fields().get("safety.identityOperandKind")); + assertEquals("true", proposal.proofArtifact().fields().get("openClReview.sourceReady")); + } + + @Test + void materializesMixedIdentityChainToFixedPoint() { + IrGpuArtifact original = valueArtifact( + "body\n set output[0] = ((x / 1) + 0)\n", + typedBody( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(2), + "right", List.of(5) + )), + new IrGpuTypedNode(2, "GpuIrBinary", Map.of("operator", "/"), Map.of( + "left", List.of(3), + "right", List.of(4) + )), + variable(3, "x"), + literal(4, "1"), + literal(5, "0") + ) + ); + + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of("backendTarget", "OPENCL") + )); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertEquals("body\n set output[0] = x\n", optimized.module().methodBodies().get(0).body()); + IrGpuTypedNode rewrittenRoot = optimized.module().methodBodies().get(0).typedBody().nodes().get(1); + assertEquals("GpuIrVariableRef", rewrittenRoot.kind()); + assertEquals("x", rewrittenRoot.attributes().get("name")); + assertTrue(rewrittenRoot.children().isEmpty()); + assertEquals("2", proposal.proofArtifact().fields().get("candidate.count")); + assertEquals("2", proposal.proofArtifact().fields().get("identityRewrite.count")); + assertEquals("2", proposal.proofArtifact().fields().get("fixedPoint.pass.count")); + assertEquals("0", proposal.proofArtifact().fields().get("skipped.bodyTextPatternMissing.count")); + assertEquals("2", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.Count")); + assertEquals("kernel#1=(x / 1)+0->(x / 1)", + proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Name")); + assertEquals("kernel#1=x/1->x", + proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.1.Name")); + assertEquals("x", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.1.Output.0.PostOptimization")); + assertEquals("true", proposal.proofArtifact().fields().get("openClReview.sourceReady")); + } + + @Test + void materializesRepeatedIdentityExpressionsAcrossMultipleRootsWithoutCrossingTextReplacements() { + IrGpuArtifact original = valueArtifact( + "body\n set output[0] = (x + 0)\n set output[1] = (x + 0)\n", + typedBody( + List.of(0, 4), + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(2), + "right", List.of(3) + )), + variable(2, "x"), + literal(3, "0"), + new IrGpuTypedNode(4, "GpuIrAssignment", Map.of(), Map.of("value", List.of(5))), + new IrGpuTypedNode(5, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(6), + "right", List.of(7) + )), + variable(6, "x"), + literal(7, "0") + ) + ); + + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of("backendTarget", "OPENCL") + )); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertEquals("body\n set output[0] = x\n set output[1] = x\n", + optimized.module().methodBodies().get(0).body()); + IrGpuTypedNode firstRewrittenValue = optimized.module().methodBodies().get(0).typedBody().nodes().get(1); + IrGpuTypedNode secondRewrittenValue = optimized.module().methodBodies().get(0).typedBody().nodes().get(5); + assertEquals("GpuIrVariableRef", firstRewrittenValue.kind()); + assertEquals("x", firstRewrittenValue.attributes().get("name")); + assertEquals("GpuIrVariableRef", secondRewrittenValue.kind()); + assertEquals("x", secondRewrittenValue.attributes().get("name")); + assertEquals("2", proposal.proofArtifact().fields().get("candidate.count")); + assertEquals("2", proposal.proofArtifact().fields().get("transformedNode.count")); + assertEquals("2", proposal.proofArtifact().fields().get("identityRewrite.count")); + assertEquals("2", proposal.proofArtifact().fields().get("bodyTextReplacement.count")); + assertEquals("2", proposal.proofArtifact().fields().get("fixedPoint.pass.count")); + assertEquals("true", proposal.proofArtifact().fields().get("fixedPoint.reachableNodeScan")); + assertEquals("single-reachable-candidate-per-pass", + proposal.proofArtifact().fields().get("fixedPoint.rewriteGranularity")); + assertEquals("reachable-typed-nodes", proposal.proofArtifact().fields().get("bodyTextReplacement.scope")); + assertEquals("0", proposal.proofArtifact().fields().get("skipped.bodyTextPatternMissing.count")); + assertEquals("2", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.Count")); + assertEquals("kernel#1=x+0->x", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Name")); + assertEquals("kernel#5=x+0->x", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.1.Name")); + assertEquals("true", proposal.proofArtifact().fields().get("openClReview.sourceReady")); + } + + @Test + void materializesMultipleMethodBodiesAndKeepsRuntimeEquivalenceCasesScoped() { + IrGpuArtifact original = artifact( + List.of( + methodBody( + "entry", + "kernel", + "kernel", + "body\n set output[0] = (2 + 3)\n", + typedBody( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(2), + "right", List.of(3) + )), + literal(2, "2"), + literal(3, "3") + ) + ), + methodBody( + "helper", + "helperScale", + "helperScale", + "body\n set output[0] = (x * 1)\n", + typedBody( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", "*"), Map.of( + "left", List.of(2), + "right", List.of(3) + )), + variable(2, "x"), + literal(3, "1") + ) + ) + ), + List.of( + new IrGpuEntryParameter("output", "int[]", "GLOBAL", false, List.of()), + new IrGpuEntryParameter("x", "int", "PRIVATE", false, List.of()) + ) + ); + + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertEquals("body\n set output[0] = 5\n", optimized.module().methodBodies().get(0).body()); + assertEquals("body\n set output[0] = x\n", optimized.module().methodBodies().get(1).body()); + assertEquals("GpuIrLiteral", optimized.module().methodBodies().get(0).typedBody().nodes().get(1).kind()); + assertEquals("5", optimized.module().methodBodies().get(0).typedBody().nodes().get(1).attributes().get("sourceText")); + assertEquals("GpuIrVariableRef", optimized.module().methodBodies().get(1).typedBody().nodes().get(1).kind()); + assertEquals("x", optimized.module().methodBodies().get(1).typedBody().nodes().get(1).attributes().get("name")); + assertEquals("2", proposal.proofArtifact().fields().get("methodBody.count")); + assertEquals("all-method-bodies", proposal.proofArtifact().fields().get("methodBody.rewriteScope")); + assertEquals("2", proposal.proofArtifact().fields().get("typedBody.count")); + assertEquals("2", proposal.proofArtifact().fields().get("changedMethodBody.count")); + assertEquals("2", proposal.proofArtifact().fields().get("candidate.count")); + assertEquals("1", proposal.proofArtifact().fields().get("literalRewrite.count")); + assertEquals("1", proposal.proofArtifact().fields().get("identityRewrite.count")); + assertEquals("2", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.Count")); + assertEquals("method-name-and-node-id", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.CaseIdentity")); + assertEquals("kernel", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.MethodName")); + assertEquals("1", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.NodeId")); + assertEquals("literal", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.RewriteKind")); + assertEquals("kernel#1=2+3->5", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Name")); + assertEquals("helperScale", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.1.MethodName")); + assertEquals("1", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.1.NodeId")); + assertEquals("identity", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.1.RewriteKind")); + assertEquals("helperScale#1=x*1->x", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.1.Name")); + assertEquals("false", proposal.proofArtifact().fields().get("openClReview.sourceReady")); + assertEquals("not-requested", proposal.proofArtifact().fields().get("openClReview.sourceLength")); + } + + @Test + void materializesSubtractionIdentityWithPureUnaryRetainedExpression() { + IrGpuArtifact original = valueArtifact( + "body\n set output[0] = ((-a) - 0)\n", + typedBody( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", "-"), Map.of( + "left", List.of(2), + "right", List.of(4) + )), + new IrGpuTypedNode(2, "GpuIrUnary", Map.of("operator", "-"), Map.of("operand", List.of(3))), + variable(3, "a"), + literal(4, "0") + ), + "a" + ); + + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of("backendTarget", "OPENCL") + )); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertEquals("body\n set output[0] = (-a)\n", optimized.module().methodBodies().get(0).body()); + IrGpuTypedNode rewrittenNode = optimized.module().methodBodies().get(0).typedBody().nodes().get(1); + assertEquals("GpuIrUnary", rewrittenNode.kind()); + assertEquals("-", rewrittenNode.attributes().get("operator")); + assertEquals(List.of(3), rewrittenNode.children().get("operand")); + assertEquals("kernel#1=(-a)-0->(-a)", + proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Name")); + } + + @Test + void doesNotMaterializeIdentityWhenRetainedExpressionIsImpure() { + IrGpuArtifact original = valueArtifact( + "body\n set output[0] = (helper(sideEffect args=[]) + 0)\n", + typedBody( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(2), + "right", List.of(3) + )), + new IrGpuTypedNode(2, "GpuIrHelperCall", Map.of("helper", "sideEffect"), Map.of()), + literal(3, "0") + ) + ); + + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertFalse(proposal.hasOptimizedArtifact()); + assertTrue(proposal.diagnostics().get(0).contains("non-literal-operand")); + } + + @Test + void doesNotMaterializeIdentityWhenRetainedLiteralIsNotPlainInt32() { + IrGpuArtifact original = valueArtifact( + "body\n set output[0] = (0 + 1.5)\n", + typedBody( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(2), + "right", List.of(3) + )), + literal(2, "0"), + literal(3, "1.5") + ) + ); + + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertFalse(proposal.hasOptimizedArtifact()); + assertTrue(proposal.diagnostics().get(0).contains("non-literal-operand")); + } + + @Test + void doesNotMaterializeDivisionIdentityWithLeftOne() { + IrGpuArtifact original = valueArtifact( + "body\n set output[0] = (1 / x)\n", + typedBody( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", "/"), Map.of( + "left", List.of(2), + "right", List.of(3) + )), + literal(2, "1"), + variable(3, "x") + ) + ); + + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertFalse(proposal.hasOptimizedArtifact()); + assertTrue(proposal.diagnostics().get(0).contains("non-literal-operand")); + } + + @Test + void doesNotMaterializeMultiplicationByZeroWithoutSideEffectProof() { + IrGpuArtifact original = valueArtifact( + "body\n set output[0] = (x * 0)\n", + typedBody( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", "*"), Map.of( + "left", List.of(2), + "right", List.of(3) + )), + variable(2, "x"), + literal(3, "0") + ) + ); + + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertFalse(proposal.hasOptimizedArtifact()); + assertTrue(proposal.diagnostics().get(0).contains("non-literal-operand")); + } + + @Test + void skipsIntegerLiteralDivisionByZero() { + IrGpuArtifact original = binaryArtifact("body\n set output[0] = (8 / 0)\n", "/", "8", "0"); + + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertFalse(proposal.hasOptimizedArtifact()); + assertTrue(proposal.diagnostics().get(0).contains("division-by-zero")); + } + + @Test + void skipsIntegerLiteralDivisionWhenResultIsNotEven() { + IrGpuArtifact original = binaryArtifact("body\n set output[0] = (5 / 2)\n", "/", "5", "2"); + + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertFalse(proposal.hasOptimizedArtifact()); + assertTrue(proposal.diagnostics().get(0).contains("non-even-division")); + } + + @Test + void skipsIntegerLiteralDivisionWhenFoldedValueOverflowsInt32() { + IrGpuArtifact original = binaryArtifact( + "body\n set output[0] = (-2147483648 / -1)\n", + "/", + "-2147483648", + "-1" + ); + + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertFalse(proposal.hasOptimizedArtifact()); + assertTrue(proposal.diagnostics().get(0).contains("integer-overflow-risk")); + } + + @Test + void skipsWhenTextBodyCannotBeRewrittenConsistently() { + IrGpuArtifact original = artifact( + "body\n return value\n", + typedBody( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", "*"), Map.of( + "left", List.of(2), + "right", List.of(3) + )), + new IrGpuTypedNode(2, "GpuIrLiteral", Map.of("sourceText", "4"), Map.of()), + new IrGpuTypedNode(3, "GpuIrLiteral", Map.of("sourceText", "5"), Map.of()) + ) + ); + + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertFalse(proposal.hasOptimizedArtifact()); + assertTrue(proposal.diagnostics().get(0).contains("body-text-pattern-missing")); + } + + @Test + void skipsDecimalLiteralsUntilFloatingProofExists() { + IrGpuArtifact original = artifact( + "body\n set output[0] = (1.5 + 2)\n", + typedBody( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(2), + "right", List.of(3) + )), + new IrGpuTypedNode(2, "GpuIrLiteral", Map.of("sourceText", "1.5"), Map.of()), + new IrGpuTypedNode(3, "GpuIrLiteral", Map.of("sourceText", "2"), Map.of()) + ) + ); + + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertFalse(proposal.hasOptimizedArtifact()); + assertTrue(proposal.diagnostics().get(0).contains("non-integer-literal")); + } + + @Test + void validationSandwichCanSelectMaterializedFoldWhenMutationIsExplicitlyAllowed() { + IrGpuArtifact original = artifact( + "body\n set output[0] = (4 * 5)\n", + typedBody( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", "*"), Map.of( + "left", List.of(2), + "right", List.of(3) + )), + new IrGpuTypedNode(2, "GpuIrLiteral", Map.of("sourceText", "4"), Map.of()), + new IrGpuTypedNode(3, "GpuIrLiteral", Map.of("sourceText", "5"), Map.of()) + ) + ); + + GpuIrOptimizationSandwichReport report = GpuIrOptimizationSandwichRunner.alwaysValid() + .run(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + true, + Map.of("backendTarget", "OPENCL") + ), + new GpuIrConstantFoldingMaterializationProposalProvider()); + + assertEquals(GpuIrOptimizationSandwichStatus.OPTIMIZED_SELECTED, report.status()); + assertEquals("body\n set output[0] = 20\n", report.selectedArtifact().module().methodBodies().get(0).body()); + assertTrue(report.selectedOptimized()); + assertFalse(report.selectedOriginal()); + } + + @Test + void materializationProviderIsServiceLoaded() { + List providers = ServiceLoader + .load(GpuIrOptimizationProposalProvider.class) + .stream() + .map(ServiceLoader.Provider::get) + .filter(GpuIrConstantFoldingMaterializationProposalProvider.class::isInstance) + .toList(); + + assertEquals(1, providers.size()); + } + + private static IrGpuTypedBody typedBody(IrGpuTypedNode... nodes) { + return new IrGpuTypedBody(IrGpuTypedBody.FORMAT, List.of(nodes[0].id()), List.of(nodes)); + } + + private static IrGpuTypedBody typedBody(List rootNodeIds, IrGpuTypedNode... nodes) { + return new IrGpuTypedBody(IrGpuTypedBody.FORMAT, rootNodeIds, List.of(nodes)); + } + + private static IrGpuArtifact binaryArtifact(String body, String operator, String left, String right) { + return artifact( + body, + typedBody( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", operator), Map.of( + "left", List.of(2), + "right", List.of(3) + )), + new IrGpuTypedNode(2, "GpuIrLiteral", Map.of("sourceText", left), Map.of()), + new IrGpuTypedNode(3, "GpuIrLiteral", Map.of("sourceText", right), Map.of()) + ) + ); + } + + private static void assertIdentityMaterializes( + String operator, + IrGpuTypedNode left, + IrGpuTypedNode right, + String expression, + String expressionSummary, + String foldedValue + ) { + IrGpuArtifact original = valueArtifact( + "body\n set output[0] = " + expression + "\n", + typedBody( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", operator), Map.of( + "left", List.of(left.id()), + "right", List.of(right.id()) + )), + left, + right + ) + ); + + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of("backendTarget", "OPENCL") + )); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertEquals("body\n set output[0] = " + foldedValue + "\n", optimized.module().methodBodies().get(0).body()); + IrGpuTypedNode rewrittenNode = optimized.module().methodBodies().get(0).typedBody().nodes().get(1); + assertEquals("GpuIrVariableRef", rewrittenNode.kind()); + assertEquals("x", rewrittenNode.attributes().get("name")); + assertTrue(rewrittenNode.children().isEmpty()); + assertTrue(optimized.regenerationMetadata().backendNeutralSourceReady()); + assertEquals("1", proposal.proofArtifact().fields().get("candidate.count")); + assertEquals("1", proposal.proofArtifact().fields().get("transformedNode.count")); + assertEquals("0", proposal.proofArtifact().fields().get("literalRewrite.count")); + assertEquals("1", proposal.proofArtifact().fields().get("identityRewrite.count")); + assertEquals("1", proposal.proofArtifact().fields().get("bodyTextReplacement.count")); + assertEquals("static-exact-integer-symbolic-identity-fold", + proposal.proofArtifact().fields().get("runtimeEquivalencePayload.ReferenceMode")); + assertEquals("kernel#1=" + expressionSummary + "->" + foldedValue, + proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Name")); + assertEquals(expression, proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Output.0.PreOptimization")); + assertEquals(foldedValue, proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.Output.0.PostOptimization")); + assertEquals("GpuIrPureExpression", proposal.proofArtifact().fields().get("safety.identityOperandKind")); + assertEquals("GpuIrVariableRef,GpuIrInt32Literal,GpuIrBinary,GpuIrUnary", + proposal.proofArtifact().fields().get("safety.identityRetainedExpressionKinds")); + assertEquals("true", proposal.proofArtifact().fields().get("openClReview.sourceReady")); + } + + private static IrGpuTypedNode literal(int id, String sourceText) { + return new IrGpuTypedNode(id, "GpuIrLiteral", Map.of("sourceText", sourceText), Map.of()); + } + + private static IrGpuTypedNode variable(int id, String name) { + return new IrGpuTypedNode(id, "GpuIrVariableRef", Map.of("name", name), Map.of()); + } + + private static IrGpuArtifact valueArtifact(String body, IrGpuTypedBody typedBody) { + return valueArtifact(body, typedBody, "x"); + } + + private static IrGpuArtifact valueArtifact(String body, IrGpuTypedBody typedBody, String... privateScalarParameters) { + java.util.ArrayList entryParameters = new java.util.ArrayList<>(); + entryParameters.add(new IrGpuEntryParameter("output", "int[]", "GLOBAL", false, List.of())); + for (String parameter : privateScalarParameters) { + entryParameters.add(new IrGpuEntryParameter(parameter, "int", "PRIVATE", false, List.of())); + } + return artifact( + body, + typedBody, + entryParameters + ); + } + + private static IrGpuArtifact artifact(String body, IrGpuTypedBody typedBody) { + return artifact( + body, + typedBody, + List.of(new IrGpuEntryParameter("output", "int[]", "GLOBAL", false, List.of())) + ); + } + + private static IrGpuArtifact artifact(String body, IrGpuTypedBody typedBody, List entryParameters) { + IrGpuMethodBody methodBody = methodBody( + "entry", + "kernel", + "kernel", + body, + typedBody + ); + return artifact(List.of(methodBody), entryParameters); + } + + private static IrGpuArtifact artifact(List methodBodies, List entryParameters) { + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + new IrGpuModule("kernel", "kernel", List.of(), List.of(), methodBodies), + entryParameters, + List.of(IrGpuBackendOutput.openClSource("javatogpu/test/Kernel/kernel.cl")), + "opencl", + "off" + ); + } + + private static IrGpuMethodBody methodBody( + String role, + String name, + String emittedName, + String body, + IrGpuTypedBody typedBody + ) { + return new IrGpuMethodBody( + role, + name, + emittedName, + "ir-text-v1", + body, + typedBody, + IrGpuBodyIndex.empty(), + List.of(), + IrGpuSourceLocation.unknown(name) + ); + } +} diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrLoopVectorizationMaterializationProposalProviderTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrLoopVectorizationMaterializationProposalProviderTest.java new file mode 100644 index 00000000..5fbe4818 --- /dev/null +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrLoopVectorizationMaterializationProposalProviderTest.java @@ -0,0 +1,277 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBodyIndex; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuEntryParameter; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuFeatureMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuLaunchMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuOptimizerPolicyMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuRegenerationMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuSourceLocation; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuValidationMetadata; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuIrLoopVectorizationMaterializationProposalProviderTest { + + @Test + void materializesFixedWidthContiguousFloatReductionAsVload4ReviewCandidate() { + IrGpuArtifact original = artifact(""" + body + var int id = intrinsic(get_global_id template="" args=[0]) + var float sum = 0.0F + for init=(var int i = 0) cond=(i < 4) update=(set i = (i + 1)) + set sum = (sum + input[((id * 4) + i)]) + set output[id] = sum + """); + + GpuIrOptimizationProposal proposal = new GpuIrLoopVectorizationMaterializationProposalProvider() + .propose(openClRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + assertSame(original, proposal.originalArtifact()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertEquals(""" + body + var int id = intrinsic(get_global_id template="" args=[0]) + var Float4 sum_vec4 = intrinsic(vload4 template="" args=[0, (&input[(id * 4)])]) + var float sum = ((((0.0F + sum_vec4.x) + sum_vec4.y) + sum_vec4.z) + sum_vec4.w) + set output[id] = sum + """, optimized.module().methodBodies().get(0).body()); + IrGpuTypedBody optimizedTypedBody = optimized.module().methodBodies().get(0).typedBody(); + assertTrue(optimizedTypedBody.available()); + assertTrue(optimizedTypedBody.nodes().stream().anyMatch(node -> "GpuIrIntrinsicCall".equals(node.kind()) + && "vload4".equals(node.attributes().get("backendName")) + && "Float4".equals(node.attributes().get("resultType")))); + assertTrue(optimizedTypedBody.nodes().stream().anyMatch(node -> "GpuIrFieldAccess".equals(node.kind()) + && "w".equals(node.attributes().get("fieldName")))); + assertTrue(optimized.regenerationMetadata().backendNeutralSourceReady()); + + Map fields = proposal.proofArtifact().fields(); + assertEquals("loop-vectorization-materialization", fields.get("optimizerFamily")); + assertEquals("4", fields.get("vectorWidth")); + assertEquals("Float4", fields.get("vectorType")); + assertEquals("vload4", fields.get("loadIntrinsic")); + assertEquals("ordered-scalar-float-sum", fields.get("reductionKind")); + assertEquals("1", fields.get("candidate.count")); + assertEquals("1", fields.get("transformedLoop.count")); + assertEquals("1", fields.get("bodyTextReplacement.count")); + assertEquals("1", fields.get("typedBody.materialized.count")); + assertEquals("0", fields.get("typedBody.invalidated.count")); + assertEquals("true", fields.get("rewrite.materialized")); + assertEquals("false", fields.get("provider.mutatesOriginal")); + assertEquals("true", fields.get("runtimeEquivalencePayload.present")); + assertEquals("true", fields.get("runtimeEquivalencePayload.passed")); + assertEquals("static-vload4-ordered-scalar-reduction", fields.get("runtimeEquivalencePayload.ReferenceMode")); + assertEquals("1", fields.get("runtimeEquivalencePayload.Case.Count")); + assertEquals( + "fixed-width-contiguous-vload4-ordered-reduction", + fields.get("runtimeEquivalencePayload.Case.0.RewriteKind") + ); + assertEquals("true", fields.get("safety.orderedReductionPreserved")); + assertEquals("false", fields.get("safety.reassociationRequired")); + assertEquals("false", fields.get("safety.fastMathRequired")); + assertEquals("true", fields.get("safety.typedBodyMaterializedForReview")); + assertEquals("false", fields.get("safety.typedBodyInvalidatedForReview")); + assertEquals("true", fields.get("openClReview.sourceReady")); + assertTrue(fields.get("firstReplacement").contains("intrinsic(vload4 template=\"\" args=[0, (&input[(id * 4)])])")); + } + + @Test + void materializesMadIndexedContiguousFloatReductionAsSameVload4ReviewCandidate() { + IrGpuArtifact original = artifact(""" + body + var int id = intrinsic(get_global_id template="" args=[0]) + var float sum = 0.0f + for init=(var int i = 0) cond=(i < 4) update=(set i = (i + 1)) + set sum = (sum + input[intrinsic(mad template="" args=[id, 4, i])]) + set output[id] = sum + """); + + GpuIrOptimizationProposal proposal = new GpuIrLoopVectorizationMaterializationProposalProvider() + .propose(openClRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + assertEquals(""" + body + var int id = intrinsic(get_global_id template="" args=[0]) + var Float4 sum_vec4 = intrinsic(vload4 template="" args=[0, (&input[(id * 4)])]) + var float sum = ((((0.0f + sum_vec4.x) + sum_vec4.y) + sum_vec4.z) + sum_vec4.w) + set output[id] = sum + """, proposal.optimizedArtifact().orElseThrow().module().methodBodies().get(0).body()); + assertEquals("1", proposal.proofArtifact().fields().get("transformedLoop.count")); + assertEquals("intrinsic(mad template=\"\" args=[id, 4, i])", proposal.proofArtifact().fields() + .get("runtimeEquivalencePayload.Case.0.Input.3.Value")); + } + + @Test + void materializesPrecomputedContiguousBaseTempAsVload4ReviewCandidate() { + IrGpuArtifact original = artifact(""" + body + var int id = intrinsic(get_global_id template="" args=[0]) + var int base = (id * 4) + var float sum = 0.0F + for init=(var int i = 0) cond=(i < 4) update=(set i = (i + 1)) + set sum = (sum + input[(base + i)]) + set output[id] = sum + """); + + GpuIrOptimizationProposal proposal = new GpuIrLoopVectorizationMaterializationProposalProvider() + .propose(openClRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + assertEquals(""" + body + var int id = intrinsic(get_global_id template="" args=[0]) + var int base = (id * 4) + var Float4 sum_vec4 = intrinsic(vload4 template="" args=[0, (&input[base])]) + var float sum = ((((0.0F + sum_vec4.x) + sum_vec4.y) + sum_vec4.z) + sum_vec4.w) + set output[id] = sum + """, proposal.optimizedArtifact().orElseThrow().module().methodBodies().get(0).body()); + assertTrue(proposal.optimizedArtifact().orElseThrow().module().methodBodies().get(0).typedBody().available()); + assertEquals("(base + i)", proposal.proofArtifact().fields() + .get("runtimeEquivalencePayload.Case.0.Input.3.Value")); + assertTrue(proposal.proofArtifact().fields().get("firstReplacement").contains("(&input[base])")); + } + + @Test + void materializesConstantOffsetContiguousBaseAsVload4ReviewCandidate() { + IrGpuArtifact original = artifact(""" + body + var int id = intrinsic(get_global_id template="" args=[0]) + var float sum = 0.0F + for init=(var int i = 0) cond=(i < 4) update=(set i = (i + 1)) + set sum = (sum + input[(((id * 4) + 8) + i)]) + set output[id] = sum + """); + + GpuIrOptimizationProposal proposal = new GpuIrLoopVectorizationMaterializationProposalProvider() + .propose(openClRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + assertEquals(""" + body + var int id = intrinsic(get_global_id template="" args=[0]) + var Float4 sum_vec4 = intrinsic(vload4 template="" args=[0, (&input[((id * 4) + 8)])]) + var float sum = ((((0.0F + sum_vec4.x) + sum_vec4.y) + sum_vec4.z) + sum_vec4.w) + set output[id] = sum + """, proposal.optimizedArtifact().orElseThrow().module().methodBodies().get(0).body()); + assertTrue(proposal.optimizedArtifact().orElseThrow().module().methodBodies().get(0).typedBody().available()); + assertEquals("(((id * 4) + 8) + i)", proposal.proofArtifact().fields() + .get("runtimeEquivalencePayload.Case.0.Input.3.Value")); + assertTrue(proposal.proofArtifact().fields().get("firstReplacement").contains("(&input[((id * 4) + 8)])")); + } + + @Test + void blocksMaterializationWhenBackendTargetIsNotOpenCl() { + IrGpuArtifact original = artifact(""" + body + var float sum = 0.0F + for init=(var int i = 0) cond=(i < 4) update=(set i = (i + 1)) + set sum = (sum + input[((id * 4) + i)]) + set output[id] = sum + """); + + GpuIrOptimizationProposal proposal = new GpuIrLoopVectorizationMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertFalse(proposal.hasOptimizedArtifact()); + assertTrue(proposal.diagnostics().get(0).contains("backend-target-not-opencl")); + assertEquals("0", proposal.proofArtifact().fields().get("candidate.count")); + assertEquals("1", proposal.proofArtifact().fields().get("skipped.backendTarget.count")); + } + + @Test + void blocksMaterializationWhenLoopTripCountIsNotVectorWidthFour() { + IrGpuArtifact original = artifact(""" + body + var int id = intrinsic(get_global_id template="" args=[0]) + var float sum = 0.0F + for init=(var int i = 0) cond=(i < 3) update=(set i = (i + 1)) + set sum = (sum + input[((id * 3) + i)]) + set output[id] = sum + """); + + GpuIrOptimizationProposal proposal = new GpuIrLoopVectorizationMaterializationProposalProvider() + .propose(openClRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertFalse(proposal.hasOptimizedArtifact()); + assertEquals("1", proposal.proofArtifact().fields().get("skipped.unsupportedWidth.count")); + assertEquals("loop-width-unsupported-3", proposal.proofArtifact().fields().get("firstBlocker")); + } + + @Test + void materializationProviderIsServiceLoaded() { + List providers = ServiceLoader + .load(GpuIrOptimizationProposalProvider.class) + .stream() + .map(ServiceLoader.Provider::get) + .filter(GpuIrLoopVectorizationMaterializationProposalProvider.class::isInstance) + .toList(); + + assertEquals(1, providers.size()); + } + + private static GpuIrOptimizationProposalRequest openClRequest(IrGpuArtifact original) { + return new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of("backendTarget", "OPENCL") + ); + } + + private static IrGpuArtifact artifact(String body) { + IrGpuMethodBody methodBody = new IrGpuMethodBody( + "entry", + "kernel", + "kernel", + "ir-text-v1", + body, + typedBody(), + IrGpuBodyIndex.empty(), + List.of(), + IrGpuSourceLocation.unknown("kernel") + ); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + new IrGpuModule("kernel", "kernel", List.of(), List.of(), List.of(methodBody)), + List.of( + new IrGpuEntryParameter("output", "float[]", "GLOBAL", false, List.of()), + new IrGpuEntryParameter("input", "float[]", "GLOBAL", false, List.of()) + ), + IrGpuLaunchMetadata.defaultOneDimensional(), + IrGpuValidationMetadata.frontendSubset(), + IrGpuFeatureMetadata.none(), + IrGpuOptimizerPolicyMetadata.defaultStrict(), + IrGpuRegenerationMetadata.transitionalIrText(), + List.of(IrGpuBackendOutput.openClSource("javatogpu/test/Kernel/kernel.cl")), + "opencl", + "off" + ); + } + + private static IrGpuTypedBody typedBody() { + return new IrGpuTypedBody( + IrGpuTypedBody.FORMAT, + List.of(0), + List.of(new IrGpuTypedNode(0, "GpuIrVariableRef", Map.of("name", "sum"), Map.of())) + ); + } +} diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMadFmaMaterializationProposalProviderTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMadFmaMaterializationProposalProviderTest.java new file mode 100644 index 00000000..87ff9562 --- /dev/null +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMadFmaMaterializationProposalProviderTest.java @@ -0,0 +1,164 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBodyIndex; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuEntryParameter; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuFeatureMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuLaunchMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuOptimizerPolicyMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuRegenerationMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuSourceLocation; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuValidationMetadata; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuIrMadFmaMaterializationProposalProviderTest { + + @Test + void materializesMultiplyAddAsOpenClMadReviewCandidate() { + IrGpuArtifact original = artifact(true); + + GpuIrOptimizationProposal proposal = new GpuIrMadFmaMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of("backendTarget", "OPENCL") + )); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + assertSame(original, proposal.originalArtifact()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertEquals( + "body\n set output[0] = intrinsic(mad template=\"\" args=[left, right, addend])\n", + optimized.module().methodBodies().get(0).body() + ); + IrGpuTypedNode replacement = optimized.module().methodBodies().get(0).typedBody().nodes().get(2); + assertEquals("GpuIrIntrinsicCall", replacement.kind()); + assertEquals("mad", replacement.attributes().get("name")); + assertEquals("mad", replacement.attributes().get("backendName")); + assertEquals(List.of(4, 5, 6), replacement.children().get("arguments")); + assertTrue(optimized.regenerationMetadata().backendNeutralSourceReady()); + + Map fields = proposal.proofArtifact().fields(); + assertEquals("mad-fma-materialization", fields.get("optimizerFamily")); + assertEquals("mad", fields.get("targetIntrinsic")); + assertEquals("1", fields.get("candidate.count")); + assertEquals("1", fields.get("transformedNode.count")); + assertEquals("1", fields.get("bodyTextReplacement.count")); + assertEquals("1", fields.get("fixedPoint.pass.count")); + assertEquals("true", fields.get("rewrite.materialized")); + assertEquals("false", fields.get("provider.mutatesOriginal")); + assertEquals("true", fields.get("policy.fastMathAllowed")); + assertEquals("true", fields.get("safety.fastMathRequired")); + assertEquals("false", fields.get("safety.strictFloatPreserved")); + assertEquals("true", fields.get("runtimeEquivalencePayload.present")); + assertEquals("true", fields.get("runtimeEquivalencePayload.passed")); + assertEquals("static-fast-math-multiply-add-to-mad", fields.get("runtimeEquivalencePayload.ReferenceMode")); + assertEquals("1", fields.get("runtimeEquivalencePayload.Case.Count")); + assertEquals("mad-fma-to-opencl-mad", fields.get("runtimeEquivalencePayload.Case.0.RewriteKind")); + assertEquals("((left * right) + addend)", fields.get("runtimeEquivalencePayload.Case.0.Output.0.PreOptimization")); + assertEquals("intrinsic(mad template=\"\" args=[left, right, addend])", fields.get("runtimeEquivalencePayload.Case.0.Output.0.PostOptimization")); + assertEquals("true", fields.get("openClReview.sourceReady")); + assertEquals("kernel#2=((left * right) + addend)->intrinsic(mad template=\"\" args=[left, right, addend])", fields.get("firstTransformedNode")); + } + + @Test + void blocksMaterializationWhenFastMathPolicyIsStrict() { + IrGpuArtifact original = artifact(false); + + GpuIrOptimizationProposal proposal = new GpuIrMadFmaMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertFalse(proposal.hasOptimizedArtifact()); + assertTrue(proposal.diagnostics().get(0).contains("fast-math-policy-disabled")); + assertEquals("1", proposal.proofArtifact().fields().get("candidate.count")); + assertEquals("1", proposal.proofArtifact().fields().get("skipped.fastMathPolicy.count")); + assertEquals("false", proposal.proofArtifact().fields().get("rewrite.materialized")); + assertEquals("false", proposal.proofArtifact().fields().get("policy.fastMathAllowed")); + } + + @Test + void materializationProviderIsServiceLoaded() { + List providers = ServiceLoader + .load(GpuIrOptimizationProposalProvider.class) + .stream() + .map(ServiceLoader.Provider::get) + .filter(GpuIrMadFmaMaterializationProposalProvider.class::isInstance) + .toList(); + + assertEquals(1, providers.size()); + } + + private static IrGpuArtifact artifact(boolean fastMath) { + IrGpuMethodBody methodBody = new IrGpuMethodBody( + "entry", + "kernel", + "kernel", + "ir-text-v1", + "body\n set output[0] = ((left * right) + addend)\n", + typedBody(), + IrGpuBodyIndex.empty(), + List.of(), + IrGpuSourceLocation.unknown("kernel") + ); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + new IrGpuModule("kernel", "kernel", List.of(), List.of(), List.of(methodBody)), + List.of( + new IrGpuEntryParameter("output", "float[]", "GLOBAL", false, List.of()), + new IrGpuEntryParameter("left", "float", "PRIVATE", false, List.of()), + new IrGpuEntryParameter("right", "float", "PRIVATE", false, List.of()), + new IrGpuEntryParameter("addend", "float", "PRIVATE", false, List.of()) + ), + IrGpuLaunchMetadata.defaultOneDimensional(), + IrGpuValidationMetadata.frontendSubset(), + IrGpuFeatureMetadata.none(), + fastMath ? IrGpuOptimizerPolicyMetadata.fromGpuOptimize(true) : IrGpuOptimizerPolicyMetadata.defaultStrict(), + IrGpuRegenerationMetadata.transitionalIrText(), + List.of(IrGpuBackendOutput.openClSource("javatogpu/test/Kernel/kernel.cl")), + "opencl", + "off" + ); + } + + private static IrGpuTypedBody typedBody() { + return new IrGpuTypedBody( + IrGpuTypedBody.FORMAT, + List.of(0), + List.of( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of( + "target", List.of(1), + "value", List.of(2) + )), + new IrGpuTypedNode(1, "GpuIrVariableRef", Map.of("name", "output[0]"), Map.of()), + new IrGpuTypedNode(2, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(3), + "right", List.of(6) + )), + new IrGpuTypedNode(3, "GpuIrBinary", Map.of("operator", "*"), Map.of( + "left", List.of(4), + "right", List.of(5) + )), + new IrGpuTypedNode(4, "GpuIrVariableRef", Map.of("name", "left"), Map.of()), + new IrGpuTypedNode(5, "GpuIrVariableRef", Map.of("name", "right"), Map.of()), + new IrGpuTypedNode(6, "GpuIrVariableRef", Map.of("name", "addend"), Map.of()) + ) + ); + } +} diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMixMaterializationProposalProviderTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMixMaterializationProposalProviderTest.java new file mode 100644 index 00000000..a320b9c6 --- /dev/null +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMixMaterializationProposalProviderTest.java @@ -0,0 +1,278 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBodyIndex; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuEntryParameter; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuFeatureMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuLaunchMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuOptimizerPolicyMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuRegenerationMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuSourceLocation; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuValidationMetadata; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuIrMixMaterializationProposalProviderTest { + + @Test + void materializesCanonicalInterpolationAsOpenClMixReviewCandidateWithoutFastMath() { + IrGpuArtifact original = canonicalArtifact(); + + GpuIrOptimizationProposal proposal = new GpuIrMixMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of("backendTarget", "OPENCL") + )); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + assertSame(original, proposal.originalArtifact()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertEquals( + "body\n set output[0] = intrinsic(mix template=\"\" args=[a, b, t])\n", + optimized.module().methodBodies().get(0).body() + ); + IrGpuTypedNode replacement = optimized.module().methodBodies().get(0).typedBody().nodes().get(2); + assertEquals("GpuIrIntrinsicCall", replacement.kind()); + assertEquals("mix", replacement.attributes().get("name")); + assertEquals("mix", replacement.attributes().get("backendName")); + assertEquals(List.of(3, 7, 5), replacement.children().get("arguments")); + assertTrue(optimized.regenerationMetadata().backendNeutralSourceReady()); + + Map fields = proposal.proofArtifact().fields(); + assertEquals("mix-materialization", fields.get("optimizerFamily")); + assertEquals("mix", fields.get("targetIntrinsic")); + assertEquals("1", fields.get("candidate.count")); + assertEquals("1", fields.get("transformedNode.count")); + assertEquals("1", fields.get("canonicalMix.count")); + assertEquals("0", fields.get("expandedMix.count")); + assertEquals("0", fields.get("madExpandedMix.count")); + assertEquals("false", fields.get("safety.fastMathRequired")); + assertEquals("true", fields.get("safety.strictFloatPreserved")); + assertEquals("false", fields.get("safety.algebraicReassociationRequired")); + assertEquals("linear-interpolation-to-opencl-mix", fields.get("runtimeEquivalencePayload.Case.0.RewriteKind")); + assertEquals("a", fields.get("runtimeEquivalencePayload.Case.0.Input.1.Value")); + assertEquals("b", fields.get("runtimeEquivalencePayload.Case.0.Input.2.Value")); + assertEquals("t", fields.get("runtimeEquivalencePayload.Case.0.Input.3.Value")); + assertEquals("true", fields.get("openClReview.sourceReady")); + assertEquals( + "kernel#2=(a + (t * (b - a)))->intrinsic(mix template=\"\" args=[a, b, t])", + fields.get("firstTransformedNode") + ); + } + + @Test + void materializesMadExpandedInterpolationAsFastMathOpenClMixReviewCandidate() { + IrGpuArtifact original = madExpandedArtifact(true); + + GpuIrOptimizationProposal proposal = new GpuIrMixMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of("backendTarget", "OPENCL") + )); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + assertSame(original, proposal.originalArtifact()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertEquals( + "body\n set output[0] = intrinsic(mix template=\"\" args=[(clampedWave * 0.1F), blend, isSolid])\n", + optimized.module().methodBodies().get(0).body() + ); + IrGpuTypedNode replacement = optimized.module().methodBodies().get(0).typedBody().nodes().get(2); + assertEquals("GpuIrIntrinsicCall", replacement.kind()); + assertEquals("mix", replacement.attributes().get("backendName")); + assertEquals(List.of(7, 3, 4), replacement.children().get("arguments")); + + Map fields = proposal.proofArtifact().fields(); + assertEquals("mix-materialization", fields.get("optimizerFamily")); + assertEquals("0", fields.get("canonicalMix.count")); + assertEquals("0", fields.get("expandedMix.count")); + assertEquals("1", fields.get("madExpandedMix.count")); + assertEquals("true", fields.get("policy.fastMathAllowed")); + assertEquals("true", fields.get("safety.fastMathRequired")); + assertEquals("false", fields.get("safety.strictFloatPreserved")); + assertEquals("true", fields.get("safety.algebraicReassociationRequired")); + assertEquals("mad-expanded-linear-interpolation-to-opencl-mix", fields.get("runtimeEquivalencePayload.Case.0.RewriteKind")); + assertEquals("(clampedWave * 0.1F)", fields.get("runtimeEquivalencePayload.Case.0.Input.1.Value")); + assertEquals("blend", fields.get("runtimeEquivalencePayload.Case.0.Input.2.Value")); + assertEquals("isSolid", fields.get("runtimeEquivalencePayload.Case.0.Input.3.Value")); + assertEquals( + "kernel#2=intrinsic(mad template=\"\" args=[blend, isSolid, ((1.0F - isSolid) * (clampedWave * 0.1F))])->intrinsic(mix template=\"\" args=[(clampedWave * 0.1F), blend, isSolid])", + fields.get("firstTransformedNode") + ); + } + + @Test + void blocksMadExpandedInterpolationWhenFastMathIsDisabled() { + IrGpuArtifact original = madExpandedArtifact(false); + + GpuIrOptimizationProposal proposal = new GpuIrMixMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of("backendTarget", "OPENCL") + )); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertSame(original, proposal.originalArtifact()); + Map fields = proposal.proofArtifact().fields(); + assertEquals("mix-materialization", fields.get("optimizerFamily")); + assertEquals("1", fields.get("candidate.count")); + assertEquals("0", fields.get("transformedNode.count")); + assertEquals("1", fields.get("skipped.fastMathPolicy.count")); + assertEquals("fast-math-policy-disabled", fields.get("firstBlocker")); + } + + @Test + void materializationProviderIsServiceLoaded() { + List providers = ServiceLoader + .load(GpuIrOptimizationProposalProvider.class) + .stream() + .map(ServiceLoader.Provider::get) + .filter(GpuIrMixMaterializationProposalProvider.class::isInstance) + .toList(); + + assertEquals(1, providers.size()); + } + + private static IrGpuArtifact canonicalArtifact() { + IrGpuMethodBody methodBody = new IrGpuMethodBody( + "entry", + "kernel", + "kernel", + "ir-text-v1", + "body\n set output[0] = (a + (t * (b - a)))\n", + canonicalTypedBody(), + IrGpuBodyIndex.empty(), + List.of(), + IrGpuSourceLocation.unknown("kernel") + ); + return artifact(methodBody, IrGpuOptimizerPolicyMetadata.defaultStrict()); + } + + private static IrGpuTypedBody canonicalTypedBody() { + return new IrGpuTypedBody( + IrGpuTypedBody.FORMAT, + List.of(0), + List.of( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of( + "target", List.of(1), + "value", List.of(2) + )), + new IrGpuTypedNode(1, "GpuIrVariableRef", Map.of("name", "output[0]"), Map.of()), + new IrGpuTypedNode(2, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(3), + "right", List.of(4) + )), + new IrGpuTypedNode(3, "GpuIrVariableRef", Map.of("name", "a"), Map.of()), + new IrGpuTypedNode(4, "GpuIrBinary", Map.of("operator", "*"), Map.of( + "left", List.of(5), + "right", List.of(6) + )), + new IrGpuTypedNode(5, "GpuIrVariableRef", Map.of("name", "t"), Map.of()), + new IrGpuTypedNode(6, "GpuIrBinary", Map.of("operator", "-"), Map.of( + "left", List.of(7), + "right", List.of(3) + )), + new IrGpuTypedNode(7, "GpuIrVariableRef", Map.of("name", "b"), Map.of()) + ) + ); + } + + private static IrGpuArtifact madExpandedArtifact(boolean fastMath) { + IrGpuMethodBody methodBody = new IrGpuMethodBody( + "entry", + "kernel", + "kernel", + "ir-text-v1", + "body\n set output[0] = intrinsic(mad template=\"\" args=[blend, isSolid, ((1.0F - isSolid) * (clampedWave * 0.1F))])\n", + madExpandedTypedBody(), + IrGpuBodyIndex.empty(), + List.of(), + IrGpuSourceLocation.unknown("kernel") + ); + return artifact(methodBody, fastMath + ? IrGpuOptimizerPolicyMetadata.fromGpuOptimize(true) + : IrGpuOptimizerPolicyMetadata.defaultStrict()); + } + + private static IrGpuTypedBody madExpandedTypedBody() { + return new IrGpuTypedBody( + IrGpuTypedBody.FORMAT, + List.of(0), + List.of( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of( + "target", List.of(1), + "value", List.of(2) + )), + new IrGpuTypedNode(1, "GpuIrVariableRef", Map.of("name", "output[0]"), Map.of()), + new IrGpuTypedNode(2, "GpuIrIntrinsicCall", Map.of( + "name", "mad", + "backendName", "mad", + "codeTemplate", "" + ), Map.of("arguments", List.of(3, 4, 5))), + new IrGpuTypedNode(3, "GpuIrVariableRef", Map.of("name", "blend"), Map.of()), + new IrGpuTypedNode(4, "GpuIrVariableRef", Map.of("name", "isSolid"), Map.of()), + new IrGpuTypedNode(5, "GpuIrBinary", Map.of("operator", "*"), Map.of( + "left", List.of(6), + "right", List.of(7) + )), + new IrGpuTypedNode(6, "GpuIrBinary", Map.of("operator", "-"), Map.of( + "left", List.of(8), + "right", List.of(4) + )), + new IrGpuTypedNode(7, "GpuIrBinary", Map.of("operator", "*"), Map.of( + "left", List.of(9), + "right", List.of(10) + )), + new IrGpuTypedNode(8, "GpuIrLiteral", Map.of("sourceText", "1.0F"), Map.of()), + new IrGpuTypedNode(9, "GpuIrVariableRef", Map.of("name", "clampedWave"), Map.of()), + new IrGpuTypedNode(10, "GpuIrLiteral", Map.of("sourceText", "0.1F"), Map.of()) + ) + ); + } + + private static IrGpuArtifact artifact( + IrGpuMethodBody methodBody, + IrGpuOptimizerPolicyMetadata optimizerPolicyMetadata + ) { + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + new IrGpuModule("kernel", "kernel", List.of(), List.of(), List.of(methodBody)), + List.of( + new IrGpuEntryParameter("output", "float[]", "GLOBAL", false, List.of()), + new IrGpuEntryParameter("a", "float", "PRIVATE", false, List.of()), + new IrGpuEntryParameter("b", "float", "PRIVATE", false, List.of()), + new IrGpuEntryParameter("t", "float", "PRIVATE", false, List.of()), + new IrGpuEntryParameter("blend", "float", "PRIVATE", false, List.of()), + new IrGpuEntryParameter("isSolid", "float", "PRIVATE", false, List.of()), + new IrGpuEntryParameter("clampedWave", "float", "PRIVATE", false, List.of()) + ), + IrGpuLaunchMetadata.defaultOneDimensional(), + IrGpuValidationMetadata.frontendSubset(), + IrGpuFeatureMetadata.none(), + optimizerPolicyMetadata, + IrGpuRegenerationMetadata.transitionalIrText(), + List.of(IrGpuBackendOutput.openClSource("javatogpu/test/Kernel/kernel.cl")), + "opencl", + "off" + ); + } +} diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalManifestTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalManifestTest.java index c40d1a9d..94724436 100644 --- a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalManifestTest.java +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalManifestTest.java @@ -7,8 +7,13 @@ import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import java.io.StringReader; +import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.List; import java.util.Map; import java.util.Properties; @@ -20,15 +25,24 @@ class GpuIrOptimizationApprovalManifestTest { + @TempDir + Path temporaryDirectory; + @Test void writesPendingTemplateBoundToProposalAndRuntimeContext() throws Exception { GpuIrOptimizationProposal proposal = proposal(); GpuIrOptimizationProposalRequest request = request(proposal.originalArtifact()); String template = GpuIrOptimizationApprovalManifest.template(proposal, request); + String resourceName = GpuIrOptimizationApprovalManifest.resourceName(proposal, request); + String resourcePath = GpuIrOptimizationApprovalManifest.resourcePath(proposal, request); assertTrue(template.contains("status=pending")); assertTrue(template.contains("scope=" + GpuIrOptimizationApprovalManifest.SCOPE)); + assertTrue(resourceName.startsWith("approval-")); + assertTrue(resourceName.endsWith(".properties")); + assertEquals(GpuIrOptimizationApprovalManifest.RESOURCE_DIRECTORY + resourceName, resourcePath); + assertTrue(template.contains("manifest.resourcePath=" + resourcePath)); assertTrue(template.contains("approval.id=REQUIRED")); assertTrue(template.contains("binding.optimizerId=optimizer:test")); assertTrue(template.contains("binding.originalIrIdentity=" + proposal.originalIdentity())); @@ -36,6 +50,8 @@ void writesPendingTemplateBoundToProposalAndRuntimeContext() throws Exception { assertTrue(template.contains("binding.backendTarget=OPENCL")); assertTrue(template.contains("binding.deviceVendor=NVIDIA")); assertTrue(template.contains("binding.proof.verdict=runtime-equivalence-passed")); + assertTrue(template.contains("binding.runtimeEquivalencePayload.required=false")); + assertTrue(template.contains("binding.runtimeEquivalencePayload.resource=not-required")); } @Test @@ -50,10 +66,84 @@ void validatesApprovedManifestAgainstExactProposalAndContext() throws Exception assertTrue(validation.valid()); assertEquals("approved", validation.status()); assertEquals("approval:optimizer-test", validation.approvalId()); + assertEquals( + GpuIrOptimizationApprovalManifest.resourcePath(proposal, request), + validation.manifestResourcePath() + ); assertEquals(proposal.originalIdentity(), validation.originalIrIdentity()); assertEquals(proposal.optimizedIdentity(), validation.optimizedIrIdentity()); assertTrue(validation.toPropertiesText().contains("valid=true")); + assertTrue(validation.toPropertiesText().contains( + "manifest.resourcePath=" + GpuIrOptimizationApprovalManifest.resourcePath(proposal, request) + )); assertTrue(validation.toPropertiesText().contains("blocker.count=0")); + assertTrue(validation.toPropertiesText().contains("binding.runtimeEquivalencePayload.required=false")); + } + + @Test + void bindsApprovedManifestToRuntimeEquivalencePayloadEvidence() throws Exception { + GpuIrOptimizationProposal proposal = proposalWithRuntimeEquivalencePayload(); + GpuIrOptimizationProposalRequest request = request(proposal.originalArtifact()); + + String template = GpuIrOptimizationApprovalManifest.template(proposal, request); + Properties manifest = approvedManifest(proposal, request); + GpuIrOptimizationApprovalManifest.Validation validation = + GpuIrOptimizationApprovalManifest.validate(proposal, request, manifest); + + assertTrue(template.contains("binding.runtimeEquivalencePayload.required=true")); + assertTrue(template.contains("binding.runtimeEquivalencePayload.present=true")); + assertTrue(template.contains("binding.runtimeEquivalencePayload.passed=true")); + assertTrue(template.contains("binding.runtimeEquivalencePayload.componentsComplete=true")); + assertTrue(template.contains("binding.runtimeEquivalencePayload.caseCount=1")); + assertTrue(template.contains("binding.runtimeEquivalencePayload.resource=artifact://payload/cf")); + assertTrue(validation.valid()); + assertTrue(validation.runtimeEquivalencePayloadRequired()); + assertTrue(validation.runtimeEquivalencePayloadComponentsComplete()); + assertEquals("artifact://payload/cf", validation.runtimeEquivalencePayloadResource()); + } + + @Test + void blocksManifestForDifferentRuntimeEquivalencePayloadResource() throws Exception { + GpuIrOptimizationProposal proposal = proposalWithRuntimeEquivalencePayload(); + GpuIrOptimizationProposalRequest request = request(proposal.originalArtifact()); + Properties manifest = approvedManifest(proposal, request); + manifest.setProperty("binding.runtimeEquivalencePayload.resource", "artifact://payload/other"); + + GpuIrOptimizationApprovalManifest.Validation validation = + GpuIrOptimizationApprovalManifest.validate(proposal, request, manifest); + + assertFalse(validation.valid()); + assertTrue(validation.blockers().contains("manifest-runtime-equivalence-payload-resource-mismatch")); + } + + @Test + void blocksManifestForDifferentPackagedResourcePath() throws Exception { + GpuIrOptimizationProposal proposal = proposalWithRuntimeEquivalencePayload(); + GpuIrOptimizationProposalRequest request = request(proposal.originalArtifact()); + Properties manifest = approvedManifest(proposal, request); + manifest.setProperty( + "manifest.resourcePath", + GpuIrOptimizationApprovalManifest.RESOURCE_DIRECTORY + "approval-other.properties" + ); + + GpuIrOptimizationApprovalManifest.Validation validation = + GpuIrOptimizationApprovalManifest.validate(proposal, request, manifest); + + assertFalse(validation.valid()); + assertTrue(validation.blockers().contains("manifest-resource-path-mismatch")); + } + + @Test + void blocksApprovalWhenRequiredRuntimeEquivalencePayloadIsIncomplete() throws Exception { + GpuIrOptimizationProposal proposal = proposalWithIncompleteRuntimeEquivalencePayload(); + GpuIrOptimizationProposalRequest request = request(proposal.originalArtifact()); + Properties manifest = approvedManifest(proposal, request); + + GpuIrOptimizationApprovalManifest.Validation validation = + GpuIrOptimizationApprovalManifest.validate(proposal, request, manifest); + + assertFalse(validation.valid()); + assertTrue(validation.blockers().contains("runtime-equivalence-payload-missing")); } @Test @@ -84,6 +174,80 @@ void blocksManifestForDifferentDeviceBinding() throws Exception { assertTrue(validation.blockers().contains("manifest-device-vendor-mismatch")); } + @Test + void loaderReportsMissingClasspathManifestAsPendingValidation() { + GpuIrOptimizationProposal proposal = proposalWithRuntimeEquivalencePayload(); + GpuIrOptimizationProposalRequest request = request(proposal.originalArtifact()); + + GpuIrOptimizationApprovalManifestLoader.Result result = + GpuIrOptimizationApprovalManifestLoader.loadAndValidate(proposal, request, null); + + assertEquals("pending-manifest-validation", result.status()); + assertTrue(result.required()); + assertFalse(result.present()); + assertFalse(result.accepted()); + assertEquals("approval-manifest-not-loaded", result.firstBlocker()); + assertEquals(GpuIrOptimizationApprovalManifest.resourcePath(proposal, request), result.resourcePath()); + assertEquals("false", result.fields().get("accepted")); + assertEquals("disabled", result.fields().get("productionMutation")); + } + + @Test + void loaderAcceptsApprovedClasspathManifestForExactProposal() throws Exception { + GpuIrOptimizationProposal proposal = proposalWithRuntimeEquivalencePayload(); + GpuIrOptimizationProposalRequest request = request(proposal.originalArtifact()); + Path classpathRoot = temporaryDirectory.resolve("approval-loader"); + writeApprovedManifest(classpathRoot, proposal, request); + + try (URLClassLoader classLoader = new URLClassLoader( + new java.net.URL[]{classpathRoot.toUri().toURL()}, + GpuIrOptimizationApprovalManifestTest.class.getClassLoader() + )) { + GpuIrOptimizationApprovalManifestLoader.Result result = + GpuIrOptimizationApprovalManifestLoader.loadAndValidate(proposal, request, classLoader); + + assertEquals("accepted", result.status()); + assertTrue(result.present()); + assertTrue(result.accepted()); + assertEquals(1, result.resourceCount()); + assertEquals("none", result.firstBlocker()); + assertTrue(result.validation().orElseThrow().valid()); + assertEquals("true", result.fields().get("accepted")); + assertEquals("true", result.fields().get("validation.valid")); + } + } + + @Test + void loaderBlocksStaleClasspathManifest() throws Exception { + GpuIrOptimizationProposal proposal = proposalWithRuntimeEquivalencePayload(); + GpuIrOptimizationProposalRequest request = request(proposal.originalArtifact()); + Path classpathRoot = temporaryDirectory.resolve("stale-approval-loader"); + Path resource = resourcePath(classpathRoot, proposal, request); + Files.createDirectories(resource.getParent()); + Files.writeString( + resource, + approvedManifestText(proposal, request) + .replace("binding.optimizedIrIdentity=" + proposal.optimizedIdentity(), + "binding.optimizedIrIdentity=irgpu:sha256:stale"), + StandardCharsets.UTF_8 + ); + + try (URLClassLoader classLoader = new URLClassLoader( + new java.net.URL[]{classpathRoot.toUri().toURL()}, + GpuIrOptimizationApprovalManifestTest.class.getClassLoader() + )) { + GpuIrOptimizationApprovalManifestLoader.Result result = + GpuIrOptimizationApprovalManifestLoader.loadAndValidate(proposal, request, classLoader); + + assertEquals("blocked", result.status()); + assertTrue(result.present()); + assertFalse(result.accepted()); + assertEquals("manifest-optimized-ir-identity-mismatch", result.firstBlocker()); + assertTrue(result.validation().orElseThrow().blockers() + .contains("manifest-optimized-ir-identity-mismatch")); + } + } + @Test void templateRequiresRealProposedDistinctArtifactAndAcceptedProof() { IrGpuArtifact artifact = artifact("body\n"); @@ -132,6 +296,54 @@ private static GpuIrOptimizationProposal proposal() { ); } + private static GpuIrOptimizationProposal proposalWithRuntimeEquivalencePayload() { + return proposalWithProof(GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.test-proof", + "runtime-equivalence-passed", + Map.ofEntries( + Map.entry("proof.runtimeEquivalencePayloadRequiredBeforeSelection", "true"), + Map.entry("runtimeEquivalencePayload.required", "true"), + Map.entry("runtimeEquivalencePayload.present", "true"), + Map.entry("runtimeEquivalencePayload.passed", "true"), + Map.entry("runtimeEquivalencePayload.cpuReference.present", "true"), + Map.entry("runtimeEquivalencePayload.preOptimizationOutput.present", "true"), + Map.entry("runtimeEquivalencePayload.postOptimizationOutput.present", "true"), + Map.entry("runtimeEquivalencePayload.tolerance.present", "true"), + Map.entry("runtimeEquivalencePayload.failureFixture.present", "true"), + Map.entry("runtimeEquivalencePayload.Case.Count", "1"), + Map.entry("runtimeEquivalencePayload.resource", "artifact://payload/cf"), + Map.entry( + "runtimeEquivalencePayload.comparisonMode", + "optimizer-family:constant-folding-materialization:review-candidate" + ) + ) + )); + } + + private static GpuIrOptimizationProposal proposalWithIncompleteRuntimeEquivalencePayload() { + return proposalWithProof(GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.test-proof", + "runtime-equivalence-passed", + Map.of( + "proof.runtimeEquivalencePayloadRequiredBeforeSelection", "true", + "runtimeEquivalencePayload.required", "true" + ) + )); + } + + private static GpuIrOptimizationProposal proposalWithProof(GpuRuntimeIrOptimizationProofArtifact proofArtifact) { + IrGpuArtifact original = artifact("body\n"); + IrGpuArtifact optimized = artifact("body optimized\n"); + return GpuIrOptimizationProposal.proposed( + "optimizer:test", + "optimizer:test:1", + original, + optimized, + proofArtifact, + List.of("test proposal") + ); + } + private static GpuIrOptimizationProposalRequest request(IrGpuArtifact artifact) { return new GpuIrOptimizationProposalRequest( artifact, @@ -149,14 +361,38 @@ private static Properties approvedManifest( GpuIrOptimizationProposal proposal, GpuIrOptimizationProposalRequest request ) throws Exception { - String approved = GpuIrOptimizationApprovalManifest.template(proposal, request) + Properties properties = new Properties(); + properties.load(new StringReader(approvedManifestText(proposal, request))); + return properties; + } + + private static void writeApprovedManifest( + Path classpathRoot, + GpuIrOptimizationProposal proposal, + GpuIrOptimizationProposalRequest request + ) throws Exception { + Path resource = resourcePath(classpathRoot, proposal, request); + Files.createDirectories(resource.getParent()); + Files.writeString(resource, approvedManifestText(proposal, request), StandardCharsets.UTF_8); + } + + private static Path resourcePath( + Path classpathRoot, + GpuIrOptimizationProposal proposal, + GpuIrOptimizationProposalRequest request + ) { + return classpathRoot.resolve(GpuIrOptimizationApprovalManifest.resourcePath(proposal, request)); + } + + private static String approvedManifestText( + GpuIrOptimizationProposal proposal, + GpuIrOptimizationProposalRequest request + ) { + return GpuIrOptimizationApprovalManifest.template(proposal, request) .replace("status=pending", "status=approved") .replace("approval.id=REQUIRED", "approval.id=approval:optimizer-test") .replace("approval.approvedBy=REQUIRED", "approval.approvedBy=CI") .replace("approval.approvedAtUtc=REQUIRED", "approval.approvedAtUtc=2026-07-12T00:00:00Z"); - Properties properties = new Properties(); - properties.load(new StringReader(approved)); - return properties; } private static IrGpuArtifact artifact(String body) { diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalTemplateFormatterTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalTemplateFormatterTest.java index 142fdaa2..d40e0a99 100644 --- a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalTemplateFormatterTest.java +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationApprovalTemplateFormatterTest.java @@ -45,15 +45,76 @@ void returnsPendingTemplateForRealProposedRewrite() { proposal, request(original) ); + String resourcePath = GpuIrOptimizationApprovalManifest.resourcePath(proposal, request(original)); assertTrue(result.applicable()); assertEquals("pending", result.status()); assertEquals("none", result.firstBlocker()); assertTrue(result.templateText().contains("status=pending")); + assertTrue(result.templateText().contains("manifest.resourcePath=" + resourcePath)); assertTrue(result.templateText().contains("binding.optimizerId=optimizer:test")); assertTrue(result.templateText().contains("authorization.productionMutation=disabled")); + assertEquals("false", result.fields().get("runtimeEquivalencePayload.required")); + assertEquals(resourcePath, result.fields().get("resourcePath")); assertTrue(result.toPropertiesText().contains("template.present=true")); assertTrue(result.toPropertiesText().contains("template.resourceDirectory=META-INF/javatogpu/ir-optimization-approvals/")); + assertTrue(result.toPropertiesText().contains("template.resourcePath=" + resourcePath)); + } + + @Test + void exposesRuntimeEquivalencePayloadBindingForPendingTemplate() { + IrGpuArtifact original = artifact("body\n"); + GpuIrOptimizationProposal proposal = proposed( + original, + artifact("body optimized\n"), + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.test-proof", + "runtime-equivalence-passed", + Map.ofEntries( + Map.entry("proof.runtimeEquivalencePayloadRequiredBeforeSelection", "true"), + Map.entry("runtimeEquivalencePayload.required", "true"), + Map.entry("runtimeEquivalencePayload.present", "true"), + Map.entry("runtimeEquivalencePayload.passed", "true"), + Map.entry("runtimeEquivalencePayload.cpuReference.present", "true"), + Map.entry("runtimeEquivalencePayload.preOptimizationOutput.present", "true"), + Map.entry("runtimeEquivalencePayload.postOptimizationOutput.present", "true"), + Map.entry("runtimeEquivalencePayload.tolerance.present", "true"), + Map.entry("runtimeEquivalencePayload.failureFixture.present", "true"), + Map.entry("runtimeEquivalencePayload.Case.Count", "1"), + Map.entry("runtimeEquivalencePayload.resource", "artifact://payload/cf"), + Map.entry( + "runtimeEquivalencePayload.comparisonMode", + "optimizer-family:constant-folding-materialization:review-candidate" + ) + ) + ) + ); + + GpuIrOptimizationApprovalTemplateResult result = GpuIrOptimizationApprovalTemplateFormatter.format( + proposal, + request(original) + ); + + assertTrue(result.applicable()); + assertEquals("true", result.fields().get("runtimeEquivalencePayload.required")); + assertEquals("true", result.fields().get("runtimeEquivalencePayload.present")); + assertEquals("true", result.fields().get("runtimeEquivalencePayload.passed")); + assertEquals("true", result.fields().get("runtimeEquivalencePayload.componentsComplete")); + assertEquals("1", result.fields().get("runtimeEquivalencePayload.caseCount")); + assertEquals("artifact://payload/cf", result.fields().get("runtimeEquivalencePayload.resource")); + assertEquals( + "optimizer-family:constant-folding-materialization:review-candidate", + result.fields().get("runtimeEquivalencePayload.comparisonMode") + ); + assertTrue(result.fields().get("resourcePath").startsWith( + GpuIrOptimizationApprovalManifest.RESOURCE_DIRECTORY + "approval-" + )); + assertTrue(result.templateText().contains("binding.runtimeEquivalencePayload.resource=artifact://payload/cf")); + assertTrue(result.templateText().contains( + "binding.runtimeEquivalencePayload.comparisonMode=optimizer-family:constant-folding-materialization:review-candidate" + )); + assertTrue(result.templateText().contains("manifest.resourcePath=" + result.fields().get("resourcePath"))); + assertTrue(result.toPropertiesText().contains("runtimeEquivalencePayload.componentsComplete=true")); } @Test diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalRegistryTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalRegistryTest.java index 9d1a7a9e..38fd3775 100644 --- a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalRegistryTest.java +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalRegistryTest.java @@ -35,9 +35,17 @@ void serviceLoaderRegistryLoadsBackendNeutralProvidersOnly() { assertTrue(providerIds.contains(GpuIrNoOpOptimizationPass.PASS_ID)); assertTrue(providerIds.contains(GpuIrTextCanonicalizationProposalProvider.PROVIDER_ID)); assertTrue(providerIds.contains(GpuIrHelperDependencyDeduplicationProposalProvider.PROVIDER_ID)); + assertTrue(providerIds.contains(GpuIrBackendNeutralSourceMaterializationProposalProvider.PROVIDER_ID)); assertTrue(providerIds.contains(GpuIrConstantFoldingPreviewProposalProvider.PROVIDER_ID)); + assertTrue(providerIds.contains(GpuIrConstantFoldingMaterializationProposalProvider.PROVIDER_ID)); assertTrue(providerIds.contains(GpuIrSafeLocalCsePreviewProposalProvider.PROVIDER_ID)); + assertTrue(providerIds.contains(GpuIrSafeLocalCseMaterializationProposalProvider.PROVIDER_ID)); + assertTrue(providerIds.contains(GpuIrMadFmaMaterializationProposalProvider.PROVIDER_ID)); + assertTrue(providerIds.contains(GpuIrClampMaterializationProposalProvider.PROVIDER_ID)); + assertTrue(providerIds.contains(GpuIrStepMaterializationProposalProvider.PROVIDER_ID)); + assertTrue(providerIds.contains(GpuIrMixMaterializationProposalProvider.PROVIDER_ID)); assertTrue(providerIds.contains(GpuIrTypedDeadCodePreviewProposalProvider.PROVIDER_ID)); + assertTrue(providerIds.contains(GpuIrTypedDeadCodeMaterializationProposalProvider.PROVIDER_ID)); assertTrue(providerIds.stream().noneMatch(id -> id.contains("vendor"))); } diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePassTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePassTest.java index 4ddf9854..b6f99c52 100644 --- a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePassTest.java +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePassTest.java @@ -4,8 +4,18 @@ import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBodyIndex; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuEntryParameter; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuFeatureMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuLaunchMetadata; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuOptimizerPolicyMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuRegenerationMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuSourceLocation; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuValidationMetadata; import net.sixik.ga_utils.javatogpu.runtime.GpuKernelDescriptor; import net.sixik.ga_utils.javatogpu.runtime.GpuBackendModuleArtifact; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileArtifactDump; @@ -28,6 +38,7 @@ import java.io.BufferedReader; import java.io.InputStreamReader; +import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; import java.nio.charset.StandardCharsets; @@ -66,13 +77,22 @@ void runtimeBridgeIsTheOnlyServiceLoadedRuntimePassFromModule() { void bridgeLoadsProposalProvidersInDeterministicOrder() { GpuIrProposalRuntimeBridgePass bridge = new GpuIrProposalRuntimeBridgePass(); - assertEquals(6, bridge.proposalProviders().size()); + assertEquals(15, bridge.proposalProviders().size()); assertInstanceOf(GpuIrNoOpProposalProvider.class, bridge.proposalProviders().get(0)); assertInstanceOf(GpuIrTextCanonicalizationProposalProvider.class, bridge.proposalProviders().get(1)); assertInstanceOf(GpuIrHelperDependencyDeduplicationProposalProvider.class, bridge.proposalProviders().get(2)); - assertInstanceOf(GpuIrConstantFoldingPreviewProposalProvider.class, bridge.proposalProviders().get(3)); - assertInstanceOf(GpuIrSafeLocalCsePreviewProposalProvider.class, bridge.proposalProviders().get(4)); - assertInstanceOf(GpuIrTypedDeadCodePreviewProposalProvider.class, bridge.proposalProviders().get(5)); + assertInstanceOf(GpuIrBackendNeutralSourceMaterializationProposalProvider.class, bridge.proposalProviders().get(3)); + assertInstanceOf(GpuIrConstantFoldingPreviewProposalProvider.class, bridge.proposalProviders().get(4)); + assertInstanceOf(GpuIrConstantFoldingMaterializationProposalProvider.class, bridge.proposalProviders().get(5)); + assertInstanceOf(GpuIrSafeLocalCsePreviewProposalProvider.class, bridge.proposalProviders().get(6)); + assertInstanceOf(GpuIrSafeLocalCseMaterializationProposalProvider.class, bridge.proposalProviders().get(7)); + assertInstanceOf(GpuIrMadFmaMaterializationProposalProvider.class, bridge.proposalProviders().get(8)); + assertInstanceOf(GpuIrTypedDeadCodePreviewProposalProvider.class, bridge.proposalProviders().get(9)); + assertInstanceOf(GpuIrClampMaterializationProposalProvider.class, bridge.proposalProviders().get(10)); + assertInstanceOf(GpuIrStepMaterializationProposalProvider.class, bridge.proposalProviders().get(11)); + assertInstanceOf(GpuIrMixMaterializationProposalProvider.class, bridge.proposalProviders().get(12)); + assertInstanceOf(GpuIrTypedDeadCodeMaterializationProposalProvider.class, bridge.proposalProviders().get(13)); + assertInstanceOf(GpuIrLoopVectorizationMaterializationProposalProvider.class, bridge.proposalProviders().get(14)); } @Test @@ -83,9 +103,18 @@ void modulePublishesOnlyBackendNeutralProposalProviderDescriptorEntries() throws assertTrue(neutralDescriptor.contains(GpuIrNoOpProposalProvider.class.getName())); assertTrue(neutralDescriptor.contains(GpuIrTextCanonicalizationProposalProvider.class.getName())); assertTrue(neutralDescriptor.contains(GpuIrHelperDependencyDeduplicationProposalProvider.class.getName())); + assertTrue(neutralDescriptor.contains(GpuIrBackendNeutralSourceMaterializationProposalProvider.class.getName())); assertTrue(neutralDescriptor.contains(GpuIrConstantFoldingPreviewProposalProvider.class.getName())); + assertTrue(neutralDescriptor.contains(GpuIrConstantFoldingMaterializationProposalProvider.class.getName())); assertTrue(neutralDescriptor.contains(GpuIrSafeLocalCsePreviewProposalProvider.class.getName())); + assertTrue(neutralDescriptor.contains(GpuIrSafeLocalCseMaterializationProposalProvider.class.getName())); + assertTrue(neutralDescriptor.contains(GpuIrMadFmaMaterializationProposalProvider.class.getName())); + assertTrue(neutralDescriptor.contains(GpuIrClampMaterializationProposalProvider.class.getName())); + assertTrue(neutralDescriptor.contains(GpuIrStepMaterializationProposalProvider.class.getName())); + assertTrue(neutralDescriptor.contains(GpuIrMixMaterializationProposalProvider.class.getName())); assertTrue(neutralDescriptor.contains(GpuIrTypedDeadCodePreviewProposalProvider.class.getName())); + assertTrue(neutralDescriptor.contains(GpuIrTypedDeadCodeMaterializationProposalProvider.class.getName())); + assertTrue(neutralDescriptor.contains(GpuIrLoopVectorizationMaterializationProposalProvider.class.getName())); assertTrue(neutralDescriptor.stream().noneMatch(value -> value.contains("irvendoroptimizer"))); assertTrue(vendorDescriptor.isEmpty()); } @@ -98,8 +127,9 @@ void bridgeKeepsCanonicalizationProposalOnlyByDefault() { .run(new GpuRuntimeIrOptimizationRequest(request(original), Optional.of(original))); assertSame(original, report.artifact().orElseThrow()); + assertEquals("body\n return original\n", report.candidateArtifact().orElseThrow().module().methodBodies().get(0).body()); assertFalse(report.requiresRollback()); - assertEquals(6, report.passReports().size()); + assertEquals(15, report.passReports().size()); assertEquals(GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, report.passReports().get(1).stage()); assertEquals(GpuRuntimeIrOptimizationOutcome.SKIPPED, report.passReports().get(1).outcome()); assertEquals("proposal-only", report.passReports().get(1).proofStatus()); @@ -124,6 +154,214 @@ void bridgeKeepsCanonicalizationProposalOnlyByDefault() { )); } + @Test + void bridgeMaterializesConstantFoldingCandidateButKeepsOriginalSelectedByDefault() { + IrGpuArtifact original = constantFoldingArtifact(); + GpuIrProposalRuntimeBridgePass bridge = new GpuIrProposalRuntimeBridgePass( + List.of(new GpuIrConstantFoldingMaterializationProposalProvider()), + GpuIrOptimizationSandwichRunner.alwaysValid(), + false + ); + + GpuRuntimeIrOptimizationReport report = bridge + .run(new GpuRuntimeIrOptimizationRequest(request(original), Optional.of(original))); + + assertSame(original, report.artifact().orElseThrow()); + IrGpuArtifact candidate = report.candidateArtifact().orElseThrow(); + assertEquals("body\n set output[0] = 5\n", candidate.module().methodBodies().get(0).body()); + assertTrue(candidate.regenerationMetadata().backendNeutralSourceReady()); + assertEquals(1, report.passReports().size()); + assertEquals(GpuRuntimeIrOptimizationOutcome.SKIPPED, report.passReports().get(0).outcome()); + assertEquals("proposal-only", report.passReports().get(0).proofStatus()); + assertEquals( + "true", + report.passReports().get(0).proofArtifact().fields().get("rewrite.materialized") + ); + assertEquals( + "true", + report.passReports().get(0).proofArtifact().fields().get("runtimeEquivalencePayload.present") + ); + assertEquals( + "true", + report.passReports().get(0).proofArtifact().fields().get("runtimeEquivalencePayload.passed") + ); + assertEquals( + "none", + report.passReports().get(0).proofArtifact().fields().get("runtimeEquivalencePayload.firstBlocker") + ); + assertEquals( + "pending", + report.passReports().get(0).proofArtifact().fields().get("approvalTemplate.status") + ); + assertEquals( + "optimizer-family:constant-folding-materialization:review-candidate", + report.passReports().get(0).proofArtifact().fields().get("approvalTemplate.runtimeEquivalencePayload.comparisonMode") + ); + assertTrue(report.passReports().get(0).proofArtifact().fields() + .get("approvalTemplate.resourcePath") + .startsWith(GpuIrOptimizationApprovalManifest.RESOURCE_DIRECTORY + "approval-")); + assertEquals( + "pending-manifest-validation", + report.passReports().get(0).proofArtifact().fields().get("approvalManifest.status") + ); + assertEquals( + "false", + report.passReports().get(0).proofArtifact().fields().get("approvalManifest.present") + ); + assertEquals( + "false", + report.passReports().get(0).proofArtifact().fields().get("approvalManifest.accepted") + ); + assertEquals( + "approval-manifest-not-loaded", + report.passReports().get(0).proofArtifact().fields().get("approvalManifest.firstBlocker") + ); + assertEquals( + "false", + report.passReports().get(0).proofArtifact().fields().get("optimizedArtifactCandidate.selectionApplied") + ); + assertEquals( + "false", + report.passReports().get(0).proofArtifact().fields().get("optimizedArtifactCandidate.selectedIrReplacement") + ); + } + + @Test + void bridgeChainsReviewOnlyMaterializationsWithoutSelectingOptimizedIr() { + IrGpuArtifact original = chainedMaterializationArtifact(); + GpuIrProposalRuntimeBridgePass bridge = new GpuIrProposalRuntimeBridgePass( + List.of( + new GpuIrConstantFoldingMaterializationProposalProvider(), + new GpuIrSafeLocalCseMaterializationProposalProvider() + ), + GpuIrOptimizationSandwichRunner.alwaysValid(), + false + ); + + GpuRuntimeIrOptimizationReport report = bridge + .run(new GpuRuntimeIrOptimizationRequest(request(original), Optional.of(original))); + + assertSame(original, report.artifact().orElseThrow()); + assertEquals( + "body\n var int tmp = (a + b)\n set output[0] = 5\n set output[1] = tmp\n", + report.candidateArtifact().orElseThrow().module().methodBodies().get(0).body() + ); + assertEquals(2, report.passReports().size()); + assertEquals(GpuRuntimeIrOptimizationOutcome.SKIPPED, report.passReports().get(0).outcome()); + assertEquals(GpuRuntimeIrOptimizationOutcome.SKIPPED, report.passReports().get(1).outcome()); + assertEquals(report.passReports().get(0).transformedIrIdentity(), report.passReports().get(1).originalIrIdentity()); + assertEquals( + "1", + report.passReports().get(0).proofArtifact().fields().get("transformedNode.count") + ); + assertEquals( + "1", + report.passReports().get(1).proofArtifact().fields().get("transformedNode.count") + ); + assertEquals( + "false", + report.passReports().get(1).proofArtifact().fields().get("optimizedArtifactCandidate.selectionApplied") + ); + assertEquals( + "false", + report.passReports().get(1).proofArtifact().fields().get("optimizedArtifactCandidate.selectedIrReplacement") + ); + } + + @Test + void bridgeMaterializesMadFmaCandidateButKeepsOriginalSelectedByDefault() { + IrGpuArtifact original = madFmaArtifact(); + GpuIrProposalRuntimeBridgePass bridge = new GpuIrProposalRuntimeBridgePass( + List.of(new GpuIrMadFmaMaterializationProposalProvider()), + GpuIrOptimizationSandwichRunner.alwaysValid(), + false + ); + + GpuRuntimeIrOptimizationReport report = bridge + .run(new GpuRuntimeIrOptimizationRequest(request(original), Optional.of(original))); + + assertSame(original, report.artifact().orElseThrow()); + assertEquals( + "body\n set output[0] = intrinsic(mad template=\"\" args=[left, right, addend])\n", + report.candidateArtifact().orElseThrow().module().methodBodies().get(0).body() + ); + assertEquals(1, report.passReports().size()); + assertEquals(GpuRuntimeIrOptimizationOutcome.SKIPPED, report.passReports().get(0).outcome()); + assertEquals("proposal-only", report.passReports().get(0).proofStatus()); + Map fields = report.passReports().get(0).proofArtifact().fields(); + assertEquals("mad-fma-materialization", fields.get("optimizerFamily")); + assertEquals("true", fields.get("policy.fastMathAllowed")); + assertEquals("1", fields.get("transformedNode.count")); + assertEquals("true", fields.get("rewrite.materialized")); + assertEquals("true", fields.get("runtimeEquivalencePayload.present")); + assertEquals("true", fields.get("runtimeEquivalencePayload.passed")); + assertEquals("pending", fields.get("approvalTemplate.status")); + assertEquals("false", fields.get("optimizedArtifactCandidate.selectionApplied")); + assertEquals("false", fields.get("optimizedArtifactCandidate.selectedIrReplacement")); + } + + @Test + void bridgeLoadsApprovedManifestAsEvidenceWithoutSelectingOptimizedIr() throws Exception { + IrGpuArtifact original = constantFoldingArtifact(); + GpuRuntimeCompileRequest compileRequest = request(original); + GpuIrOptimizationProposalRequest proposalRequest = proposalRequest(compileRequest, original, false); + GpuIrOptimizationProposal proposal = new GpuIrConstantFoldingMaterializationProposalProvider() + .propose(proposalRequest); + Path classpathRoot = temporaryDirectory.resolve("bridge-approved-manifest"); + writeApprovedManifest(classpathRoot, proposal, proposalRequest); + + ClassLoader previousClassLoader = Thread.currentThread().getContextClassLoader(); + try (URLClassLoader classLoader = new URLClassLoader( + new java.net.URL[]{classpathRoot.toUri().toURL()}, + GpuIrProposalRuntimeBridgePassTest.class.getClassLoader() + )) { + Thread.currentThread().setContextClassLoader(classLoader); + GpuRuntimeIrOptimizationReport report = new GpuIrProposalRuntimeBridgePass( + List.of(new GpuIrConstantFoldingMaterializationProposalProvider()), + GpuIrOptimizationSandwichRunner.alwaysValid(), + false + ).run(new GpuRuntimeIrOptimizationRequest(compileRequest, Optional.of(original))); + + assertSame(original, report.artifact().orElseThrow()); + assertEquals("body\n set output[0] = 5\n", report.candidateArtifact().orElseThrow() + .module().methodBodies().get(0).body()); + Map fields = report.passReports().get(0).proofArtifact().fields(); + assertEquals("accepted", fields.get("approvalManifest.status")); + assertEquals("true", fields.get("approvalManifest.present")); + assertEquals("true", fields.get("approvalManifest.accepted")); + assertEquals("none", fields.get("approvalManifest.firstBlocker")); + assertEquals("disabled", fields.get("approvalManifest.productionMutation")); + assertEquals("disabled", fields.get("approvalManifest.selectedIrReplacement")); + assertEquals("false", fields.get("optimizedArtifactCandidate.selectionApplied")); + assertEquals("false", fields.get("optimizedArtifactCandidate.selectedIrReplacement")); + + GpuBackendModuleArtifact backendArtifact = GpuBackendModuleArtifact.openClSource( + "__kernel void run(__global int* out) { out[0] = 1; }", + "runtime/lowered/run.cl", + "test-lowerer-v1" + ); + GpuRuntimeCompileArtifactDump dump = GpuRuntimeCompileArtifactDumper.dump( + GpuRuntimeCompileArtifactSnapshot.from( + compileRequest, + compileRequest, + backendArtifact, + GpuRuntimeCompileInvalidationStamp.from(compileRequest, backendArtifact, "approval-loader"), + GpuRuntimeCompileProvenance.from(compileRequest), + report + ) + ); + String evidence = dump.artifact(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + assertTrue(evidence.contains("reviewPackage.approvalManifest.status=accepted")); + assertTrue(evidence.contains("reviewPackage.approvalManifest.present.count=1")); + assertTrue(evidence.contains("reviewPackage.approvalManifest.accepted.count=1")); + assertTrue(evidence.contains("reviewPackage.approvalManifest.productionMutation=disabled")); + assertTrue(evidence.contains("optimizedArtifactCandidate.selectionApplied=false")); + assertTrue(evidence.contains("optimizedArtifactCandidate.selectedIrReplacement=false")); + } finally { + Thread.currentThread().setContextClassLoader(previousClassLoader); + } + } + @Test void bridgeCandidateEnvelopeReachesRuntimeEvidenceArtifact() throws Exception { IrGpuArtifact original = artifact("body \r\n return original\t\r\n"); @@ -164,6 +402,12 @@ void bridgeCandidateEnvelopeReachesRuntimeEvidenceArtifact() throws Exception { assertTrue(evidence.contains("optimizedArtifactCandidate.selectionApplied=false")); assertTrue(evidence.contains("optimizedArtifactCandidate.selectedIrReplacement=false")); assertTrue(evidence.contains("pass.0.proofArtifact.field.optimizedArtifactCandidate.status=candidate-ready")); + assertTrue(evidence.contains( + "pass.0.proofArtifact.field.approvalTemplate.resourcePath=META-INF/javatogpu/ir-optimization-approvals/approval-" + )); + assertTrue(evidence.contains( + "pass.0.approvalTemplate.field.resourcePath=META-INF/javatogpu/ir-optimization-approvals/approval-" + )); assertTrue(evidence.contains("pass.0.proofArtifact.field.optimizedArtifactCandidate.selectionApplied=false")); assertTrue(evidence.contains("pass.0.proofArtifact.field.optimizedArtifactCandidate.selectedIrReplacement=false")); @@ -240,6 +484,7 @@ void runtimeRegistrySeesBridgeButKeepsOriginalIrByDefault() { .optimizeWithReport(new GpuRuntimeIrOptimizationRequest(request(original), Optional.of(original))); assertSame(original, report.artifact().orElseThrow()); + assertEquals("body\n return original\n", report.candidateArtifact().orElseThrow().module().methodBodies().get(0).body()); assertTrue(report.passReports().stream().anyMatch(passReport -> GpuIrProposalRuntimeBridgePass.PASS_VERSION.equals(passReport.optimizerVersion()) || passReport.optimizerVersion().startsWith(GpuIrOptimizerModule.MODULE_ID) @@ -256,6 +501,52 @@ private static GpuRuntimeCompileRequest request(IrGpuArtifact artifact) { ); } + private static GpuIrOptimizationProposalRequest proposalRequest( + GpuRuntimeCompileRequest compileRequest, + IrGpuArtifact artifact, + boolean mutationAllowed + ) { + GpuRuntimeDeviceProfile profile = compileRequest.deviceProfile(); + return new GpuIrOptimizationProposalRequest( + artifact, + compileRequest.options().optimizationProfile(), + mutationAllowed, + Map.of( + "backendTarget", compileRequest.options().backendTarget().name(), + "optimizationProfile", compileRequest.options().optimizationProfile(), + "deviceProfile.backendTarget", profile.backendTarget().name(), + "deviceProfile.id", profile.deviceId(), + "deviceProfile.label", profile.deviceLabel(), + "deviceProfile.vendor", profile.vendor(), + "deviceProfile.driverVersion", profile.driverVersion(), + "deviceProfile.apiVersionText", profile.apiVersionText(), + "deviceProfile.deviceClass", profile.deviceClass().name(), + "mutationAllowed", Boolean.toString(mutationAllowed) + ) + ); + } + + private static void writeApprovedManifest( + Path classpathRoot, + GpuIrOptimizationProposal proposal, + GpuIrOptimizationProposalRequest request + ) throws Exception { + Path resource = classpathRoot.resolve(GpuIrOptimizationApprovalManifest.resourcePath(proposal, request)); + Files.createDirectories(resource.getParent()); + Files.writeString(resource, approvedManifestText(proposal, request), StandardCharsets.UTF_8); + } + + private static String approvedManifestText( + GpuIrOptimizationProposal proposal, + GpuIrOptimizationProposalRequest request + ) { + return GpuIrOptimizationApprovalManifest.template(proposal, request) + .replace("status=pending", "status=approved") + .replace("approval.id=REQUIRED", "approval.id=approval:bridge-test") + .replace("approval.approvedBy=REQUIRED", "approval.approvedBy=CI") + .replace("approval.approvedAtUtc=REQUIRED", "approval.approvedAtUtc=2026-07-12T00:00:00Z"); + } + private static IrGpuArtifact artifact(String body) { IrGpuMethodBody methodBody = IrGpuMethodBody.entry( "test.Kernel.run", @@ -273,6 +564,162 @@ private static IrGpuArtifact artifact(String body) { ); } + private static IrGpuArtifact constantFoldingArtifact() { + IrGpuTypedBody typedBody = new IrGpuTypedBody( + IrGpuTypedBody.FORMAT, + List.of(0), + List.of( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(2), + "right", List.of(3) + )), + new IrGpuTypedNode(2, "GpuIrLiteral", Map.of("sourceText", "2"), Map.of()), + new IrGpuTypedNode(3, "GpuIrLiteral", Map.of("sourceText", "3"), Map.of()) + ) + ); + IrGpuMethodBody methodBody = new IrGpuMethodBody( + "entry", + "test.Kernel.run", + "run", + "ir-text-v1", + "body\n set output[0] = (2 + 3)\n", + typedBody, + IrGpuBodyIndex.empty(), + List.of(), + IrGpuSourceLocation.unknown("test.Kernel.run") + ); + IrGpuModule module = new IrGpuModule("test.Kernel.run", "run", List.of(), List.of(), List.of(methodBody)); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + module, + List.of(new IrGpuEntryParameter("output", "int[]", "GLOBAL", false, List.of())), + List.of(IrGpuBackendOutput.openClSource("test.Kernel.run.irgpu")), + "opencl", + "off" + ); + } + + private static IrGpuArtifact chainedMaterializationArtifact() { + IrGpuTypedBody typedBody = new IrGpuTypedBody( + IrGpuTypedBody.FORMAT, + List.of(0, 4, 9), + List.of( + new IrGpuTypedNode(0, "GpuIrVariableDeclaration", Map.of( + "typeName", "int", + "name", "tmp" + ), Map.of("initializer", List.of(1))), + new IrGpuTypedNode(1, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(2), + "right", List.of(3) + )), + new IrGpuTypedNode(2, "GpuIrVariableRef", Map.of("name", "a"), Map.of()), + new IrGpuTypedNode(3, "GpuIrVariableRef", Map.of("name", "b"), Map.of()), + new IrGpuTypedNode(4, "GpuIrAssignment", Map.of(), Map.of( + "target", List.of(5), + "value", List.of(6) + )), + new IrGpuTypedNode(5, "GpuIrVariableRef", Map.of("name", "output[0]"), Map.of()), + new IrGpuTypedNode(6, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(7), + "right", List.of(8) + )), + new IrGpuTypedNode(7, "GpuIrLiteral", Map.of("sourceText", "2"), Map.of()), + new IrGpuTypedNode(8, "GpuIrLiteral", Map.of("sourceText", "3"), Map.of()), + new IrGpuTypedNode(9, "GpuIrAssignment", Map.of(), Map.of( + "target", List.of(10), + "value", List.of(11) + )), + new IrGpuTypedNode(10, "GpuIrVariableRef", Map.of("name", "output[1]"), Map.of()), + new IrGpuTypedNode(11, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(12), + "right", List.of(13) + )), + new IrGpuTypedNode(12, "GpuIrVariableRef", Map.of("name", "a"), Map.of()), + new IrGpuTypedNode(13, "GpuIrVariableRef", Map.of("name", "b"), Map.of()) + ) + ); + IrGpuMethodBody methodBody = new IrGpuMethodBody( + "entry", + "test.Kernel.run", + "run", + "ir-text-v1", + "body\n var int tmp = (a + b)\n set output[0] = (2 + 3)\n set output[1] = (a + b)\n", + typedBody, + IrGpuBodyIndex.empty(), + List.of(), + IrGpuSourceLocation.unknown("test.Kernel.run") + ); + IrGpuModule module = new IrGpuModule("test.Kernel.run", "run", List.of(), List.of(), List.of(methodBody)); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + module, + List.of( + new IrGpuEntryParameter("output", "int[]", "GLOBAL", false, List.of()), + new IrGpuEntryParameter("a", "int", "PRIVATE", false, List.of()), + new IrGpuEntryParameter("b", "int", "PRIVATE", false, List.of()) + ), + List.of(IrGpuBackendOutput.openClSource("test.Kernel.run.irgpu")), + "opencl", + "off" + ); + } + + private static IrGpuArtifact madFmaArtifact() { + IrGpuTypedBody typedBody = new IrGpuTypedBody( + IrGpuTypedBody.FORMAT, + List.of(0), + List.of( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of( + "target", List.of(1), + "value", List.of(2) + )), + new IrGpuTypedNode(1, "GpuIrVariableRef", Map.of("name", "output[0]"), Map.of()), + new IrGpuTypedNode(2, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(3), + "right", List.of(6) + )), + new IrGpuTypedNode(3, "GpuIrBinary", Map.of("operator", "*"), Map.of( + "left", List.of(4), + "right", List.of(5) + )), + new IrGpuTypedNode(4, "GpuIrVariableRef", Map.of("name", "left"), Map.of()), + new IrGpuTypedNode(5, "GpuIrVariableRef", Map.of("name", "right"), Map.of()), + new IrGpuTypedNode(6, "GpuIrVariableRef", Map.of("name", "addend"), Map.of()) + ) + ); + IrGpuMethodBody methodBody = new IrGpuMethodBody( + "entry", + "test.Kernel.run", + "run", + "ir-text-v1", + "body\n set output[0] = ((left * right) + addend)\n", + typedBody, + IrGpuBodyIndex.empty(), + List.of(), + IrGpuSourceLocation.unknown("test.Kernel.run") + ); + IrGpuModule module = new IrGpuModule("test.Kernel.run", "run", List.of(), List.of(), List.of(methodBody)); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + module, + List.of( + new IrGpuEntryParameter("output", "float[]", "GLOBAL", false, List.of()), + new IrGpuEntryParameter("left", "float", "PRIVATE", false, List.of()), + new IrGpuEntryParameter("right", "float", "PRIVATE", false, List.of()), + new IrGpuEntryParameter("addend", "float", "PRIVATE", false, List.of()) + ), + IrGpuLaunchMetadata.defaultOneDimensional(), + IrGpuValidationMetadata.frontendSubset(), + IrGpuFeatureMetadata.none(), + IrGpuOptimizerPolicyMetadata.fromGpuOptimize(true), + IrGpuRegenerationMetadata.transitionalIrText(), + List.of(IrGpuBackendOutput.openClSource("test.Kernel.run.irgpu")), + "opencl", + "off" + ); + } + private static List serviceDescriptorLines(String serviceName) throws Exception { String resourceName = "META-INF/services/" + serviceName; ClassLoader classLoader = GpuIrProposalRuntimeBridgePassTest.class.getClassLoader(); diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrSafeLocalCseMaterializationProposalProviderTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrSafeLocalCseMaterializationProposalProviderTest.java new file mode 100644 index 00000000..a8759dbb --- /dev/null +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrSafeLocalCseMaterializationProposalProviderTest.java @@ -0,0 +1,269 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactIdentity; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBodyIndex; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuEntryParameter; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuSourceLocation; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuIrSafeLocalCseMaterializationProposalProviderTest { + + @Test + void materializesRepeatedPureExpressionAsExistingLocalReference() { + IrGpuArtifact original = artifact( + "body\n var int tmp = (a + b)\n set output[0] = (a + b)\n", + repeatedExpressionTypedBody() + ); + + GpuIrOptimizationProposal proposal = new GpuIrSafeLocalCseMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of("backendTarget", "OPENCL") + )); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertSame(original, proposal.originalArtifact()); + assertNotEquals(IrGpuArtifactIdentity.stableIdentity(original), IrGpuArtifactIdentity.stableIdentity(optimized)); + assertEquals("body\n var int tmp = (a + b)\n set output[0] = tmp\n", + optimized.module().methodBodies().get(0).body()); + IrGpuTypedNode rewrittenNode = optimized.module().methodBodies().get(0).typedBody().nodes().get(6); + assertEquals("GpuIrVariableRef", rewrittenNode.kind()); + assertEquals("tmp", rewrittenNode.attributes().get("name")); + assertTrue(rewrittenNode.children().isEmpty()); + assertTrue(optimized.regenerationMetadata().backendNeutralSourceReady()); + + Map fields = proposal.proofArtifact().fields(); + assertEquals("safe-local-cse-materialization", fields.get("optimizerFamily")); + assertEquals("1", fields.get("localBinding.count")); + assertEquals("1", fields.get("candidate.count")); + assertEquals("1", fields.get("transformedNode.count")); + assertEquals("1", fields.get("bodyTextReplacement.count")); + assertEquals("1", fields.get("fixedPoint.pass.count")); + assertEquals("true", fields.get("rewrite.materialized")); + assertEquals("false", fields.get("provider.mutatesOriginal")); + assertEquals("true", fields.get("runtimeEquivalencePayload.present")); + assertEquals("true", fields.get("runtimeEquivalencePayload.passed")); + assertEquals("static-existing-local-expression-reuse", fields.get("runtimeEquivalencePayload.ReferenceMode")); + assertEquals("1", fields.get("runtimeEquivalencePayload.Case.Count")); + assertEquals("kernel#6=(a + b)->tmp", fields.get("runtimeEquivalencePayload.Case.0.Name")); + assertEquals("safe-local-cse-reuse-existing-local", fields.get("runtimeEquivalencePayload.Case.0.RewriteKind")); + assertEquals("(a + b)", fields.get("runtimeEquivalencePayload.Case.0.Output.0.PreOptimization")); + assertEquals("tmp", fields.get("runtimeEquivalencePayload.Case.0.Output.0.PostOptimization")); + assertEquals("true", fields.get("safety.dominanceProven")); + assertEquals("true", fields.get("safety.sideEffectFreedomProven")); + assertEquals("false", fields.get("safety.newTemporaryIntroduced")); + assertEquals("true", fields.get("openClReview.sourceReady")); + assertEquals("kernel#6=(a + b)->tmp", fields.get("firstTransformedNode")); + assertEquals("(a + b)", fields.get("firstExpression")); + assertEquals("tmp", fields.get("firstReplacement")); + } + + @Test + void materializesRepeatedPureExpressionsToFixedPoint() { + IrGpuArtifact original = artifact( + "body\n var int tmp = (a + b)\n set output[0] = (a + b)\n set output[1] = (a + b)\n", + typedBody( + List.of(0, 4, 9), + new IrGpuTypedNode(0, "GpuIrVariableDeclaration", Map.of( + "typeName", "int", + "name", "tmp" + ), Map.of("initializer", List.of(1))), + binary(1, "+", 2, 3), + variable(2, "a"), + variable(3, "b"), + assignment(4, 5, 6), + variable(5, "output[0]"), + binary(6, "+", 7, 8), + variable(7, "a"), + variable(8, "b"), + assignment(9, 10, 11), + variable(10, "output[1]"), + binary(11, "+", 12, 13), + variable(12, "a"), + variable(13, "b") + ) + ); + + GpuIrOptimizationProposal proposal = new GpuIrSafeLocalCseMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of("backendTarget", "OPENCL") + )); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertEquals( + "body\n var int tmp = (a + b)\n set output[0] = tmp\n set output[1] = tmp\n", + optimized.module().methodBodies().get(0).body() + ); + Map fields = proposal.proofArtifact().fields(); + assertEquals("2", fields.get("candidate.count")); + assertEquals("2", fields.get("transformedNode.count")); + assertEquals("2", fields.get("bodyTextReplacement.count")); + assertEquals("2", fields.get("fixedPoint.pass.count")); + assertEquals("2", fields.get("runtimeEquivalencePayload.Case.Count")); + assertEquals("safe-local-cse-reuse-existing-local", fields.get("runtimeEquivalencePayload.Case.1.RewriteKind")); + } + + @Test + void doesNotMaterializeWithoutExistingLocalBinding() { + IrGpuArtifact original = artifact( + "body\n set output[0] = (a + b)\n", + typedBody( + List.of(0), + assignment(0, 1, 2), + variable(1, "output[0]"), + binary(2, "+", 3, 4), + variable(3, "a"), + variable(4, "b") + ) + ); + + GpuIrOptimizationProposal proposal = new GpuIrSafeLocalCseMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertFalse(proposal.hasOptimizedArtifact()); + assertTrue(proposal.diagnostics().get(0).contains("no-existing-local-expression-binding")); + assertEquals("0", proposal.proofArtifact().fields().get("localBinding.count")); + assertEquals("false", proposal.proofArtifact().fields().get("rewrite.materialized")); + } + + @Test + void blocksControlFlowBoundariesUntilDominanceProofIsWider() { + IrGpuArtifact original = artifact( + "body\n if (flag)\n return a\n", + typedBody( + List.of(0), + new IrGpuTypedNode(0, "GpuIrIf", Map.of(), Map.of("condition", List.of(1))), + variable(1, "flag") + ) + ); + + GpuIrOptimizationProposal proposal = new GpuIrSafeLocalCseMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertFalse(proposal.hasOptimizedArtifact()); + assertTrue(proposal.diagnostics().get(0).contains("control-flow-boundary-present")); + assertEquals("1", proposal.proofArtifact().fields().get("skipped.controlFlowBoundary.count")); + } + + @Test + void skipsWhenRepeatedExpressionIsMissingFromTextBody() { + IrGpuArtifact original = artifact( + "body\n var int tmp = (a + b)\n set output[0] = tmp\n", + repeatedExpressionTypedBody() + ); + + GpuIrOptimizationProposal proposal = new GpuIrSafeLocalCseMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertFalse(proposal.hasOptimizedArtifact()); + assertTrue(proposal.diagnostics().get(0).contains("body-text-pattern-missing")); + assertEquals("1", proposal.proofArtifact().fields().get("localBinding.count")); + assertEquals("1", proposal.proofArtifact().fields().get("candidate.count")); + assertEquals("1", proposal.proofArtifact().fields().get("skipped.bodyTextPatternMissing.count")); + } + + @Test + void materializationProviderIsServiceLoaded() { + List providers = ServiceLoader + .load(GpuIrOptimizationProposalProvider.class) + .stream() + .map(ServiceLoader.Provider::get) + .filter(GpuIrSafeLocalCseMaterializationProposalProvider.class::isInstance) + .toList(); + + assertEquals(1, providers.size()); + } + + private static IrGpuTypedBody repeatedExpressionTypedBody() { + return typedBody( + List.of(0, 4), + new IrGpuTypedNode(0, "GpuIrVariableDeclaration", Map.of( + "typeName", "int", + "name", "tmp" + ), Map.of("initializer", List.of(1))), + binary(1, "+", 2, 3), + variable(2, "a"), + variable(3, "b"), + assignment(4, 5, 6), + variable(5, "output[0]"), + binary(6, "+", 7, 8), + variable(7, "a"), + variable(8, "b") + ); + } + + private static IrGpuTypedNode assignment(int id, int targetId, int valueId) { + return new IrGpuTypedNode(id, "GpuIrAssignment", Map.of(), Map.of( + "target", List.of(targetId), + "value", List.of(valueId) + )); + } + + private static IrGpuTypedNode binary(int id, String operator, int leftId, int rightId) { + return new IrGpuTypedNode(id, "GpuIrBinary", Map.of("operator", operator), Map.of( + "left", List.of(leftId), + "right", List.of(rightId) + )); + } + + private static IrGpuTypedNode variable(int id, String name) { + return new IrGpuTypedNode(id, "GpuIrVariableRef", Map.of("name", name), Map.of()); + } + + private static IrGpuTypedBody typedBody(List rootNodeIds, IrGpuTypedNode... nodes) { + return new IrGpuTypedBody(IrGpuTypedBody.FORMAT, rootNodeIds, List.of(nodes)); + } + + private static IrGpuArtifact artifact(String body, IrGpuTypedBody typedBody) { + IrGpuMethodBody methodBody = new IrGpuMethodBody( + "entry", + "kernel", + "kernel", + "ir-text-v1", + body, + typedBody, + IrGpuBodyIndex.empty(), + List.of(), + IrGpuSourceLocation.unknown("kernel") + ); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + new IrGpuModule("kernel", "kernel", List.of(), List.of(), List.of(methodBody)), + List.of( + new IrGpuEntryParameter("output", "int[]", "GLOBAL", false, List.of()), + new IrGpuEntryParameter("a", "int", "PRIVATE", false, List.of()), + new IrGpuEntryParameter("b", "int", "PRIVATE", false, List.of()) + ), + List.of(IrGpuBackendOutput.openClSource("javatogpu/test/Kernel/kernel.cl")), + "opencl", + "off" + ); + } +} diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrStepMaterializationProposalProviderTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrStepMaterializationProposalProviderTest.java new file mode 100644 index 00000000..19c1fba3 --- /dev/null +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrStepMaterializationProposalProviderTest.java @@ -0,0 +1,208 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBodyIndex; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuEntryParameter; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuFeatureMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuLaunchMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuOptimizerPolicyMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuRegenerationMetadata; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuSourceLocation; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuValidationMetadata; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuIrStepMaterializationProposalProviderTest { + + @Test + void materializesInclusiveTernaryMaskAsDirectOpenClStepReviewCandidate() { + IrGpuArtifact original = artifact(">="); + + GpuIrOptimizationProposal proposal = new GpuIrStepMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of("backendTarget", "OPENCL") + )); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + assertSame(original, proposal.originalArtifact()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertEquals( + "body\n set output[0] = intrinsic(step template=\"\" args=[threshold, blend])\n", + optimized.module().methodBodies().get(0).body() + ); + IrGpuTypedNode replacement = optimized.module().methodBodies().get(0).typedBody().nodes().get(2); + assertEquals("GpuIrIntrinsicCall", replacement.kind()); + assertEquals("step", replacement.attributes().get("name")); + assertEquals("step", replacement.attributes().get("backendName")); + assertEquals(List.of(5, 4), replacement.children().get("arguments")); + assertTrue(optimized.regenerationMetadata().backendNeutralSourceReady()); + + Map fields = proposal.proofArtifact().fields(); + assertEquals("step-materialization", fields.get("optimizerFamily")); + assertEquals("step", fields.get("targetIntrinsic")); + assertEquals("1", fields.get("candidate.count")); + assertEquals("1", fields.get("transformedNode.count")); + assertEquals("1", fields.get("bodyTextReplacement.count")); + assertEquals("1", fields.get("fixedPoint.pass.count")); + assertEquals("1", fields.get("directStep.count")); + assertEquals("0", fields.get("invertedStep.count")); + assertEquals("true", fields.get("rewrite.materialized")); + assertEquals("false", fields.get("provider.mutatesOriginal")); + assertEquals("false", fields.get("safety.fastMathRequired")); + assertEquals("true", fields.get("safety.strictComparisonPreserved")); + assertEquals("true", fields.get("safety.equalityBehaviorPreserved")); + assertEquals("true", fields.get("safety.nanComparisonPreserved")); + assertEquals("true", fields.get("runtimeEquivalencePayload.present")); + assertEquals("true", fields.get("runtimeEquivalencePayload.passed")); + assertEquals("static-ternary-mask-to-step", fields.get("runtimeEquivalencePayload.ReferenceMode")); + assertEquals("1", fields.get("runtimeEquivalencePayload.Case.Count")); + assertEquals("ternary-mask-to-opencl-step", fields.get("runtimeEquivalencePayload.Case.0.RewriteKind")); + assertEquals("threshold", fields.get("runtimeEquivalencePayload.Case.0.Input.1.Value")); + assertEquals("blend", fields.get("runtimeEquivalencePayload.Case.0.Input.2.Value")); + assertEquals(">=", fields.get("runtimeEquivalencePayload.Case.0.Input.3.Value")); + assertEquals("true", fields.get("openClReview.sourceReady")); + assertEquals( + "kernel#2=((blend >= threshold) ? 1.0F : 0.0F)->intrinsic(step template=\"\" args=[threshold, blend])", + fields.get("firstTransformedNode") + ); + } + + @Test + void materializesStrictGreaterThanTernaryMaskAsInvertedOpenClStepReviewCandidate() { + IrGpuArtifact original = artifact(">"); + + GpuIrOptimizationProposal proposal = new GpuIrStepMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of("backendTarget", "OPENCL") + )); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + assertSame(original, proposal.originalArtifact()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertEquals( + "body\n set output[0] = (1.0F - intrinsic(step template=\"\" args=[blend, threshold]))\n", + optimized.module().methodBodies().get(0).body() + ); + + List nodes = optimized.module().methodBodies().get(0).typedBody().nodes(); + IrGpuTypedNode root = nodes.get(2); + assertEquals("GpuIrBinary", root.kind()); + assertEquals("-", root.attributes().get("operator")); + assertEquals(List.of(6), root.children().get("left")); + assertEquals(List.of(8), root.children().get("right")); + IrGpuTypedNode step = nodes.stream() + .filter(node -> node.id() == 8) + .findFirst() + .orElseThrow(); + assertEquals("GpuIrIntrinsicCall", step.kind()); + assertEquals("step", step.attributes().get("backendName")); + assertEquals(List.of(4, 5), step.children().get("arguments")); + assertTrue(optimized.regenerationMetadata().backendNeutralSourceReady()); + + Map fields = proposal.proofArtifact().fields(); + assertEquals("step-materialization", fields.get("optimizerFamily")); + assertEquals("0", fields.get("directStep.count")); + assertEquals("1", fields.get("invertedStep.count")); + assertEquals("false", fields.get("safety.fastMathRequired")); + assertEquals("true", fields.get("safety.strictComparisonPreserved")); + assertEquals("true", fields.get("safety.equalityBehaviorPreserved")); + assertEquals("true", fields.get("safety.nanComparisonPreserved")); + assertEquals("ternary-mask-to-inverted-opencl-step", fields.get("runtimeEquivalencePayload.Case.0.RewriteKind")); + assertEquals("blend", fields.get("runtimeEquivalencePayload.Case.0.Input.1.Value")); + assertEquals("threshold", fields.get("runtimeEquivalencePayload.Case.0.Input.2.Value")); + assertEquals(">", fields.get("runtimeEquivalencePayload.Case.0.Input.3.Value")); + assertEquals( + "kernel#2=((blend > threshold) ? 1.0F : 0.0F)->(1.0F - intrinsic(step template=\"\" args=[blend, threshold]))", + fields.get("firstTransformedNode") + ); + } + + @Test + void materializationProviderIsServiceLoaded() { + List providers = ServiceLoader + .load(GpuIrOptimizationProposalProvider.class) + .stream() + .map(ServiceLoader.Provider::get) + .filter(GpuIrStepMaterializationProposalProvider.class::isInstance) + .toList(); + + assertEquals(1, providers.size()); + } + + private static IrGpuArtifact artifact(String operator) { + IrGpuMethodBody methodBody = new IrGpuMethodBody( + "entry", + "kernel", + "kernel", + "ir-text-v1", + "body\n set output[0] = ((blend " + operator + " threshold) ? 1.0F : 0.0F)\n", + typedBody(operator), + IrGpuBodyIndex.empty(), + List.of(), + IrGpuSourceLocation.unknown("kernel") + ); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + new IrGpuModule("kernel", "kernel", List.of(), List.of(), List.of(methodBody)), + List.of( + new IrGpuEntryParameter("output", "float[]", "GLOBAL", false, List.of()), + new IrGpuEntryParameter("blend", "float", "PRIVATE", false, List.of()), + new IrGpuEntryParameter("threshold", "float", "PRIVATE", false, List.of()) + ), + IrGpuLaunchMetadata.defaultOneDimensional(), + IrGpuValidationMetadata.frontendSubset(), + IrGpuFeatureMetadata.none(), + IrGpuOptimizerPolicyMetadata.defaultStrict(), + IrGpuRegenerationMetadata.transitionalIrText(), + List.of(IrGpuBackendOutput.openClSource("javatogpu/test/Kernel/kernel.cl")), + "opencl", + "off" + ); + } + + private static IrGpuTypedBody typedBody(String operator) { + return new IrGpuTypedBody( + IrGpuTypedBody.FORMAT, + List.of(0), + List.of( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of( + "target", List.of(1), + "value", List.of(2) + )), + new IrGpuTypedNode(1, "GpuIrVariableRef", Map.of("name", "output[0]"), Map.of()), + new IrGpuTypedNode(2, "GpuIrTernary", Map.of(), Map.of( + "condition", List.of(3), + "then", List.of(6), + "else", List.of(7) + )), + new IrGpuTypedNode(3, "GpuIrBinary", Map.of("operator", operator), Map.of( + "left", List.of(4), + "right", List.of(5) + )), + new IrGpuTypedNode(4, "GpuIrVariableRef", Map.of("name", "blend"), Map.of()), + new IrGpuTypedNode(5, "GpuIrVariableRef", Map.of("name", "threshold"), Map.of()), + new IrGpuTypedNode(6, "GpuIrLiteral", Map.of("sourceText", "1.0F"), Map.of()), + new IrGpuTypedNode(7, "GpuIrLiteral", Map.of("sourceText", "0.0F"), Map.of()) + ) + ); + } +} diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedDeadCodeMaterializationProposalProviderTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedDeadCodeMaterializationProposalProviderTest.java new file mode 100644 index 00000000..d9450eae --- /dev/null +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedDeadCodeMaterializationProposalProviderTest.java @@ -0,0 +1,157 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactIdentity; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBodyIndex; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuModule; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuSourceLocation; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuIrTypedDeadCodeMaterializationProposalProviderTest { + + @Test + void materializesUnreachablePureTypedNodesIntoReviewCandidate() { + IrGpuArtifact original = artifact(typedBody( + List.of(0), + new IrGpuTypedNode(0, "GpuIrReturn", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrVariableRef", Map.of("name", "live"), Map.of()), + new IrGpuTypedNode(2, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(3), + "right", List.of(4) + )), + new IrGpuTypedNode(3, "GpuIrLiteral", Map.of("sourceText", "1"), Map.of()), + new IrGpuTypedNode(4, "GpuIrLiteral", Map.of("sourceText", "2"), Map.of()) + )); + + GpuIrOptimizationProposal proposal = new GpuIrTypedDeadCodeMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + assertSame(original, proposal.originalArtifact()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertNotEquals(IrGpuArtifactIdentity.stableIdentity(original), IrGpuArtifactIdentity.stableIdentity(optimized)); + assertEquals(original.module().methodBodies().get(0).body(), optimized.module().methodBodies().get(0).body()); + assertEquals(List.of(0, 1), optimized.module().methodBodies().get(0).typedBody().nodes().stream() + .map(IrGpuTypedNode::id) + .toList()); + assertEquals("materialized-review-candidate", proposal.proofArtifact().verdict()); + assertEquals("typed-dead-code-materialization", proposal.proofArtifact().fields().get("optimizerFamily")); + assertEquals("5", proposal.proofArtifact().fields().get("node.count")); + assertEquals("2", proposal.proofArtifact().fields().get("reachableNode.count")); + assertEquals("3", proposal.proofArtifact().fields().get("unreachableNode.count")); + assertEquals("3", proposal.proofArtifact().fields().get("removedNode.count")); + assertEquals("1", proposal.proofArtifact().fields().get("changedMethodBody.count")); + assertEquals("{GpuIrBinary=1,GpuIrLiteral=2}", proposal.proofArtifact().fields().get("removedKind.counts")); + assertEquals("true", proposal.proofArtifact().fields().get("rewrite.proposed")); + assertEquals("true", proposal.proofArtifact().fields().get("rewrite.materialized")); + assertEquals("false", proposal.proofArtifact().fields().get("provider.mutatesOriginal")); + assertEquals("true", proposal.proofArtifact().fields().get("proof.runtimeEquivalenceRequiredBeforeSelection")); + assertEquals("true", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.required")); + assertEquals("true", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.passed")); + assertEquals("3", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.Count")); + assertEquals("method-name-and-node-id", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.CaseIdentity")); + assertEquals("kernel", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.MethodName")); + assertEquals("2", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.NodeId")); + assertEquals("typed-dead-code-removal", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.Case.0.RewriteKind")); + assertEquals("true", proposal.proofArtifact().fields().get("safety.sideEffectFreedomProven")); + assertEquals("typed-body-root-materialization", proposal.proofArtifact().fields().get("safety.reachabilityScope")); + assertEquals("non-side-effecting-unreachable-typed-nodes", + proposal.proofArtifact().fields().get("safety.removedNodeKindScope")); + assertEquals("none", proposal.proofArtifact().fields().get("firstBlocker")); + } + + @Test + void blocksMaterializationWhenUnreachableNodeMayHaveSideEffects() { + IrGpuArtifact original = artifact(typedBody( + List.of(0), + new IrGpuTypedNode(0, "GpuIrReturn", Map.of(), Map.of("value", List.of(1))), + new IrGpuTypedNode(1, "GpuIrLiteral", Map.of("sourceText", "1"), Map.of()), + new IrGpuTypedNode(2, "GpuIrHelperCall", Map.of("helper", "sideEffect"), Map.of()) + )); + + GpuIrOptimizationProposal proposal = new GpuIrTypedDeadCodeMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertFalse(proposal.hasOptimizedArtifact()); + assertEquals("materialization-no-change", proposal.proofArtifact().verdict()); + assertEquals("1", proposal.proofArtifact().fields().get("blocked.sideEffectingUnreachableNode.count")); + assertEquals("0", proposal.proofArtifact().fields().get("removedNode.count")); + assertEquals("side-effecting-unreachable-node", proposal.proofArtifact().fields().get("firstBlocker")); + assertEquals("false", proposal.proofArtifact().fields().get("rewrite.proposed")); + assertEquals("false", proposal.proofArtifact().fields().get("proof.runtimeEquivalenceRequiredBeforeSelection")); + assertEquals("false", proposal.proofArtifact().fields().get("runtimeEquivalencePayload.required")); + } + + @Test + void blocksMaterializationWhenTypedGraphHasMissingReferences() { + IrGpuArtifact original = artifact(typedBody( + List.of(99, 0), + new IrGpuTypedNode(0, "GpuIrReturn", Map.of(), Map.of("value", List.of(1, 42))), + new IrGpuTypedNode(1, "GpuIrLiteral", Map.of("sourceText", "1"), Map.of()), + new IrGpuTypedNode(2, "GpuIrLiteral", Map.of("sourceText", "2"), Map.of()) + )); + + GpuIrOptimizationProposal proposal = new GpuIrTypedDeadCodeMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.NO_CHANGE, proposal.decision()); + assertFalse(proposal.hasOptimizedArtifact()); + assertEquals("1", proposal.proofArtifact().fields().get("blocked.missingRoot.count")); + assertEquals("1", proposal.proofArtifact().fields().get("blocked.missingChildReference.count")); + assertEquals("missing-root-node", proposal.proofArtifact().fields().get("firstBlocker")); + assertEquals("false", proposal.proofArtifact().fields().get("safety.sideEffectFreedomProven")); + } + + @Test + void materializationProviderIsServiceLoaded() { + List providers = ServiceLoader + .load(GpuIrOptimizationProposalProvider.class) + .stream() + .map(ServiceLoader.Provider::get) + .filter(GpuIrTypedDeadCodeMaterializationProposalProvider.class::isInstance) + .toList(); + + assertEquals(1, providers.size()); + } + + private static IrGpuTypedBody typedBody(List rootNodeIds, IrGpuTypedNode... nodes) { + return new IrGpuTypedBody(IrGpuTypedBody.FORMAT, rootNodeIds, List.of(nodes)); + } + + private static IrGpuArtifact artifact(IrGpuTypedBody typedBody) { + IrGpuMethodBody methodBody = new IrGpuMethodBody( + "entry", + "kernel", + "jtg_kernel", + "ir-text-v1", + "body\n return 1\n", + typedBody, + IrGpuBodyIndex.empty(), + List.of(), + IrGpuSourceLocation.unknown("kernel") + ); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + new IrGpuModule("kernel", "jtg_kernel", List.of(), List.of(), List.of(methodBody)), + List.of(IrGpuBackendOutput.openClSource("test.cl")), + "opencl", + "off" + ); + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java index 0408941d..22c422bf 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java @@ -750,6 +750,15 @@ private static String formatRuntimeIrOptimizerEvidence(GpuRuntimeIrOptimizationR builder.append("approvalTemplate.notApplicable.count=") .append(countApprovalTemplateStatus(irOptimizerReports, "not-applicable")) .append('\n'); + ApprovalTemplatePayloadEvidence approvalTemplatePayload = approvalTemplatePayloadEvidence(irOptimizerReports); + builder.append("approvalTemplate.runtimeEquivalencePayloadRequired.count=") + .append(approvalTemplatePayload.requiredCount()).append('\n'); + builder.append("approvalTemplate.runtimeEquivalencePayloadPresent.count=") + .append(approvalTemplatePayload.presentCount()).append('\n'); + builder.append("approvalTemplate.runtimeEquivalencePayloadPassed.count=") + .append(approvalTemplatePayload.passedCount()).append('\n'); + builder.append("approvalTemplate.runtimeEquivalencePayloadComplete.count=") + .append(approvalTemplatePayload.completeCount()).append('\n'); OptimizedArtifactCandidateEvidence optimizedArtifactCandidate = optimizedArtifactCandidateEvidence(irOptimizerReports); builder.append("optimizedArtifactCandidate.status=").append(optimizedArtifactCandidate.status()).append('\n'); builder.append("optimizedArtifactCandidate.count=").append(optimizedArtifactCandidate.count()).append('\n'); @@ -773,6 +782,22 @@ private static String formatRuntimeIrOptimizerEvidence(GpuRuntimeIrOptimizationR .append(optimizedArtifactCandidate.selectionApplied()).append('\n'); builder.append("optimizedArtifactCandidate.selectedIrReplacement=") .append(optimizedArtifactCandidate.selectedIrReplacement()).append('\n'); + BackendNeutralSourceMaterializationEvidence backendNeutralSourceMaterialization = + backendNeutralSourceMaterializationEvidence(irOptimizerReports); + builder.append("backendNeutralSourceMaterialization.pass.count=") + .append(backendNeutralSourceMaterialization.passCount()).append('\n'); + builder.append("backendNeutralSourceMaterialization.candidate.count=") + .append(backendNeutralSourceMaterialization.candidateCount()).append('\n'); + builder.append("backendNeutralSourceMaterialization.sourceReady.count=") + .append(backendNeutralSourceMaterialization.sourceReadyCount()).append('\n'); + builder.append("backendNeutralSourceMaterialization.sourceLength.total=") + .append(backendNeutralSourceMaterialization.sourceLengthTotal()).append('\n'); + builder.append("backendNeutralSourceMaterialization.materializationOnly.count=") + .append(backendNeutralSourceMaterialization.materializationOnlyCount()).append('\n'); + builder.append("backendNeutralSourceMaterialization.status=") + .append(backendNeutralSourceMaterialization.status()).append('\n'); + builder.append("backendNeutralSourceMaterialization.firstBlocker=") + .append(safePropertyValue(backendNeutralSourceMaterialization.firstBlocker())).append('\n'); ConstantFoldingPreviewEvidence constantFoldingPreview = constantFoldingPreviewEvidence(irOptimizerReports); builder.append("constantFoldingPreview.pass.count=").append(constantFoldingPreview.passCount()).append('\n'); builder.append("constantFoldingPreview.candidate.count=").append(constantFoldingPreview.candidateCount()).append('\n'); @@ -794,6 +819,42 @@ private static String formatRuntimeIrOptimizerEvidence(GpuRuntimeIrOptimizationR .append(constantFoldingPreview.integerOverflowProven()).append('\n'); builder.append("constantFoldingPreview.floatingPointRoundingProven=") .append(constantFoldingPreview.floatingPointRoundingProven()).append('\n'); + ConstantFoldingMaterializationEvidence constantFoldingMaterialization = + constantFoldingMaterializationEvidence(irOptimizerReports); + builder.append("constantFoldingMaterialization.pass.count=") + .append(constantFoldingMaterialization.passCount()).append('\n'); + builder.append("constantFoldingMaterialization.candidate.count=") + .append(constantFoldingMaterialization.candidateCount()).append('\n'); + builder.append("constantFoldingMaterialization.transformedNode.count=") + .append(constantFoldingMaterialization.transformedNodeCount()).append('\n'); + builder.append("constantFoldingMaterialization.literalRewrite.count=") + .append(constantFoldingMaterialization.literalRewriteCount()).append('\n'); + builder.append("constantFoldingMaterialization.identityRewrite.count=") + .append(constantFoldingMaterialization.identityRewriteCount()).append('\n'); + builder.append("constantFoldingMaterialization.fixedPointPass.count=") + .append(constantFoldingMaterialization.fixedPointPassCount()).append('\n'); + builder.append("constantFoldingMaterialization.changedMethodBody.count=") + .append(constantFoldingMaterialization.changedMethodBodyCount()).append('\n'); + builder.append("constantFoldingMaterialization.bodyTextReplacement.count=") + .append(constantFoldingMaterialization.bodyTextReplacementCount()).append('\n'); + builder.append("constantFoldingMaterialization.skipped.divideByZero.count=") + .append(constantFoldingMaterialization.skippedDivideByZeroCount()).append('\n'); + builder.append("constantFoldingMaterialization.skipped.nonEvenDivision.count=") + .append(constantFoldingMaterialization.skippedNonEvenDivisionCount()).append('\n'); + builder.append("constantFoldingMaterialization.runtimeEquivalenceRequiredBeforeSelection=") + .append(constantFoldingMaterialization.runtimeEquivalenceRequiredBeforeSelection()).append('\n'); + builder.append("constantFoldingMaterialization.runtimeEquivalencePayloadRequired=") + .append(constantFoldingMaterialization.runtimeEquivalencePayloadRequired()).append('\n'); + builder.append("constantFoldingMaterialization.runtimeEquivalencePayloadPresent.count=") + .append(constantFoldingMaterialization.runtimeEquivalencePayloadPresentCount()).append('\n'); + builder.append("constantFoldingMaterialization.runtimeEquivalencePassed.count=") + .append(constantFoldingMaterialization.runtimeEquivalencePassedCount()).append('\n'); + builder.append("constantFoldingMaterialization.approvalRequiredBeforeProduction=") + .append(constantFoldingMaterialization.approvalRequiredBeforeProduction()).append('\n'); + builder.append("constantFoldingMaterialization.status=") + .append(constantFoldingMaterialization.status()).append('\n'); + builder.append("constantFoldingMaterialization.firstBlocker=") + .append(safePropertyValue(constantFoldingMaterialization.firstBlocker())).append('\n'); SafeLocalCsePreviewEvidence safeLocalCsePreview = safeLocalCsePreviewEvidence(irOptimizerReports); builder.append("safeLocalCsePreview.pass.count=").append(safeLocalCsePreview.passCount()).append('\n'); builder.append("safeLocalCsePreview.expression.count=") @@ -818,6 +879,174 @@ private static String formatRuntimeIrOptimizerEvidence(GpuRuntimeIrOptimizationR .append(safeLocalCsePreview.dominanceProven()).append('\n'); builder.append("safeLocalCsePreview.sideEffectFreedomProven=") .append(safeLocalCsePreview.sideEffectFreedomProven()).append('\n'); + SafeLocalCseMaterializationEvidence safeLocalCseMaterialization = + safeLocalCseMaterializationEvidence(irOptimizerReports); + builder.append("safeLocalCseMaterialization.pass.count=") + .append(safeLocalCseMaterialization.passCount()).append('\n'); + builder.append("safeLocalCseMaterialization.localBinding.count=") + .append(safeLocalCseMaterialization.localBindingCount()).append('\n'); + builder.append("safeLocalCseMaterialization.candidate.count=") + .append(safeLocalCseMaterialization.candidateCount()).append('\n'); + builder.append("safeLocalCseMaterialization.transformedNode.count=") + .append(safeLocalCseMaterialization.transformedNodeCount()).append('\n'); + builder.append("safeLocalCseMaterialization.changedMethodBody.count=") + .append(safeLocalCseMaterialization.changedMethodBodyCount()).append('\n'); + builder.append("safeLocalCseMaterialization.bodyTextReplacement.count=") + .append(safeLocalCseMaterialization.bodyTextReplacementCount()).append('\n'); + builder.append("safeLocalCseMaterialization.fixedPoint.pass.count=") + .append(safeLocalCseMaterialization.fixedPointPassCount()).append('\n'); + builder.append("safeLocalCseMaterialization.skipped.controlFlowBoundary.count=") + .append(safeLocalCseMaterialization.skippedControlFlowBoundaryCount()).append('\n'); + builder.append("safeLocalCseMaterialization.skipped.unsupportedOperator.count=") + .append(safeLocalCseMaterialization.skippedUnsupportedOperatorCount()).append('\n'); + builder.append("safeLocalCseMaterialization.skipped.impureOperand.count=") + .append(safeLocalCseMaterialization.skippedImpureOperandCount()).append('\n'); + builder.append("safeLocalCseMaterialization.skipped.bodyTextPatternMissing.count=") + .append(safeLocalCseMaterialization.skippedBodyTextPatternMissingCount()).append('\n'); + builder.append("safeLocalCseMaterialization.runtimeEquivalenceRequiredBeforeSelection=") + .append(safeLocalCseMaterialization.runtimeEquivalenceRequiredBeforeSelection()).append('\n'); + builder.append("safeLocalCseMaterialization.runtimeEquivalencePayloadRequired=") + .append(safeLocalCseMaterialization.runtimeEquivalencePayloadRequired()).append('\n'); + builder.append("safeLocalCseMaterialization.runtimeEquivalencePayloadPresent.count=") + .append(safeLocalCseMaterialization.runtimeEquivalencePayloadPresentCount()).append('\n'); + builder.append("safeLocalCseMaterialization.runtimeEquivalencePassed.count=") + .append(safeLocalCseMaterialization.runtimeEquivalencePassedCount()).append('\n'); + builder.append("safeLocalCseMaterialization.approvalRequiredBeforeProduction=") + .append(safeLocalCseMaterialization.approvalRequiredBeforeProduction()).append('\n'); + builder.append("safeLocalCseMaterialization.dominanceProven=") + .append(safeLocalCseMaterialization.dominanceProven()).append('\n'); + builder.append("safeLocalCseMaterialization.sideEffectFreedomProven=") + .append(safeLocalCseMaterialization.sideEffectFreedomProven()).append('\n'); + builder.append("safeLocalCseMaterialization.status=") + .append(safeLocalCseMaterialization.status()).append('\n'); + builder.append("safeLocalCseMaterialization.firstBlocker=") + .append(safePropertyValue(safeLocalCseMaterialization.firstBlocker())).append('\n'); + MadFmaMaterializationEvidence madFmaMaterialization = madFmaMaterializationEvidence(irOptimizerReports); + builder.append("madFmaMaterialization.pass.count=") + .append(madFmaMaterialization.passCount()).append('\n'); + builder.append("madFmaMaterialization.candidate.count=") + .append(madFmaMaterialization.candidateCount()).append('\n'); + builder.append("madFmaMaterialization.transformedNode.count=") + .append(madFmaMaterialization.transformedNodeCount()).append('\n'); + builder.append("madFmaMaterialization.changedMethodBody.count=") + .append(madFmaMaterialization.changedMethodBodyCount()).append('\n'); + builder.append("madFmaMaterialization.bodyTextReplacement.count=") + .append(madFmaMaterialization.bodyTextReplacementCount()).append('\n'); + builder.append("madFmaMaterialization.fixedPoint.pass.count=") + .append(madFmaMaterialization.fixedPointPassCount()).append('\n'); + builder.append("madFmaMaterialization.skipped.fastMathPolicy.count=") + .append(madFmaMaterialization.skippedFastMathPolicyCount()).append('\n'); + builder.append("madFmaMaterialization.skipped.bodyTextPatternMissing.count=") + .append(madFmaMaterialization.skippedBodyTextPatternMissingCount()).append('\n'); + builder.append("madFmaMaterialization.runtimeEquivalenceRequiredBeforeSelection=") + .append(madFmaMaterialization.runtimeEquivalenceRequiredBeforeSelection()).append('\n'); + builder.append("madFmaMaterialization.runtimeEquivalencePayloadRequired=") + .append(madFmaMaterialization.runtimeEquivalencePayloadRequired()).append('\n'); + builder.append("madFmaMaterialization.runtimeEquivalencePayloadPresent.count=") + .append(madFmaMaterialization.runtimeEquivalencePayloadPresentCount()).append('\n'); + builder.append("madFmaMaterialization.runtimeEquivalencePassed.count=") + .append(madFmaMaterialization.runtimeEquivalencePassedCount()).append('\n'); + builder.append("madFmaMaterialization.approvalRequiredBeforeProduction=") + .append(madFmaMaterialization.approvalRequiredBeforeProduction()).append('\n'); + builder.append("madFmaMaterialization.fastMathAllowed=") + .append(madFmaMaterialization.fastMathAllowed()).append('\n'); + builder.append("madFmaMaterialization.status=") + .append(madFmaMaterialization.status()).append('\n'); + builder.append("madFmaMaterialization.firstBlocker=") + .append(safePropertyValue(madFmaMaterialization.firstBlocker())).append('\n'); + IntrinsicMaterializationEvidence clampMaterialization = intrinsicMaterializationEvidence( + irOptimizerReports, + "clamp-materialization" + ); + appendIntrinsicMaterializationEvidence(builder, "clampMaterialization", clampMaterialization); + builder.append("clampMaterialization.strictFloatPreserved=") + .append(clampMaterialization.strictFloatPreserved()).append('\n'); + builder.append("clampMaterialization.argumentOrderPreserved=") + .append(clampMaterialization.argumentOrderPreserved()).append('\n'); + builder.append("clampMaterialization.fastMathRequired=") + .append(clampMaterialization.fastMathRequired()).append('\n'); + IntrinsicMaterializationEvidence stepMaterialization = intrinsicMaterializationEvidence( + irOptimizerReports, + "step-materialization" + ); + appendIntrinsicMaterializationEvidence(builder, "stepMaterialization", stepMaterialization); + builder.append("stepMaterialization.directStep.count=") + .append(stepMaterialization.directStepCount()).append('\n'); + builder.append("stepMaterialization.invertedStep.count=") + .append(stepMaterialization.invertedStepCount()).append('\n'); + builder.append("stepMaterialization.strictFloatPreserved=") + .append(stepMaterialization.strictFloatPreserved()).append('\n'); + builder.append("stepMaterialization.strictComparisonPreserved=") + .append(stepMaterialization.strictComparisonPreserved()).append('\n'); + builder.append("stepMaterialization.equalityBehaviorPreserved=") + .append(stepMaterialization.equalityBehaviorPreserved()).append('\n'); + builder.append("stepMaterialization.nanComparisonPreserved=") + .append(stepMaterialization.nanComparisonPreserved()).append('\n'); + builder.append("stepMaterialization.fastMathRequired=") + .append(stepMaterialization.fastMathRequired()).append('\n'); + IntrinsicMaterializationEvidence mixMaterialization = intrinsicMaterializationEvidence( + irOptimizerReports, + "mix-materialization" + ); + appendIntrinsicMaterializationEvidence(builder, "mixMaterialization", mixMaterialization); + builder.append("mixMaterialization.canonicalMix.count=") + .append(mixMaterialization.canonicalMixCount()).append('\n'); + builder.append("mixMaterialization.expandedMix.count=") + .append(mixMaterialization.expandedMixCount()).append('\n'); + builder.append("mixMaterialization.madExpandedMix.count=") + .append(mixMaterialization.madExpandedMixCount()).append('\n'); + builder.append("mixMaterialization.fastMathAllowed=") + .append(mixMaterialization.fastMathAllowed()).append('\n'); + builder.append("mixMaterialization.fastMathRequired=") + .append(mixMaterialization.fastMathRequired()).append('\n'); + builder.append("mixMaterialization.strictFloatPreserved=") + .append(mixMaterialization.strictFloatPreserved()).append('\n'); + builder.append("mixMaterialization.algebraicReassociationRequired=") + .append(mixMaterialization.algebraicReassociationRequired()).append('\n'); + builder.append("mixMaterialization.mixArgumentOrderPreserved=") + .append(mixMaterialization.mixArgumentOrderPreserved()).append('\n'); + LoopVectorizationMaterializationEvidence loopVectorizationMaterialization = + loopVectorizationMaterializationEvidence(irOptimizerReports); + builder.append("loopVectorizationMaterialization.pass.count=") + .append(loopVectorizationMaterialization.passCount()).append('\n'); + builder.append("loopVectorizationMaterialization.candidate.count=") + .append(loopVectorizationMaterialization.candidateCount()).append('\n'); + builder.append("loopVectorizationMaterialization.transformedLoop.count=") + .append(loopVectorizationMaterialization.transformedLoopCount()).append('\n'); + builder.append("loopVectorizationMaterialization.changedMethodBody.count=") + .append(loopVectorizationMaterialization.changedMethodBodyCount()).append('\n'); + builder.append("loopVectorizationMaterialization.bodyTextReplacement.count=") + .append(loopVectorizationMaterialization.bodyTextReplacementCount()).append('\n'); + builder.append("loopVectorizationMaterialization.typedBody.materialized.count=") + .append(loopVectorizationMaterialization.typedBodyMaterializedCount()).append('\n'); + builder.append("loopVectorizationMaterialization.typedBody.invalidated.count=") + .append(loopVectorizationMaterialization.typedBodyInvalidatedCount()).append('\n'); + builder.append("loopVectorizationMaterialization.skipped.loopShape.count=") + .append(loopVectorizationMaterialization.skippedLoopShapeCount()).append('\n'); + builder.append("loopVectorizationMaterialization.skipped.unsupportedWidth.count=") + .append(loopVectorizationMaterialization.skippedUnsupportedWidthCount()).append('\n'); + builder.append("loopVectorizationMaterialization.skipped.unsafeLoadPattern.count=") + .append(loopVectorizationMaterialization.skippedUnsafeLoadPatternCount()).append('\n'); + builder.append("loopVectorizationMaterialization.runtimeEquivalenceRequiredBeforeSelection=") + .append(loopVectorizationMaterialization.runtimeEquivalenceRequiredBeforeSelection()).append('\n'); + builder.append("loopVectorizationMaterialization.runtimeEquivalencePayloadRequired=") + .append(loopVectorizationMaterialization.runtimeEquivalencePayloadRequired()).append('\n'); + builder.append("loopVectorizationMaterialization.runtimeEquivalencePayloadPresent.count=") + .append(loopVectorizationMaterialization.runtimeEquivalencePayloadPresentCount()).append('\n'); + builder.append("loopVectorizationMaterialization.runtimeEquivalencePassed.count=") + .append(loopVectorizationMaterialization.runtimeEquivalencePassedCount()).append('\n'); + builder.append("loopVectorizationMaterialization.approvalRequiredBeforeProduction=") + .append(loopVectorizationMaterialization.approvalRequiredBeforeProduction()).append('\n'); + builder.append("loopVectorizationMaterialization.loopTripCountProven=") + .append(loopVectorizationMaterialization.loopTripCountProven()).append('\n'); + builder.append("loopVectorizationMaterialization.contiguousLoadProven=") + .append(loopVectorizationMaterialization.contiguousLoadProven()).append('\n'); + builder.append("loopVectorizationMaterialization.orderedReductionPreserved=") + .append(loopVectorizationMaterialization.orderedReductionPreserved()).append('\n'); + builder.append("loopVectorizationMaterialization.status=") + .append(loopVectorizationMaterialization.status()).append('\n'); + builder.append("loopVectorizationMaterialization.firstBlocker=") + .append(safePropertyValue(loopVectorizationMaterialization.firstBlocker())).append('\n'); TypedDeadCodePreviewEvidence typedDeadCodePreview = typedDeadCodePreviewEvidence(irOptimizerReports); builder.append("typedDeadCodePreview.pass.count=").append(typedDeadCodePreview.passCount()).append('\n'); builder.append("typedDeadCodePreview.node.count=").append(typedDeadCodePreview.nodeCount()).append('\n'); @@ -837,6 +1066,40 @@ private static String formatRuntimeIrOptimizerEvidence(GpuRuntimeIrOptimizationR .append(typedDeadCodePreview.approvalRequiredBeforeRewrite()).append('\n'); builder.append("typedDeadCodePreview.sideEffectFreedomProven=") .append(typedDeadCodePreview.sideEffectFreedomProven()).append('\n'); + TypedDeadCodeMaterializationEvidence typedDeadCodeMaterialization = + typedDeadCodeMaterializationEvidence(irOptimizerReports); + builder.append("typedDeadCodeMaterialization.pass.count=") + .append(typedDeadCodeMaterialization.passCount()).append('\n'); + builder.append("typedDeadCodeMaterialization.node.count=") + .append(typedDeadCodeMaterialization.nodeCount()).append('\n'); + builder.append("typedDeadCodeMaterialization.unreachableNode.count=") + .append(typedDeadCodeMaterialization.unreachableNodeCount()).append('\n'); + builder.append("typedDeadCodeMaterialization.removedNode.count=") + .append(typedDeadCodeMaterialization.removedNodeCount()).append('\n'); + builder.append("typedDeadCodeMaterialization.changedMethodBody.count=") + .append(typedDeadCodeMaterialization.changedMethodBodyCount()).append('\n'); + builder.append("typedDeadCodeMaterialization.blocked.missingRoot.count=") + .append(typedDeadCodeMaterialization.blockedMissingRootCount()).append('\n'); + builder.append("typedDeadCodeMaterialization.blocked.missingChildReference.count=") + .append(typedDeadCodeMaterialization.blockedMissingChildReferenceCount()).append('\n'); + builder.append("typedDeadCodeMaterialization.blocked.sideEffectingUnreachableNode.count=") + .append(typedDeadCodeMaterialization.blockedSideEffectingUnreachableNodeCount()).append('\n'); + builder.append("typedDeadCodeMaterialization.runtimeEquivalenceRequiredBeforeSelection=") + .append(typedDeadCodeMaterialization.runtimeEquivalenceRequiredBeforeSelection()).append('\n'); + builder.append("typedDeadCodeMaterialization.runtimeEquivalencePayloadRequired=") + .append(typedDeadCodeMaterialization.runtimeEquivalencePayloadRequired()).append('\n'); + builder.append("typedDeadCodeMaterialization.runtimeEquivalencePayloadPresent.count=") + .append(typedDeadCodeMaterialization.runtimeEquivalencePayloadPresentCount()).append('\n'); + builder.append("typedDeadCodeMaterialization.runtimeEquivalencePassed.count=") + .append(typedDeadCodeMaterialization.runtimeEquivalencePassedCount()).append('\n'); + builder.append("typedDeadCodeMaterialization.approvalRequiredBeforeProduction=") + .append(typedDeadCodeMaterialization.approvalRequiredBeforeProduction()).append('\n'); + builder.append("typedDeadCodeMaterialization.sideEffectFreedomProven=") + .append(typedDeadCodeMaterialization.sideEffectFreedomProven()).append('\n'); + builder.append("typedDeadCodeMaterialization.status=") + .append(typedDeadCodeMaterialization.status()).append('\n'); + builder.append("typedDeadCodeMaterialization.firstBlocker=") + .append(safePropertyValue(typedDeadCodeMaterialization.firstBlocker())).append('\n'); PreviewReadinessEvidence previewReadiness = previewReadinessEvidence( constantFoldingPreview, safeLocalCsePreview, @@ -850,7 +1113,17 @@ private static String formatRuntimeIrOptimizerEvidence(GpuRuntimeIrOptimizationR .append(previewReadiness.blockedFamilyCount()).append('\n'); builder.append("previewReadiness.familySummary=") .append(safePropertyValue(previewReadiness.familySummary())).append('\n'); - RuntimeEquivalenceReviewEvidence runtimeEquivalenceReview = runtimeEquivalenceReviewEvidence(previewReadiness); + RuntimeEquivalenceReviewEvidence runtimeEquivalenceReview = runtimeEquivalenceReviewEvidence( + previewReadiness, + constantFoldingMaterialization, + safeLocalCseMaterialization, + madFmaMaterialization, + clampMaterialization, + stepMaterialization, + mixMaterialization, + loopVectorizationMaterialization, + typedDeadCodeMaterialization + ); builder.append("runtimeEquivalenceReview.status=").append(runtimeEquivalenceReview.status()).append('\n'); builder.append("runtimeEquivalenceReview.eligible=").append(runtimeEquivalenceReview.eligible()).append('\n'); builder.append("runtimeEquivalenceReview.required=").append(runtimeEquivalenceReview.required()).append('\n'); @@ -871,6 +1144,25 @@ private static String formatRuntimeIrOptimizerEvidence(GpuRuntimeIrOptimizationR builder.append("reviewPackage.pendingApproval.count=").append(reviewPackage.pendingApprovalCount()).append('\n'); builder.append("reviewPackage.runtimeEquivalence.status=") .append(safePropertyValue(reviewPackage.runtimeEquivalenceStatus())).append('\n'); + ApprovalManifestPackageEvidence approvalManifestPackage = approvalManifestPackageEvidence( + irOptimizerReports, + reviewPackage + ); + builder.append("reviewPackage.approvalManifest.status=") + .append(approvalManifestPackage.status()).append('\n'); + builder.append("reviewPackage.approvalManifest.required=") + .append(approvalManifestPackage.required()).append('\n'); + builder.append("reviewPackage.approvalManifest.present.count=") + .append(approvalManifestPackage.presentCount()).append('\n'); + builder.append("reviewPackage.approvalManifest.accepted.count=") + .append(approvalManifestPackage.acceptedCount()).append('\n'); + builder.append("reviewPackage.approvalManifest.resourcePath.summary=") + .append(safePropertyValue(approvalManifestPackage.resourcePathSummary())).append('\n'); + builder.append("reviewPackage.approvalManifest.firstBlocker=") + .append(safePropertyValue(approvalManifestPackage.firstBlocker())).append('\n'); + builder.append("reviewPackage.approvalManifest.manualReviewOnly=true\n"); + builder.append("reviewPackage.approvalManifest.productionMutation=disabled\n"); + builder.append("reviewPackage.approvalManifest.selectedIrReplacement=disabled\n"); builder.append("reviewPackage.originalIrRequired=true\n"); builder.append("reviewPackage.optimizedIrRequired=true\n"); builder.append("reviewPackage.proofSummaryRequired=true\n"); @@ -916,6 +1208,11 @@ private static String formatRuntimeIrOptimizerEvidence(GpuRuntimeIrOptimizationR builder.append(prefix) .append("approvalTemplate.resourceDirectory=META-INF/javatogpu/ir-optimization-approvals/\n"); } + approvalTemplate.fields().entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .forEach(entry -> builder.append(prefix).append("approvalTemplate.field.") + .append(safePropertyValue(entry.getKey())).append('=') + .append(safePropertyValue(entry.getValue())).append('\n')); builder.append(prefix).append("diagnostic.count=").append(passReport.diagnostics().size()).append('\n'); for (int diagnosticIndex = 0; diagnosticIndex < passReport.diagnostics().size(); diagnosticIndex++) { builder.append(prefix).append("diagnostic.").append(diagnosticIndex).append('=') @@ -958,6 +1255,50 @@ private static long countApprovalTemplateStatus( .count(); } + private static ApprovalTemplatePayloadEvidence approvalTemplatePayloadEvidence( + List passReports + ) { + int requiredCount = 0; + int presentCount = 0; + int passedCount = 0; + int completeCount = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : passReports) { + ApprovalTemplateEvidence approvalTemplate = approvalTemplateEvidence(passReport); + if (!approvalTemplate.applicable()) { + continue; + } + Map fields = approvalTemplate.fields(); + if (parseBoolean(fields.get("runtimeEquivalencePayload.required"))) { + requiredCount++; + } + if (parseBoolean(fields.get("runtimeEquivalencePayload.present"))) { + presentCount++; + } + if (parseBoolean(fields.get("runtimeEquivalencePayload.passed"))) { + passedCount++; + } + if (approvalTemplatePayloadComplete(fields)) { + completeCount++; + } + } + return new ApprovalTemplatePayloadEvidence(requiredCount, presentCount, passedCount, completeCount); + } + + private static boolean approvalTemplatePayloadComplete(Map fields) { + return parseBoolean(fields.get("runtimeEquivalencePayload.present")) + && parseBoolean(fields.get("runtimeEquivalencePayload.passed")) + && parseBoolean(fields.get("runtimeEquivalencePayload.componentsComplete")) + && parseNonNegativeInt(fields.get("runtimeEquivalencePayload.caseCount")) > 0; + } + + private record ApprovalTemplatePayloadEvidence( + int requiredCount, + int presentCount, + int passedCount, + int completeCount + ) { + } + private static OptimizedArtifactCandidateEvidence optimizedArtifactCandidateEvidence( List passReports ) { @@ -1045,6 +1386,74 @@ private static boolean isPreferredCandidateBlocker(String currentBlocker, String return "none".equals(currentBlocker) && !"none".equals(candidateBlocker); } + private static boolean isPreferredMaterializationBlocker(String currentBlocker, String candidateBlocker) { + if (candidateBlocker == null || candidateBlocker.isBlank()) { + return false; + } + if (currentBlocker == null || currentBlocker.isBlank() || "not-recorded".equals(currentBlocker)) { + return true; + } + return "none".equals(currentBlocker) && !"none".equals(candidateBlocker); + } + + private static BackendNeutralSourceMaterializationEvidence backendNeutralSourceMaterializationEvidence( + List passReports + ) { + int passCount = 0; + int candidateCount = 0; + int sourceReadyCount = 0; + int sourceLengthTotal = 0; + int materializationOnlyCount = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : passReports) { + if (passReport == null || passReport.proofArtifact() == null) { + continue; + } + Map fields = passReport.proofArtifact().fields(); + String source = passReport.proofArtifact().source() == null ? "" : passReport.proofArtifact().source(); + String optimizerVersion = passReport.optimizerVersion() == null ? "" : passReport.optimizerVersion(); + if (!source.contains("backend-neutral-source-materialization") + && !optimizerVersion.contains("backend-neutral-source-materialization")) { + continue; + } + passCount++; + if (parseBoolean(fields.get("materializationOnly"))) { + materializationOnlyCount++; + candidateCount++; + } + if (parseBoolean(fields.get("sourceGenerated")) || parseBoolean(fields.get("sourceReady"))) { + sourceReadyCount++; + } + sourceLengthTotal += parseNonNegativeInt(fields.get("sourceLength")); + } + String status; + String firstBlocker; + if (passCount <= 0) { + status = "not-recorded"; + firstBlocker = "not-recorded"; + } else if (candidateCount <= 0) { + status = "no-candidates"; + firstBlocker = "backend-neutral-source-materialization-not-needed"; + } else if (sourceReadyCount >= candidateCount) { + status = "review-ready"; + firstBlocker = "none"; + } else if (sourceReadyCount > 0) { + status = "mixed"; + firstBlocker = "backend-neutral-source-partially-materialized"; + } else { + status = "blocked"; + firstBlocker = "backend-neutral-source-not-materialized"; + } + return new BackendNeutralSourceMaterializationEvidence( + passCount, + candidateCount, + sourceReadyCount, + sourceLengthTotal, + materializationOnlyCount, + status, + firstBlocker + ); + } + private static String optimizedArtifactCandidateStatus(int count, int readyCount, int blockedCount) { if (count <= 0) { return "not-recorded"; @@ -1106,6 +1515,992 @@ private static ConstantFoldingPreviewEvidence constantFoldingPreviewEvidence( ); } + private static ConstantFoldingMaterializationEvidence constantFoldingMaterializationEvidence( + List passReports + ) { + int passCount = 0; + int candidateCount = 0; + int transformedNodeCount = 0; + int literalRewriteCount = 0; + int identityRewriteCount = 0; + int fixedPointPassCount = 0; + int changedMethodBodyCount = 0; + int bodyTextReplacementCount = 0; + int skippedDivideByZeroCount = 0; + int skippedNonEvenDivisionCount = 0; + int runtimeEquivalencePayloadPresentCount = 0; + int runtimeEquivalencePassedCount = 0; + boolean runtimeEquivalenceRequiredBeforeSelection = false; + boolean runtimeEquivalencePayloadRequired = false; + boolean approvalRequiredBeforeProduction = false; + String firstBlocker = "not-recorded"; + for (GpuRuntimeIrOptimizationPassReport passReport : passReports) { + if (passReport == null || passReport.proofArtifact() == null) { + continue; + } + Map fields = passReport.proofArtifact().fields(); + String source = passReport.proofArtifact().source() == null ? "" : passReport.proofArtifact().source(); + String optimizerVersion = passReport.optimizerVersion() == null ? "" : passReport.optimizerVersion(); + if (!source.contains("constant-folding-materialization") + && !optimizerVersion.contains("constant-folding-materialization")) { + continue; + } + passCount++; + candidateCount += parseNonNegativeInt(fields.get("candidate.count")); + transformedNodeCount += parseNonNegativeInt(fields.get("transformedNode.count")); + literalRewriteCount += parseNonNegativeInt(fields.get("literalRewrite.count")); + identityRewriteCount += parseNonNegativeInt(fields.get("identityRewrite.count")); + fixedPointPassCount += parseNonNegativeInt(fields.get("fixedPoint.pass.count")); + changedMethodBodyCount += parseNonNegativeInt(fields.get("changedMethodBody.count")); + bodyTextReplacementCount += parseNonNegativeInt(fields.get("bodyTextReplacement.count")); + skippedDivideByZeroCount += parseNonNegativeInt(fields.get("skipped.divideByZero.count")); + skippedNonEvenDivisionCount += parseNonNegativeInt(fields.get("skipped.nonEvenDivision.count")); + runtimeEquivalenceRequiredBeforeSelection |= parseBoolean( + fields.get("proof.runtimeEquivalenceRequiredBeforeSelection") + ); + runtimeEquivalencePayloadRequired |= parseBoolean( + fields.get("proof.runtimeEquivalencePayloadRequiredBeforeSelection") + ) || parseBoolean(fields.get("runtimeEquivalencePayload.required")); + approvalRequiredBeforeProduction |= parseBoolean(fields.get("proof.approvalRequiredBeforeProduction")); + if (parseBoolean(fields.get("runtimeEquivalencePayload.present"))) { + runtimeEquivalencePayloadPresentCount++; + } + if (parseBoolean(fields.get("runtimeEquivalencePayload.passed"))) { + runtimeEquivalencePassedCount++; + } + String blocker = fields.getOrDefault("runtimeEquivalencePayload.firstBlocker", ""); + if (isPreferredMaterializationBlocker(firstBlocker, blocker)) { + firstBlocker = blocker; + } + } + String status; + if (passCount <= 0) { + status = "not-recorded"; + firstBlocker = "not-recorded"; + } else if (transformedNodeCount <= 0) { + status = "no-candidates"; + firstBlocker = "no-materialized-candidates"; + } else if (runtimeEquivalencePayloadPresentCount <= 0) { + status = "pending-runtime-equivalence"; + firstBlocker = "runtime-equivalence-payload-not-recorded"; + } else if (runtimeEquivalencePassedCount < runtimeEquivalencePayloadPresentCount) { + status = "runtime-equivalence-not-passed"; + firstBlocker = "runtime-equivalence-not-passed"; + } else { + status = "review-ready"; + firstBlocker = "none"; + } + return new ConstantFoldingMaterializationEvidence( + passCount, + candidateCount, + transformedNodeCount, + literalRewriteCount, + identityRewriteCount, + fixedPointPassCount, + changedMethodBodyCount, + bodyTextReplacementCount, + skippedDivideByZeroCount, + skippedNonEvenDivisionCount, + runtimeEquivalenceRequiredBeforeSelection, + runtimeEquivalencePayloadRequired, + runtimeEquivalencePayloadPresentCount, + runtimeEquivalencePassedCount, + approvalRequiredBeforeProduction, + status, + firstBlocker + ); + } + + private static TypedDeadCodeMaterializationEvidence typedDeadCodeMaterializationEvidence( + List passReports + ) { + int passCount = 0; + int nodeCount = 0; + int unreachableNodeCount = 0; + int removedNodeCount = 0; + int changedMethodBodyCount = 0; + int blockedMissingRootCount = 0; + int blockedMissingChildReferenceCount = 0; + int blockedSideEffectingUnreachableNodeCount = 0; + int runtimeEquivalencePayloadPresentCount = 0; + int runtimeEquivalencePassedCount = 0; + boolean runtimeEquivalenceRequiredBeforeSelection = false; + boolean runtimeEquivalencePayloadRequired = false; + boolean approvalRequiredBeforeProduction = false; + boolean sideEffectFreedomProven = false; + String firstBlocker = "not-recorded"; + for (GpuRuntimeIrOptimizationPassReport passReport : passReports) { + if (passReport == null || passReport.proofArtifact() == null) { + continue; + } + Map fields = passReport.proofArtifact().fields(); + String source = passReport.proofArtifact().source() == null ? "" : passReport.proofArtifact().source(); + String optimizerVersion = passReport.optimizerVersion() == null ? "" : passReport.optimizerVersion(); + if (!source.contains("typed-dead-code-materialization") + && !optimizerVersion.contains("typed-dead-code-materialization")) { + continue; + } + passCount++; + nodeCount += parseNonNegativeInt(fields.get("node.count")); + unreachableNodeCount += parseNonNegativeInt(fields.get("unreachableNode.count")); + removedNodeCount += parseNonNegativeInt(fields.get("removedNode.count")); + changedMethodBodyCount += parseNonNegativeInt(fields.get("changedMethodBody.count")); + blockedMissingRootCount += parseNonNegativeInt(fields.get("blocked.missingRoot.count")); + blockedMissingChildReferenceCount += parseNonNegativeInt(fields.get("blocked.missingChildReference.count")); + blockedSideEffectingUnreachableNodeCount += parseNonNegativeInt( + fields.get("blocked.sideEffectingUnreachableNode.count") + ); + runtimeEquivalenceRequiredBeforeSelection |= parseBoolean( + fields.get("proof.runtimeEquivalenceRequiredBeforeSelection") + ) || parseBoolean(fields.get("proof.runtimeEquivalencePayloadRequiredBeforeSelection")); + runtimeEquivalencePayloadRequired |= parseBoolean( + fields.get("proof.runtimeEquivalencePayloadRequiredBeforeSelection") + ) || parseBoolean(fields.get("runtimeEquivalencePayload.required")); + approvalRequiredBeforeProduction |= parseBoolean(fields.get("proof.approvalRequiredBeforeProduction")); + sideEffectFreedomProven |= parseBoolean(fields.get("safety.sideEffectFreedomProven")); + if (parseBoolean(fields.get("runtimeEquivalencePayload.present"))) { + runtimeEquivalencePayloadPresentCount++; + } + if (parseBoolean(fields.get("runtimeEquivalencePayload.passed"))) { + runtimeEquivalencePassedCount++; + } + String blocker = fields.getOrDefault("firstBlocker", ""); + if (isPreferredMaterializationBlocker(firstBlocker, blocker)) { + firstBlocker = blocker; + } + blocker = fields.getOrDefault("runtimeEquivalencePayload.firstBlocker", ""); + if (isPreferredMaterializationBlocker(firstBlocker, blocker)) { + firstBlocker = blocker; + } + } + + int blockerCount = blockedMissingRootCount + + blockedMissingChildReferenceCount + + blockedSideEffectingUnreachableNodeCount; + String status; + if (passCount <= 0) { + status = "not-recorded"; + firstBlocker = "not-recorded"; + } else if (removedNodeCount <= 0 && blockerCount > 0) { + status = "blocked"; + if ("not-recorded".equals(firstBlocker) || "none".equals(firstBlocker)) { + firstBlocker = typedDeadCodeMaterializationFirstBlocker( + blockedMissingRootCount, + blockedMissingChildReferenceCount, + blockedSideEffectingUnreachableNodeCount + ); + } + } else if (removedNodeCount <= 0) { + status = "no-candidates"; + firstBlocker = "no-materialized-candidates"; + } else if (!sideEffectFreedomProven) { + status = "blocked"; + firstBlocker = "side-effect-freedom-not-proven"; + } else if (runtimeEquivalencePayloadPresentCount <= 0) { + status = "pending-runtime-equivalence"; + firstBlocker = "runtime-equivalence-payload-not-recorded"; + } else if (runtimeEquivalencePassedCount < runtimeEquivalencePayloadPresentCount) { + status = "runtime-equivalence-not-passed"; + firstBlocker = "runtime-equivalence-not-passed"; + } else { + status = "review-ready"; + firstBlocker = "none"; + } + return new TypedDeadCodeMaterializationEvidence( + passCount, + nodeCount, + unreachableNodeCount, + removedNodeCount, + changedMethodBodyCount, + blockedMissingRootCount, + blockedMissingChildReferenceCount, + blockedSideEffectingUnreachableNodeCount, + runtimeEquivalenceRequiredBeforeSelection, + runtimeEquivalencePayloadRequired, + runtimeEquivalencePayloadPresentCount, + runtimeEquivalencePassedCount, + approvalRequiredBeforeProduction, + sideEffectFreedomProven, + status, + firstBlocker + ); + } + + private static String typedDeadCodeMaterializationFirstBlocker( + int blockedMissingRootCount, + int blockedMissingChildReferenceCount, + int blockedSideEffectingUnreachableNodeCount + ) { + if (blockedMissingRootCount > 0) { + return "missing-root-node"; + } + if (blockedMissingChildReferenceCount > 0) { + return "missing-child-reference"; + } + if (blockedSideEffectingUnreachableNodeCount > 0) { + return "side-effecting-unreachable-node"; + } + return "typed-dead-code-materialization-blocked"; + } + + private static SafeLocalCseMaterializationEvidence safeLocalCseMaterializationEvidence( + List passReports + ) { + int passCount = 0; + int localBindingCount = 0; + int candidateCount = 0; + int transformedNodeCount = 0; + int changedMethodBodyCount = 0; + int bodyTextReplacementCount = 0; + int fixedPointPassCount = 0; + int skippedControlFlowBoundaryCount = 0; + int skippedUnsupportedOperatorCount = 0; + int skippedImpureOperandCount = 0; + int skippedBodyTextPatternMissingCount = 0; + int runtimeEquivalencePayloadPresentCount = 0; + int runtimeEquivalencePassedCount = 0; + boolean runtimeEquivalenceRequiredBeforeSelection = false; + boolean runtimeEquivalencePayloadRequired = false; + boolean approvalRequiredBeforeProduction = false; + boolean dominanceProven = false; + boolean sideEffectFreedomProven = false; + String firstBlocker = "not-recorded"; + for (GpuRuntimeIrOptimizationPassReport passReport : passReports) { + if (passReport == null || passReport.proofArtifact() == null) { + continue; + } + Map fields = passReport.proofArtifact().fields(); + String source = passReport.proofArtifact().source() == null ? "" : passReport.proofArtifact().source(); + String optimizerVersion = passReport.optimizerVersion() == null ? "" : passReport.optimizerVersion(); + if (!source.contains("safe-local-cse-materialization") + && !optimizerVersion.contains("safe-local-cse-materialization")) { + continue; + } + passCount++; + localBindingCount += parseNonNegativeInt(fields.get("localBinding.count")); + candidateCount += parseNonNegativeInt(fields.get("candidate.count")); + transformedNodeCount += parseNonNegativeInt(fields.get("transformedNode.count")); + changedMethodBodyCount += parseNonNegativeInt(fields.get("changedMethodBody.count")); + bodyTextReplacementCount += parseNonNegativeInt(fields.get("bodyTextReplacement.count")); + fixedPointPassCount += parseNonNegativeInt(fields.get("fixedPoint.pass.count")); + skippedControlFlowBoundaryCount += parseNonNegativeInt(fields.get("skipped.controlFlowBoundary.count")); + skippedUnsupportedOperatorCount += parseNonNegativeInt(fields.get("skipped.unsupportedOperator.count")); + skippedImpureOperandCount += parseNonNegativeInt(fields.get("skipped.impureOperand.count")); + skippedBodyTextPatternMissingCount += parseNonNegativeInt(fields.get("skipped.bodyTextPatternMissing.count")); + runtimeEquivalenceRequiredBeforeSelection |= parseBoolean( + fields.get("proof.runtimeEquivalenceRequiredBeforeSelection") + ) || parseBoolean(fields.get("proof.runtimeEquivalencePayloadRequiredBeforeSelection")); + runtimeEquivalencePayloadRequired |= parseBoolean( + fields.get("proof.runtimeEquivalencePayloadRequiredBeforeSelection") + ) || parseBoolean(fields.get("runtimeEquivalencePayload.required")); + approvalRequiredBeforeProduction |= parseBoolean(fields.get("proof.approvalRequiredBeforeProduction")); + dominanceProven |= parseBoolean(fields.get("safety.dominanceProven")); + sideEffectFreedomProven |= parseBoolean(fields.get("safety.sideEffectFreedomProven")); + if (parseBoolean(fields.get("runtimeEquivalencePayload.present"))) { + runtimeEquivalencePayloadPresentCount++; + } + if (parseBoolean(fields.get("runtimeEquivalencePayload.passed"))) { + runtimeEquivalencePassedCount++; + } + String blocker = fields.getOrDefault("firstBlocker", ""); + if (isPreferredMaterializationBlocker(firstBlocker, blocker)) { + firstBlocker = blocker; + } + blocker = fields.getOrDefault("runtimeEquivalencePayload.firstBlocker", ""); + if (isPreferredMaterializationBlocker(firstBlocker, blocker)) { + firstBlocker = blocker; + } + } + + int blockerCount = skippedControlFlowBoundaryCount + + skippedUnsupportedOperatorCount + + skippedImpureOperandCount + + skippedBodyTextPatternMissingCount; + String status; + if (passCount <= 0) { + status = "not-recorded"; + firstBlocker = "not-recorded"; + } else if (transformedNodeCount <= 0 && blockerCount > 0) { + status = "blocked"; + if ("not-recorded".equals(firstBlocker) || "none".equals(firstBlocker)) { + firstBlocker = "safe-local-cse-materialization-blocked"; + } + } else if (transformedNodeCount <= 0) { + status = "no-candidates"; + firstBlocker = "no-materialized-candidates"; + } else if (!dominanceProven) { + status = "blocked"; + firstBlocker = "dominance-not-proven"; + } else if (!sideEffectFreedomProven) { + status = "blocked"; + firstBlocker = "side-effect-freedom-not-proven"; + } else if (runtimeEquivalencePayloadPresentCount <= 0) { + status = "pending-runtime-equivalence"; + firstBlocker = "runtime-equivalence-payload-not-recorded"; + } else if (runtimeEquivalencePassedCount < runtimeEquivalencePayloadPresentCount) { + status = "runtime-equivalence-not-passed"; + firstBlocker = "runtime-equivalence-not-passed"; + } else { + status = "review-ready"; + firstBlocker = "none"; + } + return new SafeLocalCseMaterializationEvidence( + passCount, + localBindingCount, + candidateCount, + transformedNodeCount, + changedMethodBodyCount, + bodyTextReplacementCount, + fixedPointPassCount, + skippedControlFlowBoundaryCount, + skippedUnsupportedOperatorCount, + skippedImpureOperandCount, + skippedBodyTextPatternMissingCount, + runtimeEquivalenceRequiredBeforeSelection, + runtimeEquivalencePayloadRequired, + runtimeEquivalencePayloadPresentCount, + runtimeEquivalencePassedCount, + approvalRequiredBeforeProduction, + dominanceProven, + sideEffectFreedomProven, + status, + firstBlocker + ); + } + + private static MadFmaMaterializationEvidence madFmaMaterializationEvidence( + List passReports + ) { + int passCount = 0; + int candidateCount = 0; + int transformedNodeCount = 0; + int changedMethodBodyCount = 0; + int bodyTextReplacementCount = 0; + int fixedPointPassCount = 0; + int skippedFastMathPolicyCount = 0; + int skippedBodyTextPatternMissingCount = 0; + int runtimeEquivalencePayloadPresentCount = 0; + int runtimeEquivalencePassedCount = 0; + boolean runtimeEquivalenceRequiredBeforeSelection = false; + boolean runtimeEquivalencePayloadRequired = false; + boolean approvalRequiredBeforeProduction = false; + boolean fastMathAllowed = false; + String firstBlocker = "not-recorded"; + for (GpuRuntimeIrOptimizationPassReport passReport : passReports) { + if (passReport == null || passReport.proofArtifact() == null) { + continue; + } + Map fields = passReport.proofArtifact().fields(); + String source = passReport.proofArtifact().source() == null ? "" : passReport.proofArtifact().source(); + String optimizerVersion = passReport.optimizerVersion() == null ? "" : passReport.optimizerVersion(); + if (!source.contains("mad-fma-materialization") + && !optimizerVersion.contains("mad-fma-materialization")) { + continue; + } + passCount++; + candidateCount += parseNonNegativeInt(fields.get("candidate.count")); + transformedNodeCount += parseNonNegativeInt(fields.get("transformedNode.count")); + changedMethodBodyCount += parseNonNegativeInt(fields.get("changedMethodBody.count")); + bodyTextReplacementCount += parseNonNegativeInt(fields.get("bodyTextReplacement.count")); + fixedPointPassCount += parseNonNegativeInt(fields.get("fixedPoint.pass.count")); + skippedFastMathPolicyCount += parseNonNegativeInt(fields.get("skipped.fastMathPolicy.count")); + skippedBodyTextPatternMissingCount += parseNonNegativeInt(fields.get("skipped.bodyTextPatternMissing.count")); + runtimeEquivalenceRequiredBeforeSelection |= parseBoolean( + fields.get("proof.runtimeEquivalenceRequiredBeforeSelection") + ) || parseBoolean(fields.get("proof.runtimeEquivalencePayloadRequiredBeforeSelection")); + runtimeEquivalencePayloadRequired |= parseBoolean( + fields.get("proof.runtimeEquivalencePayloadRequiredBeforeSelection") + ) || parseBoolean(fields.get("runtimeEquivalencePayload.required")); + approvalRequiredBeforeProduction |= parseBoolean(fields.get("proof.approvalRequiredBeforeProduction")); + fastMathAllowed |= parseBoolean(fields.get("policy.fastMathAllowed")) + || parseBoolean(fields.get("safety.fastMathAllowed")); + if (parseBoolean(fields.get("runtimeEquivalencePayload.present"))) { + runtimeEquivalencePayloadPresentCount++; + } + if (parseBoolean(fields.get("runtimeEquivalencePayload.passed"))) { + runtimeEquivalencePassedCount++; + } + String blocker = fields.getOrDefault("firstBlocker", ""); + if (isPreferredMaterializationBlocker(firstBlocker, blocker)) { + firstBlocker = blocker; + } + blocker = fields.getOrDefault("runtimeEquivalencePayload.firstBlocker", ""); + if (isPreferredMaterializationBlocker(firstBlocker, blocker)) { + firstBlocker = blocker; + } + } + + int blockerCount = skippedFastMathPolicyCount + skippedBodyTextPatternMissingCount; + String status; + if (passCount <= 0) { + status = "not-recorded"; + firstBlocker = "not-recorded"; + } else if (transformedNodeCount <= 0 && blockerCount > 0) { + status = "blocked"; + if ("not-recorded".equals(firstBlocker) || "none".equals(firstBlocker)) { + firstBlocker = madFmaMaterializationFirstBlocker( + skippedFastMathPolicyCount, + skippedBodyTextPatternMissingCount + ); + } + } else if (transformedNodeCount <= 0) { + status = "no-candidates"; + firstBlocker = "no-materialized-candidates"; + } else if (!fastMathAllowed) { + status = "blocked"; + firstBlocker = "fast-math-policy-not-enabled"; + } else if (runtimeEquivalencePayloadPresentCount <= 0) { + status = "pending-runtime-equivalence"; + firstBlocker = "runtime-equivalence-payload-not-recorded"; + } else if (runtimeEquivalencePassedCount < runtimeEquivalencePayloadPresentCount) { + status = "runtime-equivalence-not-passed"; + firstBlocker = "runtime-equivalence-not-passed"; + } else { + status = "review-ready"; + firstBlocker = "none"; + } + return new MadFmaMaterializationEvidence( + passCount, + candidateCount, + transformedNodeCount, + changedMethodBodyCount, + bodyTextReplacementCount, + fixedPointPassCount, + skippedFastMathPolicyCount, + skippedBodyTextPatternMissingCount, + runtimeEquivalenceRequiredBeforeSelection, + runtimeEquivalencePayloadRequired, + runtimeEquivalencePayloadPresentCount, + runtimeEquivalencePassedCount, + approvalRequiredBeforeProduction, + fastMathAllowed, + status, + firstBlocker + ); + } + + private static String madFmaMaterializationFirstBlocker( + int skippedFastMathPolicyCount, + int skippedBodyTextPatternMissingCount + ) { + if (skippedFastMathPolicyCount > 0) { + return "fast-math-policy-not-enabled"; + } + if (skippedBodyTextPatternMissingCount > 0) { + return "body-text-pattern-missing"; + } + return "mad-fma-materialization-blocked"; + } + + private static void appendIntrinsicMaterializationEvidence( + StringBuilder builder, + String prefix, + IntrinsicMaterializationEvidence evidence + ) { + builder.append(prefix).append(".pass.count=").append(evidence.passCount()).append('\n'); + builder.append(prefix).append(".candidate.count=").append(evidence.candidateCount()).append('\n'); + builder.append(prefix).append(".transformedNode.count=").append(evidence.transformedNodeCount()).append('\n'); + builder.append(prefix).append(".changedMethodBody.count=") + .append(evidence.changedMethodBodyCount()).append('\n'); + builder.append(prefix).append(".bodyTextReplacement.count=") + .append(evidence.bodyTextReplacementCount()).append('\n'); + builder.append(prefix).append(".fixedPoint.pass.count=") + .append(evidence.fixedPointPassCount()).append('\n'); + builder.append(prefix).append(".skipped.typedBodyMissing.count=") + .append(evidence.skippedTypedBodyMissingCount()).append('\n'); + builder.append(prefix).append(".skipped.unsupportedFormat.count=") + .append(evidence.skippedUnsupportedFormatCount()).append('\n'); + builder.append(prefix).append(".skipped.fastMathPolicy.count=") + .append(evidence.skippedFastMathPolicyCount()).append('\n'); + builder.append(prefix).append(".skipped.missingChildReference.count=") + .append(evidence.skippedMissingChildReferenceCount()).append('\n'); + builder.append(prefix).append(".skipped.unsupportedShape.count=") + .append(evidence.skippedUnsupportedShapeCount()).append('\n'); + builder.append(prefix).append(".skipped.bodyTextPatternMissing.count=") + .append(evidence.skippedBodyTextPatternMissingCount()).append('\n'); + builder.append(prefix).append(".runtimeEquivalenceRequiredBeforeSelection=") + .append(evidence.runtimeEquivalenceRequiredBeforeSelection()).append('\n'); + builder.append(prefix).append(".runtimeEquivalencePayloadRequired=") + .append(evidence.runtimeEquivalencePayloadRequired()).append('\n'); + builder.append(prefix).append(".runtimeEquivalencePayloadPresent.count=") + .append(evidence.runtimeEquivalencePayloadPresentCount()).append('\n'); + builder.append(prefix).append(".runtimeEquivalencePassed.count=") + .append(evidence.runtimeEquivalencePassedCount()).append('\n'); + builder.append(prefix).append(".approvalRequiredBeforeProduction=") + .append(evidence.approvalRequiredBeforeProduction()).append('\n'); + builder.append(prefix).append(".status=").append(evidence.status()).append('\n'); + builder.append(prefix).append(".firstBlocker=") + .append(safePropertyValue(evidence.firstBlocker())).append('\n'); + } + + private static IntrinsicMaterializationEvidence intrinsicMaterializationEvidence( + List passReports, + String familyToken + ) { + int passCount = 0; + int candidateCount = 0; + int transformedNodeCount = 0; + int changedMethodBodyCount = 0; + int bodyTextReplacementCount = 0; + int fixedPointPassCount = 0; + int skippedTypedBodyMissingCount = 0; + int skippedUnsupportedFormatCount = 0; + int skippedFastMathPolicyCount = 0; + int skippedMissingChildReferenceCount = 0; + int skippedUnsupportedShapeCount = 0; + int skippedBodyTextPatternMissingCount = 0; + int runtimeEquivalencePayloadPresentCount = 0; + int runtimeEquivalencePassedCount = 0; + int directStepCount = 0; + int invertedStepCount = 0; + int canonicalMixCount = 0; + int expandedMixCount = 0; + int madExpandedMixCount = 0; + boolean runtimeEquivalenceRequiredBeforeSelection = false; + boolean runtimeEquivalencePayloadRequired = false; + boolean approvalRequiredBeforeProduction = false; + boolean fastMathAllowed = false; + boolean fastMathRequired = false; + boolean strictFloatPreserved = false; + boolean argumentOrderPreserved = false; + boolean strictComparisonPreserved = false; + boolean equalityBehaviorPreserved = false; + boolean nanComparisonPreserved = false; + boolean algebraicReassociationRequired = false; + boolean mixArgumentOrderPreserved = false; + String firstBlocker = "not-recorded"; + for (GpuRuntimeIrOptimizationPassReport passReport : passReports) { + if (passReport == null || passReport.proofArtifact() == null) { + continue; + } + Map fields = passReport.proofArtifact().fields(); + String source = passReport.proofArtifact().source() == null ? "" : passReport.proofArtifact().source(); + String optimizerVersion = passReport.optimizerVersion() == null ? "" : passReport.optimizerVersion(); + String optimizerFamily = fields.getOrDefault("optimizerFamily", ""); + if (!familyToken.equals(optimizerFamily) + && !source.contains(familyToken) + && !optimizerVersion.contains(familyToken)) { + continue; + } + passCount++; + candidateCount += parseNonNegativeInt(fields.get("candidate.count")); + transformedNodeCount += parseNonNegativeInt(fields.get("transformedNode.count")); + changedMethodBodyCount += parseNonNegativeInt(fields.get("changedMethodBody.count")); + bodyTextReplacementCount += parseNonNegativeInt(fields.get("bodyTextReplacement.count")); + fixedPointPassCount += parseNonNegativeInt(fields.get("fixedPoint.pass.count")); + skippedTypedBodyMissingCount += parseNonNegativeInt(fields.get("skipped.typedBodyMissing.count")); + skippedUnsupportedFormatCount += parseNonNegativeInt(fields.get("skipped.unsupportedFormat.count")); + skippedFastMathPolicyCount += parseNonNegativeInt(fields.get("skipped.fastMathPolicy.count")); + skippedMissingChildReferenceCount += parseNonNegativeInt(fields.get("skipped.missingChildReference.count")); + skippedUnsupportedShapeCount += parseNonNegativeInt(fields.get("skipped.unsupportedShape.count")); + skippedBodyTextPatternMissingCount += parseNonNegativeInt(fields.get("skipped.bodyTextPatternMissing.count")); + directStepCount += parseNonNegativeInt(fields.get("directStep.count")); + invertedStepCount += parseNonNegativeInt(fields.get("invertedStep.count")); + canonicalMixCount += parseNonNegativeInt(fields.get("canonicalMix.count")); + expandedMixCount += parseNonNegativeInt(fields.get("expandedMix.count")); + madExpandedMixCount += parseNonNegativeInt(fields.get("madExpandedMix.count")); + runtimeEquivalenceRequiredBeforeSelection |= parseBoolean( + fields.get("proof.runtimeEquivalenceRequiredBeforeSelection") + ) || parseBoolean(fields.get("proof.runtimeEquivalencePayloadRequiredBeforeSelection")); + runtimeEquivalencePayloadRequired |= parseBoolean( + fields.get("proof.runtimeEquivalencePayloadRequiredBeforeSelection") + ) || parseBoolean(fields.get("runtimeEquivalencePayload.required")); + approvalRequiredBeforeProduction |= parseBoolean(fields.get("proof.approvalRequiredBeforeProduction")); + fastMathAllowed |= parseBoolean(fields.get("policy.fastMathAllowed")) + || parseBoolean(fields.get("safety.fastMathAllowed")); + fastMathRequired |= parseBoolean(fields.get("safety.fastMathRequired")); + strictFloatPreserved |= parseBoolean(fields.get("safety.strictFloatPreserved")); + argumentOrderPreserved |= parseBoolean(fields.get("safety.argumentOrderPreserved")); + strictComparisonPreserved |= parseBoolean(fields.get("safety.strictComparisonPreserved")); + equalityBehaviorPreserved |= parseBoolean(fields.get("safety.equalityBehaviorPreserved")); + nanComparisonPreserved |= parseBoolean(fields.get("safety.nanComparisonPreserved")); + algebraicReassociationRequired |= parseBoolean(fields.get("safety.algebraicReassociationRequired")); + mixArgumentOrderPreserved |= parseBoolean(fields.get("safety.mixArgumentOrderPreserved")); + if (parseBoolean(fields.get("runtimeEquivalencePayload.present"))) { + runtimeEquivalencePayloadPresentCount++; + } + if (parseBoolean(fields.get("runtimeEquivalencePayload.passed"))) { + runtimeEquivalencePassedCount++; + } + String blocker = fields.getOrDefault("firstBlocker", ""); + if (isPreferredMaterializationBlocker(firstBlocker, blocker)) { + firstBlocker = blocker; + } + blocker = fields.getOrDefault("runtimeEquivalencePayload.firstBlocker", ""); + if (isPreferredMaterializationBlocker(firstBlocker, blocker)) { + firstBlocker = blocker; + } + } + + int blockerCount = skippedTypedBodyMissingCount + + skippedUnsupportedFormatCount + + skippedFastMathPolicyCount + + skippedMissingChildReferenceCount + + skippedUnsupportedShapeCount + + skippedBodyTextPatternMissingCount; + String status; + if (passCount <= 0) { + status = "not-recorded"; + firstBlocker = "not-recorded"; + } else if (transformedNodeCount <= 0 && blockerCount > 0) { + status = "blocked"; + if ("not-recorded".equals(firstBlocker) || "none".equals(firstBlocker)) { + firstBlocker = intrinsicMaterializationFirstBlocker( + familyToken, + skippedTypedBodyMissingCount, + skippedUnsupportedFormatCount, + skippedFastMathPolicyCount, + skippedMissingChildReferenceCount, + skippedUnsupportedShapeCount, + skippedBodyTextPatternMissingCount + ); + } + } else if (transformedNodeCount <= 0) { + status = "no-candidates"; + firstBlocker = "no-materialized-candidates"; + } else { + String safetyBlocker = intrinsicMaterializationSafetyBlocker( + familyToken, + fastMathAllowed, + fastMathRequired, + strictFloatPreserved, + argumentOrderPreserved, + strictComparisonPreserved, + equalityBehaviorPreserved, + nanComparisonPreserved, + algebraicReassociationRequired, + mixArgumentOrderPreserved, + expandedMixCount, + madExpandedMixCount + ); + if (!"none".equals(safetyBlocker)) { + status = "blocked"; + firstBlocker = safetyBlocker; + } else if (runtimeEquivalencePayloadPresentCount <= 0) { + status = "pending-runtime-equivalence"; + firstBlocker = "runtime-equivalence-payload-not-recorded"; + } else if (runtimeEquivalencePassedCount < runtimeEquivalencePayloadPresentCount) { + status = "runtime-equivalence-not-passed"; + firstBlocker = "runtime-equivalence-not-passed"; + } else { + status = "review-ready"; + firstBlocker = "none"; + } + } + return new IntrinsicMaterializationEvidence( + passCount, + candidateCount, + transformedNodeCount, + changedMethodBodyCount, + bodyTextReplacementCount, + fixedPointPassCount, + skippedTypedBodyMissingCount, + skippedUnsupportedFormatCount, + skippedFastMathPolicyCount, + skippedMissingChildReferenceCount, + skippedUnsupportedShapeCount, + skippedBodyTextPatternMissingCount, + runtimeEquivalenceRequiredBeforeSelection, + runtimeEquivalencePayloadRequired, + runtimeEquivalencePayloadPresentCount, + runtimeEquivalencePassedCount, + approvalRequiredBeforeProduction, + directStepCount, + invertedStepCount, + canonicalMixCount, + expandedMixCount, + madExpandedMixCount, + fastMathAllowed, + fastMathRequired, + strictFloatPreserved, + argumentOrderPreserved, + strictComparisonPreserved, + equalityBehaviorPreserved, + nanComparisonPreserved, + algebraicReassociationRequired, + mixArgumentOrderPreserved, + status, + firstBlocker + ); + } + + private static String intrinsicMaterializationFirstBlocker( + String familyToken, + int skippedTypedBodyMissingCount, + int skippedUnsupportedFormatCount, + int skippedFastMathPolicyCount, + int skippedMissingChildReferenceCount, + int skippedUnsupportedShapeCount, + int skippedBodyTextPatternMissingCount + ) { + if (skippedTypedBodyMissingCount > 0) { + return "typed-body-missing"; + } + if (skippedUnsupportedFormatCount > 0) { + return "unsupported-format"; + } + if (skippedFastMathPolicyCount > 0) { + return "fast-math-policy-not-enabled"; + } + if (skippedMissingChildReferenceCount > 0) { + return "missing-child-reference"; + } + if (skippedUnsupportedShapeCount > 0) { + return "unsupported-shape"; + } + if (skippedBodyTextPatternMissingCount > 0) { + return "body-text-pattern-missing"; + } + return familyToken + "-blocked"; + } + + private static String intrinsicMaterializationSafetyBlocker( + String familyToken, + boolean fastMathAllowed, + boolean fastMathRequired, + boolean strictFloatPreserved, + boolean argumentOrderPreserved, + boolean strictComparisonPreserved, + boolean equalityBehaviorPreserved, + boolean nanComparisonPreserved, + boolean algebraicReassociationRequired, + boolean mixArgumentOrderPreserved, + int expandedMixCount, + int madExpandedMixCount + ) { + return switch (familyToken) { + case "clamp-materialization" -> clampMaterializationSafetyBlocker( + fastMathRequired, + strictFloatPreserved, + argumentOrderPreserved + ); + case "step-materialization" -> stepMaterializationSafetyBlocker( + fastMathRequired, + strictFloatPreserved, + strictComparisonPreserved, + equalityBehaviorPreserved, + nanComparisonPreserved + ); + case "mix-materialization" -> mixMaterializationSafetyBlocker( + fastMathAllowed, + fastMathRequired, + strictFloatPreserved, + algebraicReassociationRequired, + mixArgumentOrderPreserved, + expandedMixCount, + madExpandedMixCount + ); + default -> "none"; + }; + } + + private static String clampMaterializationSafetyBlocker( + boolean fastMathRequired, + boolean strictFloatPreserved, + boolean argumentOrderPreserved + ) { + if (fastMathRequired) { + return "unexpected-fast-math-requirement"; + } + if (!strictFloatPreserved) { + return "strict-float-proof-not-recorded"; + } + if (!argumentOrderPreserved) { + return "argument-order-not-preserved"; + } + return "none"; + } + + private static String stepMaterializationSafetyBlocker( + boolean fastMathRequired, + boolean strictFloatPreserved, + boolean strictComparisonPreserved, + boolean equalityBehaviorPreserved, + boolean nanComparisonPreserved + ) { + if (fastMathRequired) { + return "unexpected-fast-math-requirement"; + } + if (!strictFloatPreserved) { + return "strict-float-proof-not-recorded"; + } + if (!strictComparisonPreserved) { + return "strict-comparison-proof-not-recorded"; + } + if (!equalityBehaviorPreserved) { + return "equality-behavior-proof-not-recorded"; + } + if (!nanComparisonPreserved) { + return "nan-comparison-proof-not-recorded"; + } + return "none"; + } + + private static String mixMaterializationSafetyBlocker( + boolean fastMathAllowed, + boolean fastMathRequired, + boolean strictFloatPreserved, + boolean algebraicReassociationRequired, + boolean mixArgumentOrderPreserved, + int expandedMixCount, + int madExpandedMixCount + ) { + boolean expandedRewrite = fastMathRequired + || algebraicReassociationRequired + || expandedMixCount > 0 + || madExpandedMixCount > 0; + if (!mixArgumentOrderPreserved) { + return "mix-argument-order-not-preserved"; + } + if (expandedRewrite && !fastMathAllowed) { + return "fast-math-policy-not-enabled"; + } + if (!expandedRewrite && !strictFloatPreserved) { + return "strict-float-proof-not-recorded"; + } + return "none"; + } + + private static LoopVectorizationMaterializationEvidence loopVectorizationMaterializationEvidence( + List passReports + ) { + int passCount = 0; + int candidateCount = 0; + int transformedLoopCount = 0; + int changedMethodBodyCount = 0; + int bodyTextReplacementCount = 0; + int typedBodyMaterializedCount = 0; + int typedBodyInvalidatedCount = 0; + int skippedLoopShapeCount = 0; + int skippedUnsupportedWidthCount = 0; + int skippedUnsafeLoadPatternCount = 0; + int runtimeEquivalencePayloadPresentCount = 0; + int runtimeEquivalencePassedCount = 0; + boolean runtimeEquivalenceRequiredBeforeSelection = false; + boolean runtimeEquivalencePayloadRequired = false; + boolean approvalRequiredBeforeProduction = false; + boolean loopTripCountProven = false; + boolean contiguousLoadProven = false; + boolean orderedReductionPreserved = false; + String firstBlocker = "not-recorded"; + for (GpuRuntimeIrOptimizationPassReport passReport : passReports) { + if (passReport == null || passReport.proofArtifact() == null) { + continue; + } + Map fields = passReport.proofArtifact().fields(); + String source = passReport.proofArtifact().source() == null ? "" : passReport.proofArtifact().source(); + String optimizerVersion = passReport.optimizerVersion() == null ? "" : passReport.optimizerVersion(); + if (!source.contains("loop-vectorization-materialization") + && !optimizerVersion.contains("loop-vectorization-materialization")) { + continue; + } + passCount++; + candidateCount += parseNonNegativeInt(fields.get("candidate.count")); + transformedLoopCount += parseNonNegativeInt(fields.get("transformedLoop.count")); + changedMethodBodyCount += parseNonNegativeInt(fields.get("changedMethodBody.count")); + bodyTextReplacementCount += parseNonNegativeInt(fields.get("bodyTextReplacement.count")); + typedBodyMaterializedCount += parseNonNegativeInt(fields.get("typedBody.materialized.count")); + typedBodyInvalidatedCount += parseNonNegativeInt(fields.get("typedBody.invalidated.count")); + skippedLoopShapeCount += parseNonNegativeInt(fields.get("skipped.loopShape.count")); + skippedUnsupportedWidthCount += parseNonNegativeInt(fields.get("skipped.unsupportedWidth.count")); + skippedUnsafeLoadPatternCount += parseNonNegativeInt(fields.get("skipped.unsafeLoadPattern.count")); + runtimeEquivalenceRequiredBeforeSelection |= parseBoolean( + fields.get("proof.runtimeEquivalenceRequiredBeforeSelection") + ) || parseBoolean(fields.get("proof.runtimeEquivalencePayloadRequiredBeforeSelection")); + runtimeEquivalencePayloadRequired |= parseBoolean( + fields.get("proof.runtimeEquivalencePayloadRequiredBeforeSelection") + ) || parseBoolean(fields.get("runtimeEquivalencePayload.required")); + approvalRequiredBeforeProduction |= parseBoolean(fields.get("proof.approvalRequiredBeforeProduction")); + loopTripCountProven |= parseBoolean(fields.get("safety.loopTripCountProven")); + contiguousLoadProven |= parseBoolean(fields.get("safety.contiguousLoadProven")); + orderedReductionPreserved |= parseBoolean(fields.get("safety.orderedReductionPreserved")); + if (parseBoolean(fields.get("runtimeEquivalencePayload.present"))) { + runtimeEquivalencePayloadPresentCount++; + } + if (parseBoolean(fields.get("runtimeEquivalencePayload.passed"))) { + runtimeEquivalencePassedCount++; + } + String blocker = fields.getOrDefault("firstBlocker", ""); + if (isPreferredMaterializationBlocker(firstBlocker, blocker)) { + firstBlocker = blocker; + } + blocker = fields.getOrDefault("runtimeEquivalencePayload.firstBlocker", ""); + if (isPreferredMaterializationBlocker(firstBlocker, blocker)) { + firstBlocker = blocker; + } + } + + int blockerCount = skippedLoopShapeCount + skippedUnsupportedWidthCount + skippedUnsafeLoadPatternCount; + String status; + if (passCount <= 0) { + status = "not-recorded"; + firstBlocker = "not-recorded"; + } else if (transformedLoopCount <= 0 && blockerCount > 0) { + status = "blocked"; + if ("not-recorded".equals(firstBlocker) || "none".equals(firstBlocker)) { + firstBlocker = loopVectorizationMaterializationFirstBlocker( + skippedLoopShapeCount, + skippedUnsupportedWidthCount, + skippedUnsafeLoadPatternCount + ); + } + } else if (transformedLoopCount <= 0) { + status = "no-candidates"; + firstBlocker = "no-materialized-candidates"; + } else if (!loopTripCountProven || !contiguousLoadProven || !orderedReductionPreserved) { + status = "blocked"; + firstBlocker = "loop-vectorization-proof-incomplete"; + } else if (runtimeEquivalencePayloadPresentCount <= 0) { + status = "pending-runtime-equivalence"; + firstBlocker = "runtime-equivalence-payload-not-recorded"; + } else if (runtimeEquivalencePassedCount < runtimeEquivalencePayloadPresentCount) { + status = "runtime-equivalence-not-passed"; + firstBlocker = "runtime-equivalence-not-passed"; + } else { + status = "review-ready"; + firstBlocker = "none"; + } + return new LoopVectorizationMaterializationEvidence( + passCount, + candidateCount, + transformedLoopCount, + changedMethodBodyCount, + bodyTextReplacementCount, + typedBodyMaterializedCount, + typedBodyInvalidatedCount, + skippedLoopShapeCount, + skippedUnsupportedWidthCount, + skippedUnsafeLoadPatternCount, + runtimeEquivalenceRequiredBeforeSelection, + runtimeEquivalencePayloadRequired, + runtimeEquivalencePayloadPresentCount, + runtimeEquivalencePassedCount, + approvalRequiredBeforeProduction, + loopTripCountProven, + contiguousLoadProven, + orderedReductionPreserved, + status, + firstBlocker + ); + } + + private static String loopVectorizationMaterializationFirstBlocker( + int skippedLoopShapeCount, + int skippedUnsupportedWidthCount, + int skippedUnsafeLoadPatternCount + ) { + if (skippedUnsupportedWidthCount > 0) { + return "loop-width-unsupported"; + } + if (skippedUnsafeLoadPatternCount > 0) { + return "load-pattern-not-contiguous-float-reduction"; + } + if (skippedLoopShapeCount > 0) { + return "loop-shape-unsupported"; + } + return "loop-vectorization-materialization-blocked"; + } + private static SafeLocalCsePreviewEvidence safeLocalCsePreviewEvidence( List passReports ) { @@ -1263,12 +2658,64 @@ private static PreviewReadinessEvidence previewReadinessEvidence( } private static RuntimeEquivalenceReviewEvidence runtimeEquivalenceReviewEvidence( - PreviewReadinessEvidence previewReadiness + PreviewReadinessEvidence previewReadiness, + ConstantFoldingMaterializationEvidence constantFoldingMaterialization, + SafeLocalCseMaterializationEvidence safeLocalCseMaterialization, + MadFmaMaterializationEvidence madFmaMaterialization, + IntrinsicMaterializationEvidence clampMaterialization, + IntrinsicMaterializationEvidence stepMaterialization, + IntrinsicMaterializationEvidence mixMaterialization, + LoopVectorizationMaterializationEvidence loopVectorizationMaterialization, + TypedDeadCodeMaterializationEvidence typedDeadCodeMaterialization ) { - boolean required = previewReadiness.candidateFamilyCount() > 0; - boolean eligible = "ready-for-runtime-equivalence-review".equals(previewReadiness.status()); + boolean constantFoldingMaterializedCandidate = constantFoldingMaterialization.transformedNodeCount() > 0; + boolean safeLocalCseMaterializedCandidate = safeLocalCseMaterialization.transformedNodeCount() > 0; + boolean madFmaMaterializedCandidate = madFmaMaterialization.transformedNodeCount() > 0; + boolean clampMaterializedCandidate = clampMaterialization.transformedNodeCount() > 0; + boolean stepMaterializedCandidate = stepMaterialization.transformedNodeCount() > 0; + boolean mixMaterializedCandidate = mixMaterialization.transformedNodeCount() > 0; + boolean loopVectorizationMaterializedCandidate = loopVectorizationMaterialization.transformedLoopCount() > 0; + boolean typedDeadCodeMaterializedCandidate = typedDeadCodeMaterialization.removedNodeCount() > 0; + boolean constantFoldingMaterializationReady = !constantFoldingMaterializedCandidate + || "review-ready".equals(constantFoldingMaterialization.status()); + boolean safeLocalCseMaterializationReady = !safeLocalCseMaterializedCandidate + || "review-ready".equals(safeLocalCseMaterialization.status()); + boolean madFmaMaterializationReady = !madFmaMaterializedCandidate + || "review-ready".equals(madFmaMaterialization.status()); + boolean clampMaterializationReady = !clampMaterializedCandidate + || "review-ready".equals(clampMaterialization.status()); + boolean stepMaterializationReady = !stepMaterializedCandidate + || "review-ready".equals(stepMaterialization.status()); + boolean mixMaterializationReady = !mixMaterializedCandidate + || "review-ready".equals(mixMaterialization.status()); + boolean loopVectorizationMaterializationReady = !loopVectorizationMaterializedCandidate + || "review-ready".equals(loopVectorizationMaterialization.status()); + boolean typedDeadCodeMaterializationReady = !typedDeadCodeMaterializedCandidate + || "review-ready".equals(typedDeadCodeMaterialization.status()); + boolean previewRequired = previewReadiness.candidateFamilyCount() > 0; + boolean previewEligible = !previewRequired + || "ready-for-runtime-equivalence-review".equals(previewReadiness.status()); + boolean required = previewRequired + || constantFoldingMaterializedCandidate + || safeLocalCseMaterializedCandidate + || madFmaMaterializedCandidate + || clampMaterializedCandidate + || stepMaterializedCandidate + || mixMaterializedCandidate + || loopVectorizationMaterializedCandidate + || typedDeadCodeMaterializedCandidate; + boolean eligible = required + && constantFoldingMaterializationReady + && safeLocalCseMaterializationReady + && madFmaMaterializationReady + && clampMaterializationReady + && stepMaterializationReady + && mixMaterializationReady + && loopVectorizationMaterializationReady + && typedDeadCodeMaterializationReady + && previewEligible; String status = eligible ? "review-ready" : "blocked"; - String firstBlocker = switch (previewReadiness.status()) { + String previewBlocker = switch (previewReadiness.status()) { case "ready-for-runtime-equivalence-review" -> "none"; case "not-recorded" -> "preview-readiness-not-recorded"; case "no-candidates" -> "preview-readiness-no-candidates"; @@ -1276,12 +2723,51 @@ private static RuntimeEquivalenceReviewEvidence runtimeEquivalenceReviewEvidence case "candidates-recorded" -> "preview-readiness-candidates-not-proof-clean"; default -> "preview-readiness-unknown"; }; + String firstBlocker; + if (constantFoldingMaterializedCandidate && !constantFoldingMaterializationReady) { + firstBlocker = constantFoldingMaterialization.firstBlocker(); + } else if (safeLocalCseMaterializedCandidate && !safeLocalCseMaterializationReady) { + firstBlocker = safeLocalCseMaterialization.firstBlocker(); + } else if (madFmaMaterializedCandidate && !madFmaMaterializationReady) { + firstBlocker = madFmaMaterialization.firstBlocker(); + } else if (clampMaterializedCandidate && !clampMaterializationReady) { + firstBlocker = clampMaterialization.firstBlocker(); + } else if (stepMaterializedCandidate && !stepMaterializationReady) { + firstBlocker = stepMaterialization.firstBlocker(); + } else if (mixMaterializedCandidate && !mixMaterializationReady) { + firstBlocker = mixMaterialization.firstBlocker(); + } else if (loopVectorizationMaterializedCandidate && !loopVectorizationMaterializationReady) { + firstBlocker = loopVectorizationMaterialization.firstBlocker(); + } else if (typedDeadCodeMaterializedCandidate && !typedDeadCodeMaterializationReady) { + firstBlocker = typedDeadCodeMaterialization.firstBlocker(); + } else if (previewRequired && !previewEligible) { + firstBlocker = previewBlocker; + } else { + firstBlocker = required ? "none" : "review-not-required"; + } + String familySummary = previewReadiness.familySummary() + + ", constant-folding-materialization=" + + constantFoldingMaterialization.status() + + ", safe-local-cse-materialization=" + + safeLocalCseMaterialization.status() + + ", mad-fma-materialization=" + + madFmaMaterialization.status() + + ", clamp-materialization=" + + clampMaterialization.status() + + ", step-materialization=" + + stepMaterialization.status() + + ", mix-materialization=" + + mixMaterialization.status() + + ", loop-vectorization-materialization=" + + loopVectorizationMaterialization.status() + + ", typed-dead-code-materialization=" + + typedDeadCodeMaterialization.status(); return new RuntimeEquivalenceReviewEvidence( status, eligible, required, firstBlocker, - previewReadiness.familySummary() + familySummary ); } @@ -1308,7 +2794,7 @@ private static ReviewPackageEvidence reviewPackageEvidence( String firstBlocker; if (!required) { firstBlocker = "review-package-not-required"; - } else if (!runtimeEquivalenceReview.eligible()) { + } else if (runtimeEquivalenceReview.required() && !runtimeEquivalenceReview.eligible()) { firstBlocker = runtimeEquivalenceReview.firstBlocker(); } else if (pendingApprovalCount > 0) { firstBlocker = "approval-template-pending"; @@ -1329,6 +2815,82 @@ private static ReviewPackageEvidence reviewPackageEvidence( ); } + private static ApprovalManifestPackageEvidence approvalManifestPackageEvidence( + List passReports, + ReviewPackageEvidence reviewPackage + ) { + LinkedHashMap resourcePaths = new LinkedHashMap<>(); + int applicableTemplateCount = 0; + int presentCount = 0; + int acceptedCount = 0; + String firstManifestBlocker = "none"; + for (GpuRuntimeIrOptimizationPassReport passReport : passReports) { + ApprovalTemplateEvidence approvalTemplate = approvalTemplateEvidence(passReport); + if (!approvalTemplate.applicable()) { + continue; + } + applicableTemplateCount++; + String resourcePath = approvalTemplate.fields().getOrDefault("resourcePath", "missing"); + if (!resourcePath.isBlank() && !"missing".equals(resourcePath)) { + resourcePaths.merge(resourcePath, 1, Integer::sum); + } + if (parseBoolean(approvalTemplate.fields().get("approvalManifest.present"))) { + presentCount++; + } + if (parseBoolean(approvalTemplate.fields().get("approvalManifest.accepted"))) { + acceptedCount++; + } + String blocker = approvalTemplate.fields().getOrDefault("approvalManifest.firstBlocker", "none"); + if ("none".equals(firstManifestBlocker) && !"none".equals(blocker)) { + firstManifestBlocker = blocker; + } + } + boolean required = reviewPackage.required() && applicableTemplateCount > 0; + String resourcePathSummary = compactCountSummary(resourcePaths); + String status; + String firstBlocker; + if (!required) { + status = "not-required"; + firstBlocker = "approval-manifest-not-required"; + } else if (resourcePaths.isEmpty()) { + status = "pending-resource-path"; + firstBlocker = "approval-manifest-resource-path-missing"; + } else if (acceptedCount >= applicableTemplateCount) { + status = "accepted"; + firstBlocker = "none"; + } else if (presentCount > 0) { + status = "blocked"; + firstBlocker = "none".equals(firstManifestBlocker) + ? "approval-manifest-validation-blocked" + : firstManifestBlocker; + } else { + status = "pending-manifest-validation"; + firstBlocker = "approval-manifest-not-loaded"; + } + return new ApprovalManifestPackageEvidence( + status, + required, + presentCount, + acceptedCount, + resourcePathSummary, + firstBlocker + ); + } + + private static String compactCountSummary(Map counts) { + if (counts == null || counts.isEmpty()) { + return "none"; + } + StringBuilder builder = new StringBuilder(); + counts.forEach((key, count) -> { + if (!builder.isEmpty()) { + builder.append(", "); + } + builder.append(key).append('=').append(count); + }); + return builder.toString(); + } + private static String previewReadinessStatus(List families) { if (families.stream().allMatch(family -> "not-recorded".equals(family.status()))) { return "not-recorded"; @@ -1412,6 +2974,163 @@ private int proofBlockerCount() { } } + private record ConstantFoldingMaterializationEvidence( + int passCount, + int candidateCount, + int transformedNodeCount, + int literalRewriteCount, + int identityRewriteCount, + int fixedPointPassCount, + int changedMethodBodyCount, + int bodyTextReplacementCount, + int skippedDivideByZeroCount, + int skippedNonEvenDivisionCount, + boolean runtimeEquivalenceRequiredBeforeSelection, + boolean runtimeEquivalencePayloadRequired, + int runtimeEquivalencePayloadPresentCount, + int runtimeEquivalencePassedCount, + boolean approvalRequiredBeforeProduction, + String status, + String firstBlocker + ) { + } + + private record TypedDeadCodeMaterializationEvidence( + int passCount, + int nodeCount, + int unreachableNodeCount, + int removedNodeCount, + int changedMethodBodyCount, + int blockedMissingRootCount, + int blockedMissingChildReferenceCount, + int blockedSideEffectingUnreachableNodeCount, + boolean runtimeEquivalenceRequiredBeforeSelection, + boolean runtimeEquivalencePayloadRequired, + int runtimeEquivalencePayloadPresentCount, + int runtimeEquivalencePassedCount, + boolean approvalRequiredBeforeProduction, + boolean sideEffectFreedomProven, + String status, + String firstBlocker + ) { + } + + private record SafeLocalCseMaterializationEvidence( + int passCount, + int localBindingCount, + int candidateCount, + int transformedNodeCount, + int changedMethodBodyCount, + int bodyTextReplacementCount, + int fixedPointPassCount, + int skippedControlFlowBoundaryCount, + int skippedUnsupportedOperatorCount, + int skippedImpureOperandCount, + int skippedBodyTextPatternMissingCount, + boolean runtimeEquivalenceRequiredBeforeSelection, + boolean runtimeEquivalencePayloadRequired, + int runtimeEquivalencePayloadPresentCount, + int runtimeEquivalencePassedCount, + boolean approvalRequiredBeforeProduction, + boolean dominanceProven, + boolean sideEffectFreedomProven, + String status, + String firstBlocker + ) { + } + + private record MadFmaMaterializationEvidence( + int passCount, + int candidateCount, + int transformedNodeCount, + int changedMethodBodyCount, + int bodyTextReplacementCount, + int fixedPointPassCount, + int skippedFastMathPolicyCount, + int skippedBodyTextPatternMissingCount, + boolean runtimeEquivalenceRequiredBeforeSelection, + boolean runtimeEquivalencePayloadRequired, + int runtimeEquivalencePayloadPresentCount, + int runtimeEquivalencePassedCount, + boolean approvalRequiredBeforeProduction, + boolean fastMathAllowed, + String status, + String firstBlocker + ) { + } + + private record IntrinsicMaterializationEvidence( + int passCount, + int candidateCount, + int transformedNodeCount, + int changedMethodBodyCount, + int bodyTextReplacementCount, + int fixedPointPassCount, + int skippedTypedBodyMissingCount, + int skippedUnsupportedFormatCount, + int skippedFastMathPolicyCount, + int skippedMissingChildReferenceCount, + int skippedUnsupportedShapeCount, + int skippedBodyTextPatternMissingCount, + boolean runtimeEquivalenceRequiredBeforeSelection, + boolean runtimeEquivalencePayloadRequired, + int runtimeEquivalencePayloadPresentCount, + int runtimeEquivalencePassedCount, + boolean approvalRequiredBeforeProduction, + int directStepCount, + int invertedStepCount, + int canonicalMixCount, + int expandedMixCount, + int madExpandedMixCount, + boolean fastMathAllowed, + boolean fastMathRequired, + boolean strictFloatPreserved, + boolean argumentOrderPreserved, + boolean strictComparisonPreserved, + boolean equalityBehaviorPreserved, + boolean nanComparisonPreserved, + boolean algebraicReassociationRequired, + boolean mixArgumentOrderPreserved, + String status, + String firstBlocker + ) { + } + + private record LoopVectorizationMaterializationEvidence( + int passCount, + int candidateCount, + int transformedLoopCount, + int changedMethodBodyCount, + int bodyTextReplacementCount, + int typedBodyMaterializedCount, + int typedBodyInvalidatedCount, + int skippedLoopShapeCount, + int skippedUnsupportedWidthCount, + int skippedUnsafeLoadPatternCount, + boolean runtimeEquivalenceRequiredBeforeSelection, + boolean runtimeEquivalencePayloadRequired, + int runtimeEquivalencePayloadPresentCount, + int runtimeEquivalencePassedCount, + boolean approvalRequiredBeforeProduction, + boolean loopTripCountProven, + boolean contiguousLoadProven, + boolean orderedReductionPreserved, + String status, + String firstBlocker + ) { + } + + private record BackendNeutralSourceMaterializationEvidence( + int passCount, + int candidateCount, + int sourceReadyCount, + int sourceLengthTotal, + int materializationOnlyCount, + String status, + String firstBlocker + ) { + } + private record SafeLocalCsePreviewEvidence( int passCount, int expressionCount, @@ -1505,6 +3224,16 @@ private record ReviewPackageEvidence( ) { } + private record ApprovalManifestPackageEvidence( + String status, + boolean required, + int presentCount, + int acceptedCount, + String resourcePathSummary, + String firstBlocker + ) { + } + private record OptimizedArtifactCandidateEvidence( String status, int count, @@ -1528,26 +3257,26 @@ private boolean selectedIrReplacement() { private static ApprovalTemplateEvidence approvalTemplateEvidence(GpuRuntimeIrOptimizationPassReport passReport) { if (passReport == null) { - return ApprovalTemplateEvidence.notApplicable("pass-report-missing"); + return ApprovalTemplateEvidence.notApplicable("pass-report-missing", Map.of()); } Map proofFields = passReport.proofArtifact().fields(); if ("false".equals(proofFields.get("rewrite.proposed")) || "true".equals(proofFields.get("previewOnly"))) { - return ApprovalTemplateEvidence.notApplicable("proposal-decision-not-proposed"); + return ApprovalTemplateEvidence.notApplicable("proposal-decision-not-proposed", Map.of()); } if (!"proposal-only".equals(passReport.proofStatus()) && !"optimized-selected".equals(passReport.proofStatus())) { - return ApprovalTemplateEvidence.notApplicable("proposal-decision-not-proposed"); + return ApprovalTemplateEvidence.notApplicable("proposal-decision-not-proposed", Map.of()); } if (!distinctIrIdentities(passReport.originalIrIdentity(), passReport.transformedIrIdentity())) { - return ApprovalTemplateEvidence.notApplicable("proposal-identities-not-distinct"); + return ApprovalTemplateEvidence.notApplicable("proposal-identities-not-distinct", Map.of()); } String proofSource = passReport.proofArtifact().source(); if (proofSource == null || proofSource.isBlank() || "none".equals(proofSource) || "ir-optimizer".equals(proofSource)) { - return ApprovalTemplateEvidence.notApplicable("proposal-proof-source-not-specific"); + return ApprovalTemplateEvidence.notApplicable("proposal-proof-source-not-specific", Map.of()); } String proofVerdict = passReport.proofArtifact().verdict() == null ? "" @@ -1556,9 +3285,65 @@ private static ApprovalTemplateEvidence approvalTemplateEvidence(GpuRuntimeIrOpt || proofVerdict.contains("not-proven") || proofVerdict.contains("rejected") || proofVerdict.contains("failed")) { - return ApprovalTemplateEvidence.notApplicable("proposal-proof-verdict-not-accepted"); + return ApprovalTemplateEvidence.notApplicable("proposal-proof-verdict-not-accepted", Map.of()); } - return ApprovalTemplateEvidence.pending(); + return ApprovalTemplateEvidence.pending(approvalTemplateFields(passReport)); + } + + private static Map approvalTemplateFields(GpuRuntimeIrOptimizationPassReport passReport) { + LinkedHashMap fields = new LinkedHashMap<>(); + Map proofFields = passReport.proofArtifact().fields(); + boolean runtimeEquivalencePayloadRequired = parseBoolean( + proofFields.get("proof.runtimeEquivalencePayloadRequiredBeforeSelection") + ) || parseBoolean(proofFields.get("runtimeEquivalencePayload.required")); + boolean runtimeEquivalencePayloadComponentsComplete = parseBoolean( + proofFields.get("runtimeEquivalencePayload.cpuReference.present") + ) && parseBoolean(proofFields.get("runtimeEquivalencePayload.preOptimizationOutput.present")) + && parseBoolean(proofFields.get("runtimeEquivalencePayload.postOptimizationOutput.present")) + && parseBoolean(proofFields.get("runtimeEquivalencePayload.tolerance.present")) + && parseBoolean(proofFields.get("runtimeEquivalencePayload.failureFixture.present")); + fields.put("runtimeEquivalencePayload.required", Boolean.toString(runtimeEquivalencePayloadRequired)); + fields.put( + "runtimeEquivalencePayload.present", + Boolean.toString(parseBoolean(proofFields.get("runtimeEquivalencePayload.present"))) + ); + fields.put( + "runtimeEquivalencePayload.passed", + Boolean.toString(parseBoolean(proofFields.get("runtimeEquivalencePayload.passed"))) + ); + fields.put( + "runtimeEquivalencePayload.componentsComplete", + Boolean.toString(runtimeEquivalencePayloadComponentsComplete) + ); + fields.put( + "runtimeEquivalencePayload.caseCount", + proofFields.getOrDefault("runtimeEquivalencePayload.Case.Count", "0") + ); + fields.put( + "runtimeEquivalencePayload.resource", + runtimeEquivalencePayloadRequired + ? proofFields.getOrDefault("runtimeEquivalencePayload.resource", "missing") + : "not-required" + ); + fields.put( + "runtimeEquivalencePayload.comparisonMode", + runtimeEquivalencePayloadRequired + ? proofFields.getOrDefault("runtimeEquivalencePayload.comparisonMode", "missing") + : "not-required" + ); + fields.put("resourceDirectory", "META-INF/javatogpu/ir-optimization-approvals/"); + fields.put("resourcePath", proofFields.getOrDefault("approvalTemplate.resourcePath", "missing")); + fields.put("approvalManifest.status", proofFields.getOrDefault("approvalManifest.status", "pending-manifest-validation")); + fields.put("approvalManifest.required", proofFields.getOrDefault("approvalManifest.required", "true")); + fields.put("approvalManifest.present", proofFields.getOrDefault("approvalManifest.present", "false")); + fields.put("approvalManifest.accepted", proofFields.getOrDefault("approvalManifest.accepted", "false")); + fields.put("approvalManifest.resourcePath", proofFields.getOrDefault("approvalManifest.resourcePath", "missing")); + fields.put("approvalManifest.resource.count", proofFields.getOrDefault("approvalManifest.resource.count", "0")); + fields.put("approvalManifest.firstBlocker", proofFields.getOrDefault( + "approvalManifest.firstBlocker", + "approval-manifest-not-loaded" + )); + return Map.copyOf(fields); } private static boolean distinctIrIdentities(String original, String transformed) { @@ -1571,13 +3356,22 @@ private static boolean distinctIrIdentities(String original, String transformed) && !normalizedOriginal.equals(normalizedTransformed); } - private record ApprovalTemplateEvidence(String status, boolean applicable, String firstBlocker) { - private static ApprovalTemplateEvidence pending() { - return new ApprovalTemplateEvidence("pending", true, "none"); + private record ApprovalTemplateEvidence( + String status, + boolean applicable, + String firstBlocker, + Map fields + ) { + private ApprovalTemplateEvidence { + fields = fields == null ? Map.of() : Map.copyOf(fields); + } + + private static ApprovalTemplateEvidence pending(Map fields) { + return new ApprovalTemplateEvidence("pending", true, "none", fields); } - private static ApprovalTemplateEvidence notApplicable(String firstBlocker) { - return new ApprovalTemplateEvidence("not-applicable", false, firstBlocker); + private static ApprovalTemplateEvidence notApplicable(String firstBlocker, Map fields) { + return new ApprovalTemplateEvidence("not-applicable", false, firstBlocker, fields); } } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizationReport.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizationReport.java index 113fb062..3e92bc13 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizationReport.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizationReport.java @@ -13,14 +13,15 @@ public record GpuRuntimeIrOptimizationReport( Optional artifact, List passReports, GpuOptimizationStrategyDecision strategyDecision, - List extensionExecutionReports + List extensionExecutionReports, + Optional candidateArtifact ) { public GpuRuntimeIrOptimizationReport( Optional artifact, List passReports ) { - this(artifact, passReports, null, List.of()); + this(artifact, passReports, null, List.of(), Optional.empty()); } public GpuRuntimeIrOptimizationReport( @@ -28,7 +29,24 @@ public GpuRuntimeIrOptimizationReport( List passReports, GpuOptimizationStrategyDecision strategyDecision ) { - this(artifact, passReports, strategyDecision, List.of()); + this(artifact, passReports, strategyDecision, List.of(), Optional.empty()); + } + + public GpuRuntimeIrOptimizationReport( + Optional artifact, + List passReports, + GpuOptimizationStrategyDecision strategyDecision, + List extensionExecutionReports + ) { + this(artifact, passReports, strategyDecision, extensionExecutionReports, Optional.empty()); + } + + public GpuRuntimeIrOptimizationReport( + Optional artifact, + Optional candidateArtifact, + List passReports + ) { + this(artifact, passReports, null, List.of(), candidateArtifact); } public GpuRuntimeIrOptimizationReport { @@ -38,6 +56,7 @@ public GpuRuntimeIrOptimizationReport( ? GpuOptimizationStrategyDecision.none(null) : strategyDecision; extensionExecutionReports = extensionExecutionReports == null ? List.of() : List.copyOf(extensionExecutionReports); + candidateArtifact = candidateArtifact == null ? Optional.empty() : candidateArtifact; } public static GpuRuntimeIrOptimizationReport empty(Optional artifact) { @@ -50,7 +69,30 @@ public GpuRuntimeIrOptimizationReport append(GpuRuntimeIrOptimizationPassReport } java.util.ArrayList reports = new java.util.ArrayList<>(passReports); reports.add(passReport); - return new GpuRuntimeIrOptimizationReport(artifact, reports, strategyDecision, extensionExecutionReports); + return new GpuRuntimeIrOptimizationReport( + artifact, + reports, + strategyDecision, + extensionExecutionReports, + candidateArtifact + ); + } + + public GpuRuntimeIrOptimizationReport withCandidateArtifact(Optional candidateArtifact) { + return new GpuRuntimeIrOptimizationReport( + artifact, + passReports, + strategyDecision, + extensionExecutionReports, + candidateArtifact + ); + } + + public Optional artifactForOptimizedReview() { + if (requiresRollback()) { + return artifact; + } + return candidateArtifact.or(() -> artifact); } public boolean hasReports() { diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistry.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistry.java index 4ec889ac..98b3e1d5 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistry.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistry.java @@ -91,12 +91,14 @@ public Optional optimize(GpuRuntimeIrOptimizationRequest request) public GpuRuntimeIrOptimizationReport optimizeWithReport(GpuRuntimeIrOptimizationRequest request) { Optional currentArtifact = request.artifact(); + Optional currentCandidateArtifact = Optional.empty(); java.util.ArrayList passReports = new java.util.ArrayList<>(); java.util.ArrayList executionReports = new java.util.ArrayList<>(); for (GpuRuntimeIrOptimizationPass pass : optimizerPasses()) { GpuRuntimeIrOptimizationRequest passRequest = new GpuRuntimeIrOptimizationRequest( request.compileRequest(), - currentArtifact + currentArtifact, + request.strategyDecision() ); if (requiresProductionAuthorization(pass, passRequest)) { GpuProductionExtensionAuthorizationDecision authorization = productionAuthorizations.get(pass.extensionId()); @@ -148,6 +150,9 @@ public GpuRuntimeIrOptimizationReport optimizeWithReport(GpuRuntimeIrOptimizatio ).withStage(pass.stage())); break; } + if (passReport.candidateArtifact().isPresent()) { + currentCandidateArtifact = passReport.candidateArtifact(); + } currentArtifact = passReport.artifact(); } catch (RuntimeException exception) { GpuExtensionFailurePolicy failurePolicy = failurePolicy(pass, passRequest); @@ -177,7 +182,8 @@ public GpuRuntimeIrOptimizationReport optimizeWithReport(GpuRuntimeIrOptimizatio currentArtifact, passReports, request.strategyDecision(), - executionReports + executionReports, + currentCandidateArtifact ); } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrSelection.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrSelection.java index 2c16b063..737bceda 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrSelection.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrSelection.java @@ -116,12 +116,13 @@ public static GpuRuntimeIrSelection fromFields( || optimizationReport.requiresRollback() || productionGateBlocked; String fallbackDecision = fallbackDecision(fallbackEvidence, optimizationReport, productionGateBlocked); + String selectedStage = selectedStage(originalArtifact, optimizedArtifact, selectedArtifact); return new GpuRuntimeIrSelection( originalArtifact, optimizedArtifact, selectedArtifact, - selectedStage(originalArtifact, optimizedArtifact, selectedArtifact), + selectedStage, originalIdentity, optimizedIdentity, selectedIdentity, @@ -129,7 +130,7 @@ public static GpuRuntimeIrSelection fromFields( optimizedRejected, fallbackDecision, productionIrGate, - diagnostic(selectedArtifact, transformed, optimizedRejected, optimizedArtifact, productionGateBlocked) + diagnostic(selectedArtifact, selectedStage, transformed, optimizedRejected, optimizedArtifact, productionGateBlocked) ); } @@ -149,6 +150,9 @@ private static Optional selectedArtifact( if (productionGateBlocked && originalArtifact.isPresent()) { return originalArtifact; } + if (optimizationReport.hasReports() && optimizationReport.artifact().isPresent()) { + return optimizationReport.artifact(); + } return optimizedArtifact.or(() -> originalArtifact); } @@ -189,6 +193,7 @@ private static GpuProductionIrAcceptanceGate.Result productionIrGate( private static String diagnostic( Optional selectedArtifact, + String selectedStage, boolean transformed, boolean optimizedRejected, Optional optimizedArtifact, @@ -203,9 +208,12 @@ private static String diagnostic( if (optimizedRejected) { return "optimized IrGpu was rejected; original IrGpu remains selected for backend lowering"; } - if (transformed) { + if (transformed && "optimized".equals(selectedStage)) { return "optimized IrGpu is selected for backend lowering after runtime optimizer passes"; } + if (transformed) { + return "optimized IrGpu candidate is materialized for review, but original IrGpu remains selected for backend lowering"; + } if (optimizedArtifact.isPresent()) { return "optimized IrGpu is a pass-through artifact and remains selected for backend lowering"; } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrTypedNodeGraph.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrTypedNodeGraph.java index d0cdc890..fb1a1921 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrTypedNodeGraph.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrTypedNodeGraph.java @@ -106,7 +106,7 @@ public boolean isCall(IrGpuTypedNode node, String name) { } public String callName(IrGpuTypedNode node) { - return attribute(node, "name", "function", "intrinsic"); + return attribute(node, "name", "function", "intrinsic", "backendName"); } public List callArguments(IrGpuTypedNode node) { diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackend.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackend.java index 9331adce..3e1c6ee7 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackend.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackend.java @@ -397,7 +397,9 @@ public final void invoke(GpuKernelInvocation invocation) { GpuBackendModuleArtifact originalModuleArtifact = runtimeCompileArtifactsConfigured() ? lowerBackendModule(compileRequest) : null; - GpuBackendModuleArtifact optimizedModuleArtifact = lowerBackendModule(optimizedCompileRequest); + GpuBackendModuleArtifact optimizedModuleArtifact = runtimeCompileArtifactsConfigured() + ? lowerOptimizedReviewModule(optimizedCompileRequest, optimizationResult.report()) + : lowerBackendModule(optimizedCompileRequest); GpuRuntimeEquivalenceEvidence runtimeEquivalenceEvidence = executeRuntimeEquivalence(new GpuRuntimeEquivalenceRequest( compileRequest, optimizedCompileRequest, @@ -1125,6 +1127,35 @@ protected GpuBackendModuleArtifact lowerBackendModule(GpuRuntimeCompileRequest c return lowerer.lower(compileRequest); } + private GpuBackendModuleArtifact lowerOptimizedReviewModule( + GpuRuntimeCompileRequest optimizedCompileRequest, + GpuRuntimeIrOptimizationReport optimizationReport + ) { + if (optimizationReport == null + || optimizationReport.candidateArtifact().isEmpty() + || sameIr(optimizationReport.artifact(), optimizationReport.candidateArtifact())) { + return lowerBackendModule(optimizedCompileRequest); + } + GpuBackendSourceReconstructionResult reconstruction = OpenClIrGpuSourceReconstructor.INSTANCE.reconstruct( + optimizedCompileRequest.irGpuArtifact().orElse(null), + optimizedCompileRequest.descriptor().kernelResource() + ); + if (!reconstruction.reconstructed() || !reconstruction.sourceAvailable()) { + return lowerBackendModule(optimizedCompileRequest); + } + return GpuBackendModuleArtifact.openClSource( + reconstruction.source(), + optimizedCompileRequest.descriptor().kernelResource() + "#irgpu-review-candidate", + OpenClBackendLowerer.VERSION, + "irgpu-review-candidate", + "opencl-irgpu-review-candidate" + ); + } + + private static boolean sameIr(Optional first, Optional second) { + return IrGpuArtifactIdentity.stableIdentity(first).equals(IrGpuArtifactIdentity.stableIdentity(second)); + } + protected GpuRuntimeCompileRequest optimizeRuntimeIr(GpuRuntimeCompileRequest compileRequest) { Optional optimizedArtifact = irOptimizerRegistry.optimize(new GpuRuntimeIrOptimizationRequest( compileRequest, @@ -1148,7 +1179,9 @@ protected GpuRuntimeIrOptimizationResult optimizeRuntimeIrWithReport(GpuRuntimeC selectOptimizationStrategy(compileRequest) ) ); - GpuRuntimeCompileRequest optimizedCompileRequest = compileRequest.withIrGpuArtifact(optimizationReport.artifact()); + GpuRuntimeCompileRequest optimizedCompileRequest = compileRequest.withIrGpuArtifact( + optimizationReport.artifactForOptimizedReview() + ); return new GpuRuntimeIrOptimizationResult(optimizedCompileRequest, optimizationReport); } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java index 4429e1dd..38de30a1 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java @@ -96,6 +96,30 @@ int totalApprovalTemplateNotApplicableCount() { return entries.stream().mapToInt(Entry::approvalTemplateNotApplicableCount).sum(); } + int totalApprovalTemplateRuntimeEquivalencePayloadRequiredCount() { + return entries.stream() + .mapToInt(Entry::approvalTemplateRuntimeEquivalencePayloadRequiredCount) + .sum(); + } + + int totalApprovalTemplateRuntimeEquivalencePayloadPresentCount() { + return entries.stream() + .mapToInt(Entry::approvalTemplateRuntimeEquivalencePayloadPresentCount) + .sum(); + } + + int totalApprovalTemplateRuntimeEquivalencePayloadPassedCount() { + return entries.stream() + .mapToInt(Entry::approvalTemplateRuntimeEquivalencePayloadPassedCount) + .sum(); + } + + int totalApprovalTemplateRuntimeEquivalencePayloadCompleteCount() { + return entries.stream() + .mapToInt(Entry::approvalTemplateRuntimeEquivalencePayloadCompleteCount) + .sum(); + } + int totalOptimizedArtifactCandidateCount() { return entries.stream().mapToInt(Entry::optimizedArtifactCandidateCount).sum(); } @@ -157,6 +181,52 @@ String optimizedArtifactCandidateSelectionFirstBlocker() { return totalOptimizedArtifactCandidateCount() > 0 ? "selection-gate-not-bound" : "no-candidates"; } + int totalBackendNeutralSourceMaterializationPassCount() { + return entries.stream().mapToInt(Entry::backendNeutralSourceMaterializationPassCount).sum(); + } + + int totalBackendNeutralSourceMaterializationCandidateCount() { + return entries.stream().mapToInt(Entry::backendNeutralSourceMaterializationCandidateCount).sum(); + } + + int totalBackendNeutralSourceMaterializationSourceReadyCount() { + return entries.stream().mapToInt(Entry::backendNeutralSourceMaterializationSourceReadyCount).sum(); + } + + int totalBackendNeutralSourceMaterializationSourceLength() { + return entries.stream().mapToInt(Entry::backendNeutralSourceMaterializationSourceLengthTotal).sum(); + } + + String backendNeutralSourceMaterializationStatus() { + if (totalBackendNeutralSourceMaterializationPassCount() <= 0) { + return "not-recorded"; + } + if (totalBackendNeutralSourceMaterializationCandidateCount() <= 0) { + return "no-candidates"; + } + if (totalBackendNeutralSourceMaterializationSourceReadyCount() + >= totalBackendNeutralSourceMaterializationCandidateCount()) { + return "review-ready"; + } + return totalBackendNeutralSourceMaterializationSourceReadyCount() > 0 ? "mixed" : "blocked"; + } + + String backendNeutralSourceMaterializationFirstBlocker() { + for (Entry entry : entries) { + if (entry.backendNeutralSourceMaterializationCandidateCount() > 0 + && !"none".equals(entry.backendNeutralSourceMaterializationFirstBlocker())) { + return entry.backendNeutralSourceMaterializationFirstBlocker(); + } + } + return switch (backendNeutralSourceMaterializationStatus()) { + case "not-recorded" -> "not-recorded"; + case "no-candidates" -> "backend-neutral-source-materialization-not-needed"; + case "mixed" -> "backend-neutral-source-partially-materialized"; + case "blocked" -> "backend-neutral-source-not-materialized"; + default -> "none"; + }; + } + int totalConstantFoldingPreviewPassCount() { return entries.stream().mapToInt(Entry::constantFoldingPreviewPassCount).sum(); } @@ -169,6 +239,554 @@ int totalConstantFoldingPreviewSkippedCount() { return entries.stream().mapToInt(Entry::constantFoldingPreviewSkippedCount).sum(); } + int totalConstantFoldingMaterializationPassCount() { + return entries.stream().mapToInt(Entry::constantFoldingMaterializationPassCount).sum(); + } + + int totalConstantFoldingMaterializationTransformedNodeCount() { + return entries.stream().mapToInt(Entry::constantFoldingMaterializationTransformedNodeCount).sum(); + } + + int totalConstantFoldingMaterializationLiteralRewriteCount() { + return entries.stream().mapToInt(Entry::constantFoldingMaterializationLiteralRewriteCount).sum(); + } + + int totalConstantFoldingMaterializationIdentityRewriteCount() { + return entries.stream().mapToInt(Entry::constantFoldingMaterializationIdentityRewriteCount).sum(); + } + + int totalConstantFoldingMaterializationFixedPointPassCount() { + return entries.stream().mapToInt(Entry::constantFoldingMaterializationFixedPointPassCount).sum(); + } + + int totalConstantFoldingMaterializationSkippedCount() { + return entries.stream().mapToInt(Entry::constantFoldingMaterializationSkippedCount).sum(); + } + + int totalConstantFoldingMaterializationRuntimeEquivalencePayloadPresentCount() { + return entries.stream() + .mapToInt(Entry::constantFoldingMaterializationRuntimeEquivalencePayloadPresentCount) + .sum(); + } + + int totalConstantFoldingMaterializationRuntimeEquivalencePassedCount() { + return entries.stream() + .mapToInt(Entry::constantFoldingMaterializationRuntimeEquivalencePassedCount) + .sum(); + } + + String constantFoldingMaterializationStatus() { + if (totalConstantFoldingMaterializationPassCount() <= 0) { + return "not-recorded"; + } + if (totalConstantFoldingMaterializationTransformedNodeCount() <= 0) { + return "no-candidates"; + } + if (totalConstantFoldingMaterializationRuntimeEquivalencePayloadPresentCount() <= 0) { + return "pending-runtime-equivalence"; + } + return totalConstantFoldingMaterializationRuntimeEquivalencePassedCount() + < totalConstantFoldingMaterializationRuntimeEquivalencePayloadPresentCount() + ? "runtime-equivalence-not-passed" + : "review-ready"; + } + + String constantFoldingMaterializationFirstBlocker() { + for (Entry entry : entries) { + if (entry.constantFoldingMaterializationTransformedNodeCount() > 0 + && !"none".equals(entry.constantFoldingMaterializationFirstBlocker())) { + return entry.constantFoldingMaterializationFirstBlocker(); + } + } + return switch (constantFoldingMaterializationStatus()) { + case "not-recorded" -> "not-recorded"; + case "no-candidates" -> "no-materialized-candidates"; + case "pending-runtime-equivalence" -> "runtime-equivalence-payload-not-recorded"; + case "runtime-equivalence-not-passed" -> "runtime-equivalence-not-passed"; + default -> "none"; + }; + } + + int totalSafeLocalCseMaterializationPassCount() { + return entries.stream().mapToInt(Entry::safeLocalCseMaterializationPassCount).sum(); + } + + int totalSafeLocalCseMaterializationLocalBindingCount() { + return entries.stream().mapToInt(Entry::safeLocalCseMaterializationLocalBindingCount).sum(); + } + + int totalSafeLocalCseMaterializationTransformedNodeCount() { + return entries.stream().mapToInt(Entry::safeLocalCseMaterializationTransformedNodeCount).sum(); + } + + int totalSafeLocalCseMaterializationBodyTextReplacementCount() { + return entries.stream().mapToInt(Entry::safeLocalCseMaterializationBodyTextReplacementCount).sum(); + } + + int totalSafeLocalCseMaterializationRuntimeEquivalencePayloadPresentCount() { + return entries.stream() + .mapToInt(Entry::safeLocalCseMaterializationRuntimeEquivalencePayloadPresentCount) + .sum(); + } + + int totalSafeLocalCseMaterializationRuntimeEquivalencePassedCount() { + return entries.stream() + .mapToInt(Entry::safeLocalCseMaterializationRuntimeEquivalencePassedCount) + .sum(); + } + + String safeLocalCseMaterializationStatus() { + if (totalSafeLocalCseMaterializationPassCount() <= 0) { + return "not-recorded"; + } + if (totalSafeLocalCseMaterializationTransformedNodeCount() <= 0) { + return "no-candidates"; + } + if (totalSafeLocalCseMaterializationRuntimeEquivalencePayloadPresentCount() <= 0) { + return "pending-runtime-equivalence"; + } + return totalSafeLocalCseMaterializationRuntimeEquivalencePassedCount() + < totalSafeLocalCseMaterializationRuntimeEquivalencePayloadPresentCount() + ? "runtime-equivalence-not-passed" + : "review-ready"; + } + + String safeLocalCseMaterializationFirstBlocker() { + for (Entry entry : entries) { + if (entry.safeLocalCseMaterializationTransformedNodeCount() > 0 + && !"none".equals(entry.safeLocalCseMaterializationFirstBlocker())) { + return entry.safeLocalCseMaterializationFirstBlocker(); + } + } + return switch (safeLocalCseMaterializationStatus()) { + case "not-recorded" -> "not-recorded"; + case "no-candidates" -> "no-materialized-candidates"; + case "pending-runtime-equivalence" -> "runtime-equivalence-payload-not-recorded"; + case "runtime-equivalence-not-passed" -> "runtime-equivalence-not-passed"; + default -> "none"; + }; + } + + int totalMadFmaMaterializationPassCount() { + return entries.stream().mapToInt(Entry::madFmaMaterializationPassCount).sum(); + } + + int totalMadFmaMaterializationCandidateCount() { + return entries.stream().mapToInt(Entry::madFmaMaterializationCandidateCount).sum(); + } + + int totalMadFmaMaterializationTransformedNodeCount() { + return entries.stream().mapToInt(Entry::madFmaMaterializationTransformedNodeCount).sum(); + } + + int totalMadFmaMaterializationBodyTextReplacementCount() { + return entries.stream().mapToInt(Entry::madFmaMaterializationBodyTextReplacementCount).sum(); + } + + int totalMadFmaMaterializationFixedPointPassCount() { + return entries.stream().mapToInt(Entry::madFmaMaterializationFixedPointPassCount).sum(); + } + + int totalMadFmaMaterializationSkippedCount() { + return entries.stream().mapToInt(Entry::madFmaMaterializationSkippedCount).sum(); + } + + int totalMadFmaMaterializationRuntimeEquivalencePayloadPresentCount() { + return entries.stream() + .mapToInt(Entry::madFmaMaterializationRuntimeEquivalencePayloadPresentCount) + .sum(); + } + + int totalMadFmaMaterializationRuntimeEquivalencePassedCount() { + return entries.stream() + .mapToInt(Entry::madFmaMaterializationRuntimeEquivalencePassedCount) + .sum(); + } + + boolean madFmaMaterializationFastMathAllowed() { + return entries.stream().anyMatch(Entry::madFmaMaterializationFastMathAllowed); + } + + String madFmaMaterializationStatus() { + if (totalMadFmaMaterializationPassCount() <= 0) { + return "not-recorded"; + } + if (entries.stream().anyMatch(entry -> "blocked".equals(entry.madFmaMaterializationStatus()))) { + return "blocked"; + } + if (totalMadFmaMaterializationTransformedNodeCount() <= 0 + && totalMadFmaMaterializationSkippedCount() > 0) { + return "blocked"; + } + if (totalMadFmaMaterializationTransformedNodeCount() <= 0) { + return "no-candidates"; + } + if (!madFmaMaterializationFastMathAllowed()) { + return "blocked"; + } + if (totalMadFmaMaterializationRuntimeEquivalencePayloadPresentCount() <= 0) { + return "pending-runtime-equivalence"; + } + return totalMadFmaMaterializationRuntimeEquivalencePassedCount() + < totalMadFmaMaterializationRuntimeEquivalencePayloadPresentCount() + ? "runtime-equivalence-not-passed" + : "review-ready"; + } + + String madFmaMaterializationFirstBlocker() { + for (Entry entry : entries) { + if ((entry.madFmaMaterializationTransformedNodeCount() > 0 + || entry.madFmaMaterializationSkippedCount() > 0) + && !"none".equals(entry.madFmaMaterializationFirstBlocker())) { + return entry.madFmaMaterializationFirstBlocker(); + } + } + return switch (madFmaMaterializationStatus()) { + case "not-recorded" -> "not-recorded"; + case "no-candidates" -> "no-materialized-candidates"; + case "blocked" -> "mad-fma-materialization-blocked"; + case "pending-runtime-equivalence" -> "runtime-equivalence-payload-not-recorded"; + case "runtime-equivalence-not-passed" -> "runtime-equivalence-not-passed"; + default -> "none"; + }; + } + + int totalClampMaterializationPassCount() { + return totalIntrinsicMaterializationInt("clampMaterialization", "pass.count"); + } + + int totalClampMaterializationCandidateCount() { + return totalIntrinsicMaterializationInt("clampMaterialization", "candidate.count"); + } + + int totalClampMaterializationTransformedNodeCount() { + return totalIntrinsicMaterializationInt("clampMaterialization", "transformedNode.count"); + } + + int totalClampMaterializationBodyTextReplacementCount() { + return totalIntrinsicMaterializationInt("clampMaterialization", "bodyTextReplacement.count"); + } + + int totalClampMaterializationRuntimeEquivalencePayloadPresentCount() { + return totalIntrinsicMaterializationInt("clampMaterialization", "runtimeEquivalencePayloadPresent.count"); + } + + int totalClampMaterializationRuntimeEquivalencePassedCount() { + return totalIntrinsicMaterializationInt("clampMaterialization", "runtimeEquivalencePassed.count"); + } + + String clampMaterializationStatus() { + return intrinsicMaterializationStatus("clampMaterialization"); + } + + String clampMaterializationFirstBlocker() { + return intrinsicMaterializationFirstBlocker("clampMaterialization", "clamp-materialization-blocked"); + } + + int totalStepMaterializationPassCount() { + return totalIntrinsicMaterializationInt("stepMaterialization", "pass.count"); + } + + int totalStepMaterializationCandidateCount() { + return totalIntrinsicMaterializationInt("stepMaterialization", "candidate.count"); + } + + int totalStepMaterializationTransformedNodeCount() { + return totalIntrinsicMaterializationInt("stepMaterialization", "transformedNode.count"); + } + + int totalStepMaterializationBodyTextReplacementCount() { + return totalIntrinsicMaterializationInt("stepMaterialization", "bodyTextReplacement.count"); + } + + int totalStepMaterializationDirectStepCount() { + return totalIntrinsicMaterializationInt("stepMaterialization", "directStep.count"); + } + + int totalStepMaterializationInvertedStepCount() { + return totalIntrinsicMaterializationInt("stepMaterialization", "invertedStep.count"); + } + + int totalStepMaterializationRuntimeEquivalencePayloadPresentCount() { + return totalIntrinsicMaterializationInt("stepMaterialization", "runtimeEquivalencePayloadPresent.count"); + } + + int totalStepMaterializationRuntimeEquivalencePassedCount() { + return totalIntrinsicMaterializationInt("stepMaterialization", "runtimeEquivalencePassed.count"); + } + + String stepMaterializationStatus() { + return intrinsicMaterializationStatus("stepMaterialization"); + } + + String stepMaterializationFirstBlocker() { + return intrinsicMaterializationFirstBlocker("stepMaterialization", "step-materialization-blocked"); + } + + int totalMixMaterializationPassCount() { + return totalIntrinsicMaterializationInt("mixMaterialization", "pass.count"); + } + + int totalMixMaterializationCandidateCount() { + return totalIntrinsicMaterializationInt("mixMaterialization", "candidate.count"); + } + + int totalMixMaterializationTransformedNodeCount() { + return totalIntrinsicMaterializationInt("mixMaterialization", "transformedNode.count"); + } + + int totalMixMaterializationBodyTextReplacementCount() { + return totalIntrinsicMaterializationInt("mixMaterialization", "bodyTextReplacement.count"); + } + + int totalMixMaterializationCanonicalMixCount() { + return totalIntrinsicMaterializationInt("mixMaterialization", "canonicalMix.count"); + } + + int totalMixMaterializationExpandedMixCount() { + return totalIntrinsicMaterializationInt("mixMaterialization", "expandedMix.count"); + } + + int totalMixMaterializationMadExpandedMixCount() { + return totalIntrinsicMaterializationInt("mixMaterialization", "madExpandedMix.count"); + } + + int totalMixMaterializationRuntimeEquivalencePayloadPresentCount() { + return totalIntrinsicMaterializationInt("mixMaterialization", "runtimeEquivalencePayloadPresent.count"); + } + + int totalMixMaterializationRuntimeEquivalencePassedCount() { + return totalIntrinsicMaterializationInt("mixMaterialization", "runtimeEquivalencePassed.count"); + } + + boolean mixMaterializationFastMathAllowed() { + return anyIntrinsicMaterializationBoolean("mixMaterialization", "fastMathAllowed"); + } + + boolean mixMaterializationFastMathRequired() { + return anyIntrinsicMaterializationBoolean("mixMaterialization", "fastMathRequired"); + } + + boolean mixMaterializationAlgebraicReassociationRequired() { + return anyIntrinsicMaterializationBoolean("mixMaterialization", "algebraicReassociationRequired"); + } + + String mixMaterializationStatus() { + return intrinsicMaterializationStatus("mixMaterialization"); + } + + String mixMaterializationFirstBlocker() { + return intrinsicMaterializationFirstBlocker("mixMaterialization", "mix-materialization-blocked"); + } + + private int totalIntrinsicMaterializationInt(String prefix, String suffix) { + return entries.stream() + .mapToInt(entry -> entry.intrinsicMaterializationInt(prefix, suffix)) + .sum(); + } + + private boolean anyIntrinsicMaterializationBoolean(String prefix, String suffix) { + return entries.stream().anyMatch(entry -> entry.intrinsicMaterializationBoolean(prefix, suffix)); + } + + private int totalIntrinsicMaterializationSkippedCount(String prefix) { + return entries.stream() + .mapToInt(entry -> entry.intrinsicMaterializationSkippedCount(prefix)) + .sum(); + } + + private String intrinsicMaterializationStatus(String prefix) { + if (totalIntrinsicMaterializationInt(prefix, "pass.count") <= 0) { + return "not-recorded"; + } + if (entries.stream().anyMatch(entry -> "blocked".equals(entry.intrinsicMaterializationStatus(prefix)))) { + return "blocked"; + } + if (totalIntrinsicMaterializationInt(prefix, "transformedNode.count") <= 0 + && totalIntrinsicMaterializationSkippedCount(prefix) > 0) { + return "blocked"; + } + if (totalIntrinsicMaterializationInt(prefix, "transformedNode.count") <= 0) { + return "no-candidates"; + } + if (totalIntrinsicMaterializationInt(prefix, "runtimeEquivalencePayloadPresent.count") <= 0) { + return "pending-runtime-equivalence"; + } + return totalIntrinsicMaterializationInt(prefix, "runtimeEquivalencePassed.count") + < totalIntrinsicMaterializationInt(prefix, "runtimeEquivalencePayloadPresent.count") + ? "runtime-equivalence-not-passed" + : "review-ready"; + } + + private String intrinsicMaterializationFirstBlocker(String prefix, String defaultBlockedFirstBlocker) { + for (Entry entry : entries) { + if ((entry.intrinsicMaterializationInt(prefix, "transformedNode.count") > 0 + || entry.intrinsicMaterializationSkippedCount(prefix) > 0) + && !"none".equals(entry.intrinsicMaterializationFirstBlocker(prefix))) { + return entry.intrinsicMaterializationFirstBlocker(prefix); + } + } + return switch (intrinsicMaterializationStatus(prefix)) { + case "not-recorded" -> "not-recorded"; + case "no-candidates" -> "no-materialized-candidates"; + case "blocked" -> defaultBlockedFirstBlocker; + case "pending-runtime-equivalence" -> "runtime-equivalence-payload-not-recorded"; + case "runtime-equivalence-not-passed" -> "runtime-equivalence-not-passed"; + default -> "none"; + }; + } + + int totalLoopVectorizationMaterializationPassCount() { + return entries.stream().mapToInt(Entry::loopVectorizationMaterializationPassCount).sum(); + } + + int totalLoopVectorizationMaterializationCandidateCount() { + return entries.stream().mapToInt(Entry::loopVectorizationMaterializationCandidateCount).sum(); + } + + int totalLoopVectorizationMaterializationTransformedLoopCount() { + return entries.stream().mapToInt(Entry::loopVectorizationMaterializationTransformedLoopCount).sum(); + } + + int totalLoopVectorizationMaterializationBodyTextReplacementCount() { + return entries.stream().mapToInt(Entry::loopVectorizationMaterializationBodyTextReplacementCount).sum(); + } + + int totalLoopVectorizationMaterializationTypedBodyMaterializedCount() { + return entries.stream().mapToInt(Entry::loopVectorizationMaterializationTypedBodyMaterializedCount).sum(); + } + + int totalLoopVectorizationMaterializationTypedBodyInvalidatedCount() { + return entries.stream().mapToInt(Entry::loopVectorizationMaterializationTypedBodyInvalidatedCount).sum(); + } + + int totalLoopVectorizationMaterializationSkippedCount() { + return entries.stream().mapToInt(Entry::loopVectorizationMaterializationSkippedCount).sum(); + } + + int totalLoopVectorizationMaterializationRuntimeEquivalencePayloadPresentCount() { + return entries.stream() + .mapToInt(Entry::loopVectorizationMaterializationRuntimeEquivalencePayloadPresentCount) + .sum(); + } + + int totalLoopVectorizationMaterializationRuntimeEquivalencePassedCount() { + return entries.stream() + .mapToInt(Entry::loopVectorizationMaterializationRuntimeEquivalencePassedCount) + .sum(); + } + + String loopVectorizationMaterializationStatus() { + if (totalLoopVectorizationMaterializationPassCount() <= 0) { + return "not-recorded"; + } + if (entries.stream().anyMatch(entry -> "blocked".equals(entry.loopVectorizationMaterializationStatus()))) { + return "blocked"; + } + if (totalLoopVectorizationMaterializationTransformedLoopCount() <= 0 + && totalLoopVectorizationMaterializationSkippedCount() > 0) { + return "blocked"; + } + if (totalLoopVectorizationMaterializationTransformedLoopCount() <= 0) { + return "no-candidates"; + } + if (totalLoopVectorizationMaterializationRuntimeEquivalencePayloadPresentCount() <= 0) { + return "pending-runtime-equivalence"; + } + return totalLoopVectorizationMaterializationRuntimeEquivalencePassedCount() + < totalLoopVectorizationMaterializationRuntimeEquivalencePayloadPresentCount() + ? "runtime-equivalence-not-passed" + : "review-ready"; + } + + String loopVectorizationMaterializationFirstBlocker() { + for (Entry entry : entries) { + if ((entry.loopVectorizationMaterializationTransformedLoopCount() > 0 + || entry.loopVectorizationMaterializationSkippedCount() > 0) + && !"none".equals(entry.loopVectorizationMaterializationFirstBlocker())) { + return entry.loopVectorizationMaterializationFirstBlocker(); + } + } + return switch (loopVectorizationMaterializationStatus()) { + case "not-recorded" -> "not-recorded"; + case "no-candidates" -> "no-materialized-candidates"; + case "blocked" -> "loop-vectorization-materialization-blocked"; + case "pending-runtime-equivalence" -> "runtime-equivalence-payload-not-recorded"; + case "runtime-equivalence-not-passed" -> "runtime-equivalence-not-passed"; + default -> "none"; + }; + } + + int totalTypedDeadCodeMaterializationPassCount() { + return entries.stream().mapToInt(Entry::typedDeadCodeMaterializationPassCount).sum(); + } + + int totalTypedDeadCodeMaterializationNodeCount() { + return entries.stream().mapToInt(Entry::typedDeadCodeMaterializationNodeCount).sum(); + } + + int totalTypedDeadCodeMaterializationUnreachableNodeCount() { + return entries.stream().mapToInt(Entry::typedDeadCodeMaterializationUnreachableNodeCount).sum(); + } + + int totalTypedDeadCodeMaterializationRemovedNodeCount() { + return entries.stream().mapToInt(Entry::typedDeadCodeMaterializationRemovedNodeCount).sum(); + } + + int totalTypedDeadCodeMaterializationBlockedCount() { + return entries.stream().mapToInt(Entry::typedDeadCodeMaterializationBlockedCount).sum(); + } + + int totalTypedDeadCodeMaterializationRuntimeEquivalencePayloadPresentCount() { + return entries.stream() + .mapToInt(Entry::typedDeadCodeMaterializationRuntimeEquivalencePayloadPresentCount) + .sum(); + } + + int totalTypedDeadCodeMaterializationRuntimeEquivalencePassedCount() { + return entries.stream() + .mapToInt(Entry::typedDeadCodeMaterializationRuntimeEquivalencePassedCount) + .sum(); + } + + String typedDeadCodeMaterializationStatus() { + if (totalTypedDeadCodeMaterializationPassCount() <= 0) { + return "not-recorded"; + } + if (totalTypedDeadCodeMaterializationRemovedNodeCount() <= 0 + && totalTypedDeadCodeMaterializationBlockedCount() > 0) { + return "blocked"; + } + if (totalTypedDeadCodeMaterializationRemovedNodeCount() <= 0) { + return "no-candidates"; + } + if (totalTypedDeadCodeMaterializationRuntimeEquivalencePayloadPresentCount() <= 0) { + return "pending-runtime-equivalence"; + } + return totalTypedDeadCodeMaterializationRuntimeEquivalencePassedCount() + < totalTypedDeadCodeMaterializationRuntimeEquivalencePayloadPresentCount() + ? "runtime-equivalence-not-passed" + : "review-ready"; + } + + String typedDeadCodeMaterializationFirstBlocker() { + for (Entry entry : entries) { + if ((entry.typedDeadCodeMaterializationRemovedNodeCount() > 0 + || entry.typedDeadCodeMaterializationBlockedCount() > 0) + && !"none".equals(entry.typedDeadCodeMaterializationFirstBlocker())) { + return entry.typedDeadCodeMaterializationFirstBlocker(); + } + } + return switch (typedDeadCodeMaterializationStatus()) { + case "not-recorded" -> "not-recorded"; + case "no-candidates" -> "no-materialized-candidates"; + case "blocked" -> "typed-dead-code-materialization-blocked"; + case "pending-runtime-equivalence" -> "runtime-equivalence-payload-not-recorded"; + case "runtime-equivalence-not-passed" -> "runtime-equivalence-not-passed"; + default -> "none"; + }; + } + int totalSafeLocalCsePreviewPassCount() { return entries.stream().mapToInt(Entry::safeLocalCsePreviewPassCount).sum(); } @@ -248,14 +866,92 @@ String runtimeEquivalenceReviewStatus() { } boolean runtimeEquivalenceReviewEligible() { - return "ready-for-runtime-equivalence-review".equals(previewReadinessStatus()); + boolean materializationReady = totalConstantFoldingMaterializationTransformedNodeCount() <= 0 + || "review-ready".equals(constantFoldingMaterializationStatus()); + boolean safeLocalCseMaterializationReady = totalSafeLocalCseMaterializationTransformedNodeCount() <= 0 + || "review-ready".equals(safeLocalCseMaterializationStatus()); + boolean madFmaMaterializationReady = totalMadFmaMaterializationTransformedNodeCount() <= 0 + || "review-ready".equals(madFmaMaterializationStatus()); + boolean clampMaterializationReady = totalClampMaterializationTransformedNodeCount() <= 0 + || "review-ready".equals(clampMaterializationStatus()); + boolean stepMaterializationReady = totalStepMaterializationTransformedNodeCount() <= 0 + || "review-ready".equals(stepMaterializationStatus()); + boolean mixMaterializationReady = totalMixMaterializationTransformedNodeCount() <= 0 + || "review-ready".equals(mixMaterializationStatus()); + boolean loopVectorizationMaterializationReady = totalLoopVectorizationMaterializationTransformedLoopCount() <= 0 + || "review-ready".equals(loopVectorizationMaterializationStatus()); + boolean typedMaterializationReady = totalTypedDeadCodeMaterializationRemovedNodeCount() <= 0 + || "review-ready".equals(typedDeadCodeMaterializationStatus()); + boolean previewReady = totalPreviewReadinessCandidateFamilyCount() <= 0 + || "ready-for-runtime-equivalence-review".equals(previewReadinessStatus()); + return runtimeEquivalenceReviewRequired() + && materializationReady + && safeLocalCseMaterializationReady + && madFmaMaterializationReady + && clampMaterializationReady + && stepMaterializationReady + && mixMaterializationReady + && loopVectorizationMaterializationReady + && typedMaterializationReady + && previewReady; } boolean runtimeEquivalenceReviewRequired() { - return totalPreviewReadinessCandidateFamilyCount() > 0; + return totalPreviewReadinessCandidateFamilyCount() > 0 + || totalConstantFoldingMaterializationTransformedNodeCount() > 0 + || totalSafeLocalCseMaterializationTransformedNodeCount() > 0 + || totalMadFmaMaterializationTransformedNodeCount() > 0 + || totalClampMaterializationTransformedNodeCount() > 0 + || totalStepMaterializationTransformedNodeCount() > 0 + || totalMixMaterializationTransformedNodeCount() > 0 + || totalLoopVectorizationMaterializationTransformedLoopCount() > 0 + || totalTypedDeadCodeMaterializationRemovedNodeCount() > 0; } String runtimeEquivalenceReviewFirstBlocker() { + if (totalConstantFoldingMaterializationTransformedNodeCount() > 0 + && !"review-ready".equals(constantFoldingMaterializationStatus())) { + return constantFoldingMaterializationFirstBlocker(); + } + if (totalSafeLocalCseMaterializationTransformedNodeCount() > 0 + && !"review-ready".equals(safeLocalCseMaterializationStatus())) { + return safeLocalCseMaterializationFirstBlocker(); + } + if (totalMadFmaMaterializationTransformedNodeCount() > 0 + && !"review-ready".equals(madFmaMaterializationStatus())) { + return madFmaMaterializationFirstBlocker(); + } + if (totalClampMaterializationTransformedNodeCount() > 0 + && !"review-ready".equals(clampMaterializationStatus())) { + return clampMaterializationFirstBlocker(); + } + if (totalStepMaterializationTransformedNodeCount() > 0 + && !"review-ready".equals(stepMaterializationStatus())) { + return stepMaterializationFirstBlocker(); + } + if (totalMixMaterializationTransformedNodeCount() > 0 + && !"review-ready".equals(mixMaterializationStatus())) { + return mixMaterializationFirstBlocker(); + } + if (totalLoopVectorizationMaterializationTransformedLoopCount() > 0 + && !"review-ready".equals(loopVectorizationMaterializationStatus())) { + return loopVectorizationMaterializationFirstBlocker(); + } + if (totalTypedDeadCodeMaterializationRemovedNodeCount() > 0 + && !"review-ready".equals(typedDeadCodeMaterializationStatus())) { + return typedDeadCodeMaterializationFirstBlocker(); + } + if (totalPreviewReadinessCandidateFamilyCount() <= 0 + && (totalConstantFoldingMaterializationTransformedNodeCount() > 0 + || totalSafeLocalCseMaterializationTransformedNodeCount() > 0 + || totalMadFmaMaterializationTransformedNodeCount() > 0 + || totalClampMaterializationTransformedNodeCount() > 0 + || totalStepMaterializationTransformedNodeCount() > 0 + || totalMixMaterializationTransformedNodeCount() > 0 + || totalLoopVectorizationMaterializationTransformedLoopCount() > 0 + || totalTypedDeadCodeMaterializationRemovedNodeCount() > 0)) { + return "none"; + } return switch (previewReadinessStatus()) { case "ready-for-runtime-equivalence-review" -> "none"; case "not-recorded" -> "preview-readiness-not-recorded"; @@ -282,6 +978,43 @@ int totalReviewPackagePendingApprovalCount() { return entries.stream().mapToInt(Entry::reviewPackagePendingApprovalCount).sum(); } + int totalReviewPackageApprovalManifestRequiredCount() { + return (int) entries.stream().filter(Entry::reviewPackageApprovalManifestRequired).count(); + } + + int totalReviewPackageApprovalManifestPresentCount() { + return entries.stream().mapToInt(Entry::reviewPackageApprovalManifestPresentCount).sum(); + } + + int totalReviewPackageApprovalManifestAcceptedCount() { + return entries.stream().mapToInt(Entry::reviewPackageApprovalManifestAcceptedCount).sum(); + } + + String reviewPackageApprovalManifestStatus() { + if (totalReviewPackageApprovalManifestRequiredCount() <= 0) { + return "not-required"; + } + if (totalReviewPackageApprovalManifestAcceptedCount() >= totalReviewPackageApprovalManifestRequiredCount()) { + return "accepted"; + } + return entries.stream() + .filter(Entry::reviewPackageApprovalManifestRequired) + .map(Entry::reviewPackageApprovalManifestStatus) + .filter(status -> !status.isBlank() && !"not-recorded".equals(status)) + .findFirst() + .orElse("pending-manifest-validation"); + } + + String reviewPackageApprovalManifestFirstBlocker() { + return entries.stream() + .filter(Entry::reviewPackageApprovalManifestRequired) + .filter(entry -> entry.reviewPackageApprovalManifestAcceptedCount() <= 0) + .map(Entry::reviewPackageApprovalManifestFirstBlocker) + .filter(blocker -> !blocker.isBlank() && !"none".equals(blocker)) + .findFirst() + .orElse("none"); + } + String reviewPackageStatus() { if (entries.isEmpty() || entries.stream().allMatch(entry -> "missing".equals(entry.status()))) { return "not-recorded"; @@ -403,6 +1136,14 @@ String toMarkdown() { markdown.append("- Rolled back count: `").append(totalRolledBackCount()).append("`\n"); markdown.append("- Approval templates pending: `").append(totalApprovalTemplatePendingCount()).append("`\n"); markdown.append("- Approval templates not applicable: `").append(totalApprovalTemplateNotApplicableCount()).append("`\n"); + markdown.append("- Approval templates runtime-equivalence payload required: `") + .append(totalApprovalTemplateRuntimeEquivalencePayloadRequiredCount()).append("`\n"); + markdown.append("- Approval templates runtime-equivalence payload present: `") + .append(totalApprovalTemplateRuntimeEquivalencePayloadPresentCount()).append("`\n"); + markdown.append("- Approval templates runtime-equivalence payload passed: `") + .append(totalApprovalTemplateRuntimeEquivalencePayloadPassedCount()).append("`\n"); + markdown.append("- Approval templates runtime-equivalence payload complete: `") + .append(totalApprovalTemplateRuntimeEquivalencePayloadCompleteCount()).append("`\n"); markdown.append("- Optimized artifact candidate status: `") .append(optimizedArtifactCandidateStatus()).append("`\n"); markdown.append("- Optimized artifact candidates: `") @@ -427,9 +1168,181 @@ String toMarkdown() { .append(totalOptimizedArtifactCandidateSelectionAppliedCount() > 0).append("`\n"); markdown.append("- Optimized artifact candidate selected IR replacement: `") .append(totalOptimizedArtifactCandidateSelectedIrReplacementCount() > 0).append("`\n"); + markdown.append("- Backend-neutral source materialization status: `") + .append(backendNeutralSourceMaterializationStatus()).append("`\n"); + markdown.append("- Backend-neutral source materialization passes: `") + .append(totalBackendNeutralSourceMaterializationPassCount()).append("`\n"); + markdown.append("- Backend-neutral source materialization candidates: `") + .append(totalBackendNeutralSourceMaterializationCandidateCount()).append("`\n"); + markdown.append("- Backend-neutral source materialization source-ready count: `") + .append(totalBackendNeutralSourceMaterializationSourceReadyCount()).append("`\n"); + markdown.append("- Backend-neutral source materialization source length total: `") + .append(totalBackendNeutralSourceMaterializationSourceLength()).append("`\n"); + markdown.append("- Backend-neutral source materialization first blocker: `") + .append(inline(backendNeutralSourceMaterializationFirstBlocker())).append("`\n"); markdown.append("- Constant folding preview passes: `").append(totalConstantFoldingPreviewPassCount()).append("`\n"); markdown.append("- Constant folding preview candidates: `").append(totalConstantFoldingPreviewCandidateCount()).append("`\n"); markdown.append("- Constant folding preview skipped blockers: `").append(totalConstantFoldingPreviewSkippedCount()).append("`\n"); + markdown.append("- Constant folding materialization status: `") + .append(constantFoldingMaterializationStatus()).append("`\n"); + markdown.append("- Constant folding materialization passes: `") + .append(totalConstantFoldingMaterializationPassCount()).append("`\n"); + markdown.append("- Constant folding materialized nodes: `") + .append(totalConstantFoldingMaterializationTransformedNodeCount()).append("`\n"); + markdown.append("- Constant folding materialization literal rewrites: `") + .append(totalConstantFoldingMaterializationLiteralRewriteCount()).append("`\n"); + markdown.append("- Constant folding materialization identity rewrites: `") + .append(totalConstantFoldingMaterializationIdentityRewriteCount()).append("`\n"); + markdown.append("- Constant folding materialization fixed-point passes: `") + .append(totalConstantFoldingMaterializationFixedPointPassCount()).append("`\n"); + markdown.append("- Constant folding materialization skipped blockers: `") + .append(totalConstantFoldingMaterializationSkippedCount()).append("`\n"); + markdown.append("- Constant folding materialization runtime-equivalence payloads: `") + .append(totalConstantFoldingMaterializationRuntimeEquivalencePayloadPresentCount()).append("`\n"); + markdown.append("- Constant folding materialization runtime-equivalence passed: `") + .append(totalConstantFoldingMaterializationRuntimeEquivalencePassedCount()).append("`\n"); + markdown.append("- Constant folding materialization first blocker: `") + .append(inline(constantFoldingMaterializationFirstBlocker())).append("`\n"); + markdown.append("- Safe local CSE materialization status: `") + .append(safeLocalCseMaterializationStatus()).append("`\n"); + markdown.append("- Safe local CSE materialization passes: `") + .append(totalSafeLocalCseMaterializationPassCount()).append("`\n"); + markdown.append("- Safe local CSE materialization local bindings: `") + .append(totalSafeLocalCseMaterializationLocalBindingCount()).append("`\n"); + markdown.append("- Safe local CSE materialized nodes: `") + .append(totalSafeLocalCseMaterializationTransformedNodeCount()).append("`\n"); + markdown.append("- Safe local CSE materialization text replacements: `") + .append(totalSafeLocalCseMaterializationBodyTextReplacementCount()).append("`\n"); + markdown.append("- Safe local CSE materialization runtime-equivalence payloads: `") + .append(totalSafeLocalCseMaterializationRuntimeEquivalencePayloadPresentCount()).append("`\n"); + markdown.append("- Safe local CSE materialization runtime-equivalence passed: `") + .append(totalSafeLocalCseMaterializationRuntimeEquivalencePassedCount()).append("`\n"); + markdown.append("- Safe local CSE materialization first blocker: `") + .append(inline(safeLocalCseMaterializationFirstBlocker())).append("`\n"); + markdown.append("- Mad/FMA materialization status: `") + .append(madFmaMaterializationStatus()).append("`\n"); + markdown.append("- Mad/FMA materialization passes: `") + .append(totalMadFmaMaterializationPassCount()).append("`\n"); + markdown.append("- Mad/FMA materialization candidates: `") + .append(totalMadFmaMaterializationCandidateCount()).append("`\n"); + markdown.append("- Mad/FMA materialized nodes: `") + .append(totalMadFmaMaterializationTransformedNodeCount()).append("`\n"); + markdown.append("- Mad/FMA materialization text replacements: `") + .append(totalMadFmaMaterializationBodyTextReplacementCount()).append("`\n"); + markdown.append("- Mad/FMA materialization fixed-point passes: `") + .append(totalMadFmaMaterializationFixedPointPassCount()).append("`\n"); + markdown.append("- Mad/FMA materialization skipped blockers: `") + .append(totalMadFmaMaterializationSkippedCount()).append("`\n"); + markdown.append("- Mad/FMA materialization runtime-equivalence payloads: `") + .append(totalMadFmaMaterializationRuntimeEquivalencePayloadPresentCount()).append("`\n"); + markdown.append("- Mad/FMA materialization runtime-equivalence passed: `") + .append(totalMadFmaMaterializationRuntimeEquivalencePassedCount()).append("`\n"); + markdown.append("- Mad/FMA materialization fast-math allowed: `") + .append(madFmaMaterializationFastMathAllowed()).append("`\n"); + markdown.append("- Mad/FMA materialization first blocker: `") + .append(inline(madFmaMaterializationFirstBlocker())).append("`\n"); + markdown.append("- Clamp materialization status: `") + .append(clampMaterializationStatus()).append("`\n"); + markdown.append("- Clamp materialization passes: `") + .append(totalClampMaterializationPassCount()).append("`\n"); + markdown.append("- Clamp materialization candidates: `") + .append(totalClampMaterializationCandidateCount()).append("`\n"); + markdown.append("- Clamp materialized nodes: `") + .append(totalClampMaterializationTransformedNodeCount()).append("`\n"); + markdown.append("- Clamp materialization text replacements: `") + .append(totalClampMaterializationBodyTextReplacementCount()).append("`\n"); + markdown.append("- Clamp materialization runtime-equivalence payloads: `") + .append(totalClampMaterializationRuntimeEquivalencePayloadPresentCount()).append("`\n"); + markdown.append("- Clamp materialization runtime-equivalence passed: `") + .append(totalClampMaterializationRuntimeEquivalencePassedCount()).append("`\n"); + markdown.append("- Clamp materialization first blocker: `") + .append(inline(clampMaterializationFirstBlocker())).append("`\n"); + markdown.append("- Step materialization status: `") + .append(stepMaterializationStatus()).append("`\n"); + markdown.append("- Step materialization passes: `") + .append(totalStepMaterializationPassCount()).append("`\n"); + markdown.append("- Step materialization candidates: `") + .append(totalStepMaterializationCandidateCount()).append("`\n"); + markdown.append("- Step materialized nodes: `") + .append(totalStepMaterializationTransformedNodeCount()).append("`\n"); + markdown.append("- Step materialization direct step count: `") + .append(totalStepMaterializationDirectStepCount()).append("`\n"); + markdown.append("- Step materialization inverted step count: `") + .append(totalStepMaterializationInvertedStepCount()).append("`\n"); + markdown.append("- Step materialization text replacements: `") + .append(totalStepMaterializationBodyTextReplacementCount()).append("`\n"); + markdown.append("- Step materialization runtime-equivalence payloads: `") + .append(totalStepMaterializationRuntimeEquivalencePayloadPresentCount()).append("`\n"); + markdown.append("- Step materialization runtime-equivalence passed: `") + .append(totalStepMaterializationRuntimeEquivalencePassedCount()).append("`\n"); + markdown.append("- Step materialization first blocker: `") + .append(inline(stepMaterializationFirstBlocker())).append("`\n"); + markdown.append("- Mix materialization status: `") + .append(mixMaterializationStatus()).append("`\n"); + markdown.append("- Mix materialization passes: `") + .append(totalMixMaterializationPassCount()).append("`\n"); + markdown.append("- Mix materialization candidates: `") + .append(totalMixMaterializationCandidateCount()).append("`\n"); + markdown.append("- Mix materialized nodes: `") + .append(totalMixMaterializationTransformedNodeCount()).append("`\n"); + markdown.append("- Mix materialization canonical count: `") + .append(totalMixMaterializationCanonicalMixCount()).append("`\n"); + markdown.append("- Mix materialization expanded count: `") + .append(totalMixMaterializationExpandedMixCount()).append("`\n"); + markdown.append("- Mix materialization MAD-expanded count: `") + .append(totalMixMaterializationMadExpandedMixCount()).append("`\n"); + markdown.append("- Mix materialization text replacements: `") + .append(totalMixMaterializationBodyTextReplacementCount()).append("`\n"); + markdown.append("- Mix materialization runtime-equivalence payloads: `") + .append(totalMixMaterializationRuntimeEquivalencePayloadPresentCount()).append("`\n"); + markdown.append("- Mix materialization runtime-equivalence passed: `") + .append(totalMixMaterializationRuntimeEquivalencePassedCount()).append("`\n"); + markdown.append("- Mix materialization fast-math allowed: `") + .append(mixMaterializationFastMathAllowed()).append("`\n"); + markdown.append("- Mix materialization fast-math required: `") + .append(mixMaterializationFastMathRequired()).append("`\n"); + markdown.append("- Mix materialization algebraic reassociation required: `") + .append(mixMaterializationAlgebraicReassociationRequired()).append("`\n"); + markdown.append("- Mix materialization first blocker: `") + .append(inline(mixMaterializationFirstBlocker())).append("`\n"); + markdown.append("- Loop vectorization materialization status: `") + .append(loopVectorizationMaterializationStatus()).append("`\n"); + markdown.append("- Loop vectorization materialization passes: `") + .append(totalLoopVectorizationMaterializationPassCount()).append("`\n"); + markdown.append("- Loop vectorization materialization candidates: `") + .append(totalLoopVectorizationMaterializationCandidateCount()).append("`\n"); + markdown.append("- Loop vectorization transformed loops: `") + .append(totalLoopVectorizationMaterializationTransformedLoopCount()).append("`\n"); + markdown.append("- Loop vectorization materialization text replacements: `") + .append(totalLoopVectorizationMaterializationBodyTextReplacementCount()).append("`\n"); + markdown.append("- Loop vectorization materialization typed bodies materialized: `") + .append(totalLoopVectorizationMaterializationTypedBodyMaterializedCount()).append("`\n"); + markdown.append("- Loop vectorization materialization typed bodies invalidated: `") + .append(totalLoopVectorizationMaterializationTypedBodyInvalidatedCount()).append("`\n"); + markdown.append("- Loop vectorization materialization skipped blockers: `") + .append(totalLoopVectorizationMaterializationSkippedCount()).append("`\n"); + markdown.append("- Loop vectorization materialization runtime-equivalence payloads: `") + .append(totalLoopVectorizationMaterializationRuntimeEquivalencePayloadPresentCount()).append("`\n"); + markdown.append("- Loop vectorization materialization runtime-equivalence passed: `") + .append(totalLoopVectorizationMaterializationRuntimeEquivalencePassedCount()).append("`\n"); + markdown.append("- Loop vectorization materialization first blocker: `") + .append(inline(loopVectorizationMaterializationFirstBlocker())).append("`\n"); + markdown.append("- Typed dead-code materialization status: `") + .append(typedDeadCodeMaterializationStatus()).append("`\n"); + markdown.append("- Typed dead-code materialization passes: `") + .append(totalTypedDeadCodeMaterializationPassCount()).append("`\n"); + markdown.append("- Typed dead-code materialization nodes: `") + .append(totalTypedDeadCodeMaterializationNodeCount()).append("`\n"); + markdown.append("- Typed dead-code materialization unreachable nodes: `") + .append(totalTypedDeadCodeMaterializationUnreachableNodeCount()).append("`\n"); + markdown.append("- Typed dead-code materialization removed nodes: `") + .append(totalTypedDeadCodeMaterializationRemovedNodeCount()).append("`\n"); + markdown.append("- Typed dead-code materialization runtime-equivalence payloads: `") + .append(totalTypedDeadCodeMaterializationRuntimeEquivalencePayloadPresentCount()).append("`\n"); + markdown.append("- Typed dead-code materialization runtime-equivalence passed: `") + .append(totalTypedDeadCodeMaterializationRuntimeEquivalencePassedCount()).append("`\n"); + markdown.append("- Typed dead-code materialization first blocker: `") + .append(inline(typedDeadCodeMaterializationFirstBlocker())).append("`\n"); markdown.append("- Safe local CSE preview passes: `").append(totalSafeLocalCsePreviewPassCount()).append("`\n"); markdown.append("- Safe local CSE preview candidate expressions: `") .append(totalSafeLocalCsePreviewCandidateExpressionCount()).append("`\n"); @@ -462,6 +1375,16 @@ String toMarkdown() { markdown.append("- Review package proposal passes: `").append(totalReviewPackageProposalPassCount()).append("`\n"); markdown.append("- Review package pending approvals: `") .append(totalReviewPackagePendingApprovalCount()).append("`\n"); + markdown.append("- Review package approval manifest status: `") + .append(reviewPackageApprovalManifestStatus()).append("`\n"); + markdown.append("- Review package approval manifests required: `") + .append(totalReviewPackageApprovalManifestRequiredCount()).append("`\n"); + markdown.append("- Review package approval manifests present: `") + .append(totalReviewPackageApprovalManifestPresentCount()).append("`\n"); + markdown.append("- Review package approval manifests accepted: `") + .append(totalReviewPackageApprovalManifestAcceptedCount()).append("`\n"); + markdown.append("- Review package approval manifest first blocker: `") + .append(inline(reviewPackageApprovalManifestFirstBlocker())).append("`\n"); markdown.append("- Review package first blocker: `") .append(inline(reviewPackageFirstBlocker())).append("`\n"); markdown.append("- Review package manual review only: `true`\n"); @@ -471,8 +1394,8 @@ String toMarkdown() { if (entries.isEmpty()) { return markdown.toString(); } - markdown.append("| Kernel resource | Status | Passes | Proposal-only | Selected | Rolled back | Approval pending | Approval N/A | Candidate | Candidate blocker | Selection blocker | CF candidates | CF skipped | CSE candidates | CSE duplicates | CSE blocked | TDC unreachable | TDC blocked | Review package | Review blocker | Providers |\n"); - markdown.append("| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | --- | --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- | --- | --- |\n"); + markdown.append("| Kernel resource | Status | Passes | Proposal-only | Selected | Rolled back | Approval pending | Approval N/A | Candidate | Candidate blocker | Selection blocker | CF candidates | CF skipped | CF materialized | CF materialization blocker | CSE candidates | CSE duplicates | CSE blocked | CSE materialized | CSE materialization blocker | TDC unreachable | TDC blocked | TDC materialized | TDC materialization blocker | Review package | Review blocker | Providers |\n"); + markdown.append("| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | --- | --- | --- | ---: | ---: | ---: | --- | ---: | ---: | ---: | ---: | --- | ---: | ---: | ---: | --- | --- | --- | --- |\n"); for (Entry entry : entries) { markdown.append("| `").append(table(entry.kernelResource())).append("` | `") .append(table(entry.status())).append("` | `") @@ -487,11 +1410,17 @@ String toMarkdown() { .append(table(entry.optimizedArtifactCandidateSelectionFirstBlocker())).append("` | `") .append(entry.constantFoldingPreviewCandidateCount()).append("` | `") .append(entry.constantFoldingPreviewSkippedCount()).append("` | `") + .append(entry.constantFoldingMaterializationTransformedNodeCount()).append("` | `") + .append(table(entry.constantFoldingMaterializationFirstBlocker())).append("` | `") .append(entry.safeLocalCsePreviewCandidateExpressionCount()).append("` | `") .append(entry.safeLocalCsePreviewDuplicateExpressionCount()).append("` | `") .append(entry.safeLocalCsePreviewBlockedCount()).append("` | `") + .append(entry.safeLocalCseMaterializationTransformedNodeCount()).append("` | `") + .append(table(entry.safeLocalCseMaterializationFirstBlocker())).append("` | `") .append(entry.typedDeadCodePreviewUnreachableNodeCount()).append("` | `") .append(entry.typedDeadCodePreviewBlockedCount()).append("` | `") + .append(entry.typedDeadCodeMaterializationRemovedNodeCount()).append("` | `") + .append(table(entry.typedDeadCodeMaterializationFirstBlocker())).append("` | `") .append(table(entry.reviewPackageStatus())).append("` | `") .append(table(entry.reviewPackageFirstBlocker())).append("` | `") .append(table(formatProviderCounts(entry.providerCounts()))).append("` |\n"); @@ -613,6 +1542,10 @@ record Entry( int rolledBackCount, int approvalTemplatePendingCount, int approvalTemplateNotApplicableCount, + int approvalTemplateRuntimeEquivalencePayloadRequiredCount, + int approvalTemplateRuntimeEquivalencePayloadPresentCount, + int approvalTemplateRuntimeEquivalencePayloadPassedCount, + int approvalTemplateRuntimeEquivalencePayloadCompleteCount, String optimizedArtifactCandidateStatus, int optimizedArtifactCandidateCount, int optimizedArtifactCandidateReadyCount, @@ -623,6 +1556,13 @@ record Entry( int optimizedArtifactCandidateMutationAllowedCount, String optimizedArtifactCandidateFirstBlocker, String optimizedArtifactCandidateSelectionFirstBlocker, + int backendNeutralSourceMaterializationPassCount, + int backendNeutralSourceMaterializationCandidateCount, + int backendNeutralSourceMaterializationSourceReadyCount, + int backendNeutralSourceMaterializationSourceLengthTotal, + int backendNeutralSourceMaterializationMaterializationOnlyCount, + String backendNeutralSourceMaterializationStatus, + String backendNeutralSourceMaterializationFirstBlocker, int constantFoldingPreviewPassCount, int constantFoldingPreviewCandidateCount, int constantFoldingPreviewSkippedNonPlainLiteralCount, @@ -634,6 +1574,77 @@ record Entry( boolean constantFoldingPreviewApprovalRequiredBeforeRewrite, boolean constantFoldingPreviewIntegerOverflowProven, boolean constantFoldingPreviewFloatingPointRoundingProven, + int constantFoldingMaterializationPassCount, + int constantFoldingMaterializationCandidateCount, + int constantFoldingMaterializationTransformedNodeCount, + int constantFoldingMaterializationLiteralRewriteCount, + int constantFoldingMaterializationIdentityRewriteCount, + int constantFoldingMaterializationFixedPointPassCount, + int constantFoldingMaterializationChangedMethodBodyCount, + int constantFoldingMaterializationBodyTextReplacementCount, + int constantFoldingMaterializationSkippedDivideByZeroCount, + int constantFoldingMaterializationSkippedNonEvenDivisionCount, + boolean constantFoldingMaterializationRuntimeEquivalenceRequiredBeforeSelection, + boolean constantFoldingMaterializationRuntimeEquivalencePayloadRequired, + int constantFoldingMaterializationRuntimeEquivalencePayloadPresentCount, + int constantFoldingMaterializationRuntimeEquivalencePassedCount, + boolean constantFoldingMaterializationApprovalRequiredBeforeProduction, + String constantFoldingMaterializationStatus, + String constantFoldingMaterializationFirstBlocker, + int safeLocalCseMaterializationPassCount, + int safeLocalCseMaterializationLocalBindingCount, + int safeLocalCseMaterializationTransformedNodeCount, + int safeLocalCseMaterializationBodyTextReplacementCount, + int safeLocalCseMaterializationRuntimeEquivalencePayloadPresentCount, + int safeLocalCseMaterializationRuntimeEquivalencePassedCount, + String safeLocalCseMaterializationStatus, + String safeLocalCseMaterializationFirstBlocker, + int madFmaMaterializationPassCount, + int madFmaMaterializationCandidateCount, + int madFmaMaterializationTransformedNodeCount, + int madFmaMaterializationChangedMethodBodyCount, + int madFmaMaterializationBodyTextReplacementCount, + int madFmaMaterializationFixedPointPassCount, + int madFmaMaterializationSkippedFastMathPolicyCount, + int madFmaMaterializationSkippedBodyTextPatternMissingCount, + boolean madFmaMaterializationRuntimeEquivalenceRequiredBeforeSelection, + boolean madFmaMaterializationRuntimeEquivalencePayloadRequired, + int madFmaMaterializationRuntimeEquivalencePayloadPresentCount, + int madFmaMaterializationRuntimeEquivalencePassedCount, + boolean madFmaMaterializationApprovalRequiredBeforeProduction, + boolean madFmaMaterializationFastMathAllowed, + String madFmaMaterializationStatus, + String madFmaMaterializationFirstBlocker, + int loopVectorizationMaterializationPassCount, + int loopVectorizationMaterializationCandidateCount, + int loopVectorizationMaterializationTransformedLoopCount, + int loopVectorizationMaterializationChangedMethodBodyCount, + int loopVectorizationMaterializationBodyTextReplacementCount, + int loopVectorizationMaterializationTypedBodyMaterializedCount, + int loopVectorizationMaterializationTypedBodyInvalidatedCount, + int loopVectorizationMaterializationSkippedLoopShapeCount, + int loopVectorizationMaterializationSkippedUnsupportedWidthCount, + int loopVectorizationMaterializationSkippedUnsafeLoadPatternCount, + int loopVectorizationMaterializationRuntimeEquivalencePayloadPresentCount, + int loopVectorizationMaterializationRuntimeEquivalencePassedCount, + String loopVectorizationMaterializationStatus, + String loopVectorizationMaterializationFirstBlocker, + int typedDeadCodeMaterializationPassCount, + int typedDeadCodeMaterializationNodeCount, + int typedDeadCodeMaterializationUnreachableNodeCount, + int typedDeadCodeMaterializationRemovedNodeCount, + int typedDeadCodeMaterializationChangedMethodBodyCount, + int typedDeadCodeMaterializationBlockedMissingRootCount, + int typedDeadCodeMaterializationBlockedMissingChildReferenceCount, + int typedDeadCodeMaterializationBlockedSideEffectingUnreachableNodeCount, + boolean typedDeadCodeMaterializationRuntimeEquivalenceRequiredBeforeSelection, + boolean typedDeadCodeMaterializationRuntimeEquivalencePayloadRequired, + int typedDeadCodeMaterializationRuntimeEquivalencePayloadPresentCount, + int typedDeadCodeMaterializationRuntimeEquivalencePassedCount, + boolean typedDeadCodeMaterializationApprovalRequiredBeforeProduction, + boolean typedDeadCodeMaterializationSideEffectFreedomProven, + String typedDeadCodeMaterializationStatus, + String typedDeadCodeMaterializationFirstBlocker, int safeLocalCsePreviewPassCount, int safeLocalCsePreviewExpressionCount, int safeLocalCsePreviewCandidateExpressionCount, @@ -663,8 +1674,15 @@ record Entry( int reviewPackageProposalPassCount, int reviewPackagePendingApprovalCount, String reviewPackageRuntimeEquivalenceStatus, + String reviewPackageApprovalManifestStatus, + boolean reviewPackageApprovalManifestRequired, + int reviewPackageApprovalManifestPresentCount, + int reviewPackageApprovalManifestAcceptedCount, + String reviewPackageApprovalManifestResourcePathSummary, + String reviewPackageApprovalManifestFirstBlocker, boolean reviewPackageManualReviewOnly, - Map providerCounts + Map providerCounts, + Map evidenceProperties ) { Entry { @@ -676,6 +1694,22 @@ record Entry( rolledBackCount = Math.max(0, rolledBackCount); approvalTemplatePendingCount = Math.max(0, approvalTemplatePendingCount); approvalTemplateNotApplicableCount = Math.max(0, approvalTemplateNotApplicableCount); + approvalTemplateRuntimeEquivalencePayloadRequiredCount = Math.max( + 0, + approvalTemplateRuntimeEquivalencePayloadRequiredCount + ); + approvalTemplateRuntimeEquivalencePayloadPresentCount = Math.max( + 0, + approvalTemplateRuntimeEquivalencePayloadPresentCount + ); + approvalTemplateRuntimeEquivalencePayloadPassedCount = Math.max( + 0, + approvalTemplateRuntimeEquivalencePayloadPassedCount + ); + approvalTemplateRuntimeEquivalencePayloadCompleteCount = Math.max( + 0, + approvalTemplateRuntimeEquivalencePayloadCompleteCount + ); optimizedArtifactCandidateStatus = normalize(optimizedArtifactCandidateStatus, "not-recorded"); optimizedArtifactCandidateCount = Math.max(0, optimizedArtifactCandidateCount); optimizedArtifactCandidateReadyCount = Math.max(0, optimizedArtifactCandidateReadyCount); @@ -689,6 +1723,34 @@ record Entry( optimizedArtifactCandidateSelectionFirstBlocker, "no-candidates" ); + backendNeutralSourceMaterializationPassCount = Math.max( + 0, + backendNeutralSourceMaterializationPassCount + ); + backendNeutralSourceMaterializationCandidateCount = Math.max( + 0, + backendNeutralSourceMaterializationCandidateCount + ); + backendNeutralSourceMaterializationSourceReadyCount = Math.max( + 0, + backendNeutralSourceMaterializationSourceReadyCount + ); + backendNeutralSourceMaterializationSourceLengthTotal = Math.max( + 0, + backendNeutralSourceMaterializationSourceLengthTotal + ); + backendNeutralSourceMaterializationMaterializationOnlyCount = Math.max( + 0, + backendNeutralSourceMaterializationMaterializationOnlyCount + ); + backendNeutralSourceMaterializationStatus = normalize( + backendNeutralSourceMaterializationStatus, + "not-recorded" + ); + backendNeutralSourceMaterializationFirstBlocker = normalize( + backendNeutralSourceMaterializationFirstBlocker, + "not-recorded" + ); constantFoldingPreviewPassCount = Math.max(0, constantFoldingPreviewPassCount); constantFoldingPreviewCandidateCount = Math.max(0, constantFoldingPreviewCandidateCount); constantFoldingPreviewSkippedNonPlainLiteralCount = Math.max(0, constantFoldingPreviewSkippedNonPlainLiteralCount); @@ -696,6 +1758,207 @@ record Entry( constantFoldingPreviewSkippedNonEvenDivisionCount = Math.max(0, constantFoldingPreviewSkippedNonEvenDivisionCount); constantFoldingPreviewSkippedUnsupportedOperatorCount = Math.max(0, constantFoldingPreviewSkippedUnsupportedOperatorCount); constantFoldingPreviewSkippedNonLiteralOperandCount = Math.max(0, constantFoldingPreviewSkippedNonLiteralOperandCount); + constantFoldingMaterializationPassCount = Math.max(0, constantFoldingMaterializationPassCount); + constantFoldingMaterializationCandidateCount = Math.max(0, constantFoldingMaterializationCandidateCount); + constantFoldingMaterializationTransformedNodeCount = Math.max(0, constantFoldingMaterializationTransformedNodeCount); + constantFoldingMaterializationLiteralRewriteCount = Math.max( + 0, + constantFoldingMaterializationLiteralRewriteCount + ); + constantFoldingMaterializationIdentityRewriteCount = Math.max( + 0, + constantFoldingMaterializationIdentityRewriteCount + ); + constantFoldingMaterializationFixedPointPassCount = Math.max( + 0, + constantFoldingMaterializationFixedPointPassCount + ); + constantFoldingMaterializationChangedMethodBodyCount = Math.max(0, constantFoldingMaterializationChangedMethodBodyCount); + constantFoldingMaterializationBodyTextReplacementCount = Math.max(0, constantFoldingMaterializationBodyTextReplacementCount); + constantFoldingMaterializationSkippedDivideByZeroCount = Math.max( + 0, + constantFoldingMaterializationSkippedDivideByZeroCount + ); + constantFoldingMaterializationSkippedNonEvenDivisionCount = Math.max( + 0, + constantFoldingMaterializationSkippedNonEvenDivisionCount + ); + constantFoldingMaterializationRuntimeEquivalencePayloadPresentCount = Math.max( + 0, + constantFoldingMaterializationRuntimeEquivalencePayloadPresentCount + ); + constantFoldingMaterializationRuntimeEquivalencePassedCount = Math.max( + 0, + constantFoldingMaterializationRuntimeEquivalencePassedCount + ); + constantFoldingMaterializationStatus = normalize( + constantFoldingMaterializationStatus, + "not-recorded" + ); + constantFoldingMaterializationFirstBlocker = normalize( + constantFoldingMaterializationFirstBlocker, + "not-recorded" + ); + safeLocalCseMaterializationPassCount = Math.max(0, safeLocalCseMaterializationPassCount); + safeLocalCseMaterializationLocalBindingCount = Math.max( + 0, + safeLocalCseMaterializationLocalBindingCount + ); + safeLocalCseMaterializationTransformedNodeCount = Math.max( + 0, + safeLocalCseMaterializationTransformedNodeCount + ); + safeLocalCseMaterializationBodyTextReplacementCount = Math.max( + 0, + safeLocalCseMaterializationBodyTextReplacementCount + ); + safeLocalCseMaterializationRuntimeEquivalencePayloadPresentCount = Math.max( + 0, + safeLocalCseMaterializationRuntimeEquivalencePayloadPresentCount + ); + safeLocalCseMaterializationRuntimeEquivalencePassedCount = Math.max( + 0, + safeLocalCseMaterializationRuntimeEquivalencePassedCount + ); + safeLocalCseMaterializationStatus = normalize( + safeLocalCseMaterializationStatus, + "not-recorded" + ); + safeLocalCseMaterializationFirstBlocker = normalize( + safeLocalCseMaterializationFirstBlocker, + "not-recorded" + ); + madFmaMaterializationPassCount = Math.max(0, madFmaMaterializationPassCount); + madFmaMaterializationCandidateCount = Math.max(0, madFmaMaterializationCandidateCount); + madFmaMaterializationTransformedNodeCount = Math.max( + 0, + madFmaMaterializationTransformedNodeCount + ); + madFmaMaterializationChangedMethodBodyCount = Math.max( + 0, + madFmaMaterializationChangedMethodBodyCount + ); + madFmaMaterializationBodyTextReplacementCount = Math.max( + 0, + madFmaMaterializationBodyTextReplacementCount + ); + madFmaMaterializationFixedPointPassCount = Math.max(0, madFmaMaterializationFixedPointPassCount); + madFmaMaterializationSkippedFastMathPolicyCount = Math.max( + 0, + madFmaMaterializationSkippedFastMathPolicyCount + ); + madFmaMaterializationSkippedBodyTextPatternMissingCount = Math.max( + 0, + madFmaMaterializationSkippedBodyTextPatternMissingCount + ); + madFmaMaterializationRuntimeEquivalencePayloadPresentCount = Math.max( + 0, + madFmaMaterializationRuntimeEquivalencePayloadPresentCount + ); + madFmaMaterializationRuntimeEquivalencePassedCount = Math.max( + 0, + madFmaMaterializationRuntimeEquivalencePassedCount + ); + madFmaMaterializationStatus = normalize( + madFmaMaterializationStatus, + "not-recorded" + ); + madFmaMaterializationFirstBlocker = normalize( + madFmaMaterializationFirstBlocker, + "not-recorded" + ); + loopVectorizationMaterializationPassCount = Math.max(0, loopVectorizationMaterializationPassCount); + loopVectorizationMaterializationCandidateCount = Math.max(0, loopVectorizationMaterializationCandidateCount); + loopVectorizationMaterializationTransformedLoopCount = Math.max( + 0, + loopVectorizationMaterializationTransformedLoopCount + ); + loopVectorizationMaterializationChangedMethodBodyCount = Math.max( + 0, + loopVectorizationMaterializationChangedMethodBodyCount + ); + loopVectorizationMaterializationBodyTextReplacementCount = Math.max( + 0, + loopVectorizationMaterializationBodyTextReplacementCount + ); + loopVectorizationMaterializationTypedBodyMaterializedCount = Math.max( + 0, + loopVectorizationMaterializationTypedBodyMaterializedCount + ); + loopVectorizationMaterializationTypedBodyInvalidatedCount = Math.max( + 0, + loopVectorizationMaterializationTypedBodyInvalidatedCount + ); + loopVectorizationMaterializationSkippedLoopShapeCount = Math.max( + 0, + loopVectorizationMaterializationSkippedLoopShapeCount + ); + loopVectorizationMaterializationSkippedUnsupportedWidthCount = Math.max( + 0, + loopVectorizationMaterializationSkippedUnsupportedWidthCount + ); + loopVectorizationMaterializationSkippedUnsafeLoadPatternCount = Math.max( + 0, + loopVectorizationMaterializationSkippedUnsafeLoadPatternCount + ); + loopVectorizationMaterializationRuntimeEquivalencePayloadPresentCount = Math.max( + 0, + loopVectorizationMaterializationRuntimeEquivalencePayloadPresentCount + ); + loopVectorizationMaterializationRuntimeEquivalencePassedCount = Math.max( + 0, + loopVectorizationMaterializationRuntimeEquivalencePassedCount + ); + loopVectorizationMaterializationStatus = normalize( + loopVectorizationMaterializationStatus, + "not-recorded" + ); + loopVectorizationMaterializationFirstBlocker = normalize( + loopVectorizationMaterializationFirstBlocker, + "not-recorded" + ); + typedDeadCodeMaterializationPassCount = Math.max(0, typedDeadCodeMaterializationPassCount); + typedDeadCodeMaterializationNodeCount = Math.max(0, typedDeadCodeMaterializationNodeCount); + typedDeadCodeMaterializationUnreachableNodeCount = Math.max( + 0, + typedDeadCodeMaterializationUnreachableNodeCount + ); + typedDeadCodeMaterializationRemovedNodeCount = Math.max( + 0, + typedDeadCodeMaterializationRemovedNodeCount + ); + typedDeadCodeMaterializationChangedMethodBodyCount = Math.max( + 0, + typedDeadCodeMaterializationChangedMethodBodyCount + ); + typedDeadCodeMaterializationBlockedMissingRootCount = Math.max( + 0, + typedDeadCodeMaterializationBlockedMissingRootCount + ); + typedDeadCodeMaterializationBlockedMissingChildReferenceCount = Math.max( + 0, + typedDeadCodeMaterializationBlockedMissingChildReferenceCount + ); + typedDeadCodeMaterializationBlockedSideEffectingUnreachableNodeCount = Math.max( + 0, + typedDeadCodeMaterializationBlockedSideEffectingUnreachableNodeCount + ); + typedDeadCodeMaterializationRuntimeEquivalencePayloadPresentCount = Math.max( + 0, + typedDeadCodeMaterializationRuntimeEquivalencePayloadPresentCount + ); + typedDeadCodeMaterializationRuntimeEquivalencePassedCount = Math.max( + 0, + typedDeadCodeMaterializationRuntimeEquivalencePassedCount + ); + typedDeadCodeMaterializationStatus = normalize( + typedDeadCodeMaterializationStatus, + "not-recorded" + ); + typedDeadCodeMaterializationFirstBlocker = normalize( + typedDeadCodeMaterializationFirstBlocker, + "not-recorded" + ); safeLocalCsePreviewPassCount = Math.max(0, safeLocalCsePreviewPassCount); safeLocalCsePreviewExpressionCount = Math.max(0, safeLocalCsePreviewExpressionCount); safeLocalCsePreviewCandidateExpressionCount = Math.max(0, safeLocalCsePreviewCandidateExpressionCount); @@ -716,7 +1979,19 @@ record Entry( reviewPackageProposalPassCount = Math.max(0, reviewPackageProposalPassCount); reviewPackagePendingApprovalCount = Math.max(0, reviewPackagePendingApprovalCount); reviewPackageRuntimeEquivalenceStatus = normalize(reviewPackageRuntimeEquivalenceStatus, "unknown"); + reviewPackageApprovalManifestStatus = normalize(reviewPackageApprovalManifestStatus, "not-recorded"); + reviewPackageApprovalManifestPresentCount = Math.max(0, reviewPackageApprovalManifestPresentCount); + reviewPackageApprovalManifestAcceptedCount = Math.max(0, reviewPackageApprovalManifestAcceptedCount); + reviewPackageApprovalManifestResourcePathSummary = normalize( + reviewPackageApprovalManifestResourcePathSummary, + "none" + ); + reviewPackageApprovalManifestFirstBlocker = normalize( + reviewPackageApprovalManifestFirstBlocker, + "none" + ); providerCounts = normalizeProviderCounts(providerCounts); + evidenceProperties = normalizeEvidenceProperties(evidenceProperties); } int constantFoldingPreviewSkippedCount() { @@ -727,18 +2002,65 @@ int constantFoldingPreviewSkippedCount() { + constantFoldingPreviewSkippedNonLiteralOperandCount; } + int constantFoldingMaterializationSkippedCount() { + return constantFoldingMaterializationSkippedDivideByZeroCount + + constantFoldingMaterializationSkippedNonEvenDivisionCount; + } + int safeLocalCsePreviewBlockedCount() { return safeLocalCsePreviewBlockedUnsupportedOperatorCount + safeLocalCsePreviewBlockedImpureOperandCount + safeLocalCsePreviewBlockedControlFlowBoundaryCount; } + int madFmaMaterializationSkippedCount() { + return madFmaMaterializationSkippedFastMathPolicyCount + + madFmaMaterializationSkippedBodyTextPatternMissingCount; + } + + int intrinsicMaterializationInt(String prefix, String suffix) { + return parseInt(evidenceProperties.get(prefix + "." + suffix), 0); + } + + boolean intrinsicMaterializationBoolean(String prefix, String suffix) { + return parseBoolean(evidenceProperties.get(prefix + "." + suffix)); + } + + String intrinsicMaterializationStatus(String prefix) { + return evidenceProperties.getOrDefault(prefix + ".status", "not-recorded"); + } + + String intrinsicMaterializationFirstBlocker(String prefix) { + return evidenceProperties.getOrDefault(prefix + ".firstBlocker", "not-recorded"); + } + + int intrinsicMaterializationSkippedCount(String prefix) { + return intrinsicMaterializationInt(prefix, "skipped.typedBodyMissing.count") + + intrinsicMaterializationInt(prefix, "skipped.unsupportedFormat.count") + + intrinsicMaterializationInt(prefix, "skipped.fastMathPolicy.count") + + intrinsicMaterializationInt(prefix, "skipped.missingChildReference.count") + + intrinsicMaterializationInt(prefix, "skipped.unsupportedShape.count") + + intrinsicMaterializationInt(prefix, "skipped.bodyTextPatternMissing.count"); + } + + int loopVectorizationMaterializationSkippedCount() { + return loopVectorizationMaterializationSkippedLoopShapeCount + + loopVectorizationMaterializationSkippedUnsupportedWidthCount + + loopVectorizationMaterializationSkippedUnsafeLoadPatternCount; + } + int typedDeadCodePreviewBlockedCount() { return typedDeadCodePreviewBlockedMissingRootCount + typedDeadCodePreviewBlockedMissingChildReferenceCount + typedDeadCodePreviewBlockedSideEffectingUnreachableNodeCount; } + int typedDeadCodeMaterializationBlockedCount() { + return typedDeadCodeMaterializationBlockedMissingRootCount + + typedDeadCodeMaterializationBlockedMissingChildReferenceCount + + typedDeadCodeMaterializationBlockedSideEffectingUnreachableNodeCount; + } + int constantFoldingPreviewProofBlockerCount() { if (constantFoldingPreviewCandidateCount <= 0) { return 0; @@ -776,13 +2098,24 @@ int typedDeadCodePreviewProofBlockerCount() { static Entry from(String kernelResource, Properties properties) { if (properties == null || properties.isEmpty()) { - return new Entry(kernelResource, "missing", 0, 0, 0, 0, 0, 0, + return new Entry(kernelResource, "missing", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "not-recorded", 0, 0, 0, 0, 0, 0, 0, "no-candidates", "no-candidates", + 0, 0, 0, 0, 0, "not-recorded", "not-recorded", 0, 0, 0, 0, 0, 0, 0, - false, false, false, false, 0, 0, 0, 0, 0, 0, 0, 0, false, false, false, false, + false, false, false, false, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false, false, 0, 0, false, + "not-recorded", "not-recorded", + 0, 0, 0, 0, 0, 0, "not-recorded", "not-recorded", + 0, 0, 0, 0, 0, 0, 0, 0, false, false, 0, 0, false, false, + "not-recorded", "not-recorded", + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "not-recorded", "not-recorded", + 0, 0, 0, 0, 0, 0, 0, 0, false, false, 0, 0, false, false, + "not-recorded", "not-recorded", + 0, 0, 0, 0, 0, 0, 0, 0, false, false, false, false, 0, 0, 0, 0, 0, 0, 0, false, false, false, - "not-recorded", false, false, "review-package-not-recorded", 0, 0, "unknown", false, - Map.of()); + "not-recorded", false, false, "review-package-not-recorded", 0, 0, "unknown", + "not-recorded", false, 0, 0, "none", "approval-manifest-not-recorded", false, + Map.of(), Map.of()); } CandidateEvidence optimizedArtifactCandidate = optimizedArtifactCandidateEvidence(properties); return new Entry( @@ -794,6 +2127,10 @@ static Entry from(String kernelResource, Properties properties) { parseInt(properties.getProperty("rolledBack.count"), 0), parseInt(properties.getProperty("approvalTemplate.pending.count"), 0), parseInt(properties.getProperty("approvalTemplate.notApplicable.count"), 0), + parseInt(properties.getProperty("approvalTemplate.runtimeEquivalencePayloadRequired.count"), 0), + parseInt(properties.getProperty("approvalTemplate.runtimeEquivalencePayloadPresent.count"), 0), + parseInt(properties.getProperty("approvalTemplate.runtimeEquivalencePayloadPassed.count"), 0), + parseInt(properties.getProperty("approvalTemplate.runtimeEquivalencePayloadComplete.count"), 0), optimizedArtifactCandidate.status(), optimizedArtifactCandidate.count(), optimizedArtifactCandidate.readyCount(), @@ -804,6 +2141,13 @@ static Entry from(String kernelResource, Properties properties) { optimizedArtifactCandidate.mutationAllowedCount(), optimizedArtifactCandidate.firstBlocker(), optimizedArtifactCandidate.selectionFirstBlocker(), + parseInt(properties.getProperty("backendNeutralSourceMaterialization.pass.count"), 0), + parseInt(properties.getProperty("backendNeutralSourceMaterialization.candidate.count"), 0), + parseInt(properties.getProperty("backendNeutralSourceMaterialization.sourceReady.count"), 0), + parseInt(properties.getProperty("backendNeutralSourceMaterialization.sourceLength.total"), 0), + parseInt(properties.getProperty("backendNeutralSourceMaterialization.materializationOnly.count"), 0), + properties.getProperty("backendNeutralSourceMaterialization.status", "not-recorded"), + properties.getProperty("backendNeutralSourceMaterialization.firstBlocker", "not-recorded"), parseInt(properties.getProperty("constantFoldingPreview.pass.count"), 0), parseInt(properties.getProperty("constantFoldingPreview.candidate.count"), 0), parseInt(properties.getProperty("constantFoldingPreview.skipped.nonPlainLiteral.count"), 0), @@ -815,6 +2159,121 @@ static Entry from(String kernelResource, Properties properties) { parseBoolean(properties.getProperty("constantFoldingPreview.approvalRequiredBeforeRewrite")), parseBoolean(properties.getProperty("constantFoldingPreview.integerOverflowProven")), parseBoolean(properties.getProperty("constantFoldingPreview.floatingPointRoundingProven")), + parseInt(properties.getProperty("constantFoldingMaterialization.pass.count"), 0), + parseInt(properties.getProperty("constantFoldingMaterialization.candidate.count"), 0), + parseInt(properties.getProperty("constantFoldingMaterialization.transformedNode.count"), 0), + parseInt(properties.getProperty("constantFoldingMaterialization.literalRewrite.count"), 0), + parseInt(properties.getProperty("constantFoldingMaterialization.identityRewrite.count"), 0), + parseInt(properties.getProperty("constantFoldingMaterialization.fixedPointPass.count"), 0), + parseInt(properties.getProperty("constantFoldingMaterialization.changedMethodBody.count"), 0), + parseInt(properties.getProperty("constantFoldingMaterialization.bodyTextReplacement.count"), 0), + parseInt(properties.getProperty("constantFoldingMaterialization.skipped.divideByZero.count"), 0), + parseInt(properties.getProperty("constantFoldingMaterialization.skipped.nonEvenDivision.count"), 0), + parseBoolean(properties.getProperty( + "constantFoldingMaterialization.runtimeEquivalenceRequiredBeforeSelection" + )), + parseBoolean(properties.getProperty( + "constantFoldingMaterialization.runtimeEquivalencePayloadRequired" + )), + parseInt(properties.getProperty( + "constantFoldingMaterialization.runtimeEquivalencePayloadPresent.count" + ), 0), + parseInt(properties.getProperty( + "constantFoldingMaterialization.runtimeEquivalencePassed.count" + ), 0), + parseBoolean(properties.getProperty( + "constantFoldingMaterialization.approvalRequiredBeforeProduction" + )), + properties.getProperty("constantFoldingMaterialization.status", "not-recorded"), + properties.getProperty("constantFoldingMaterialization.firstBlocker", "not-recorded"), + parseInt(properties.getProperty("safeLocalCseMaterialization.pass.count"), 0), + parseInt(properties.getProperty("safeLocalCseMaterialization.localBinding.count"), 0), + parseInt(properties.getProperty("safeLocalCseMaterialization.transformedNode.count"), 0), + parseInt(properties.getProperty("safeLocalCseMaterialization.bodyTextReplacement.count"), 0), + parseInt(properties.getProperty( + "safeLocalCseMaterialization.runtimeEquivalencePayloadPresent.count" + ), 0), + parseInt(properties.getProperty( + "safeLocalCseMaterialization.runtimeEquivalencePassed.count" + ), 0), + properties.getProperty("safeLocalCseMaterialization.status", "not-recorded"), + properties.getProperty("safeLocalCseMaterialization.firstBlocker", "not-recorded"), + parseInt(properties.getProperty("madFmaMaterialization.pass.count"), 0), + parseInt(properties.getProperty("madFmaMaterialization.candidate.count"), 0), + parseInt(properties.getProperty("madFmaMaterialization.transformedNode.count"), 0), + parseInt(properties.getProperty("madFmaMaterialization.changedMethodBody.count"), 0), + parseInt(properties.getProperty("madFmaMaterialization.bodyTextReplacement.count"), 0), + parseInt(properties.getProperty("madFmaMaterialization.fixedPoint.pass.count"), 0), + parseInt(properties.getProperty("madFmaMaterialization.skipped.fastMathPolicy.count"), 0), + parseInt(properties.getProperty("madFmaMaterialization.skipped.bodyTextPatternMissing.count"), 0), + parseBoolean(properties.getProperty( + "madFmaMaterialization.runtimeEquivalenceRequiredBeforeSelection" + )), + parseBoolean(properties.getProperty( + "madFmaMaterialization.runtimeEquivalencePayloadRequired" + )), + parseInt(properties.getProperty( + "madFmaMaterialization.runtimeEquivalencePayloadPresent.count" + ), 0), + parseInt(properties.getProperty( + "madFmaMaterialization.runtimeEquivalencePassed.count" + ), 0), + parseBoolean(properties.getProperty( + "madFmaMaterialization.approvalRequiredBeforeProduction" + )), + parseBoolean(properties.getProperty("madFmaMaterialization.fastMathAllowed")), + properties.getProperty("madFmaMaterialization.status", "not-recorded"), + properties.getProperty("madFmaMaterialization.firstBlocker", "not-recorded"), + parseInt(properties.getProperty("loopVectorizationMaterialization.pass.count"), 0), + parseInt(properties.getProperty("loopVectorizationMaterialization.candidate.count"), 0), + parseInt(properties.getProperty("loopVectorizationMaterialization.transformedLoop.count"), 0), + parseInt(properties.getProperty("loopVectorizationMaterialization.changedMethodBody.count"), 0), + parseInt(properties.getProperty("loopVectorizationMaterialization.bodyTextReplacement.count"), 0), + parseInt(properties.getProperty("loopVectorizationMaterialization.typedBody.materialized.count"), 0), + parseInt(properties.getProperty("loopVectorizationMaterialization.typedBody.invalidated.count"), 0), + parseInt(properties.getProperty("loopVectorizationMaterialization.skipped.loopShape.count"), 0), + parseInt(properties.getProperty("loopVectorizationMaterialization.skipped.unsupportedWidth.count"), 0), + parseInt(properties.getProperty("loopVectorizationMaterialization.skipped.unsafeLoadPattern.count"), 0), + parseInt(properties.getProperty( + "loopVectorizationMaterialization.runtimeEquivalencePayloadPresent.count" + ), 0), + parseInt(properties.getProperty( + "loopVectorizationMaterialization.runtimeEquivalencePassed.count" + ), 0), + properties.getProperty("loopVectorizationMaterialization.status", "not-recorded"), + properties.getProperty("loopVectorizationMaterialization.firstBlocker", "not-recorded"), + parseInt(properties.getProperty("typedDeadCodeMaterialization.pass.count"), 0), + parseInt(properties.getProperty("typedDeadCodeMaterialization.node.count"), 0), + parseInt(properties.getProperty("typedDeadCodeMaterialization.unreachableNode.count"), 0), + parseInt(properties.getProperty("typedDeadCodeMaterialization.removedNode.count"), 0), + parseInt(properties.getProperty("typedDeadCodeMaterialization.changedMethodBody.count"), 0), + parseInt(properties.getProperty("typedDeadCodeMaterialization.blocked.missingRoot.count"), 0), + parseInt(properties.getProperty( + "typedDeadCodeMaterialization.blocked.missingChildReference.count" + ), 0), + parseInt(properties.getProperty( + "typedDeadCodeMaterialization.blocked.sideEffectingUnreachableNode.count" + ), 0), + parseBoolean(properties.getProperty( + "typedDeadCodeMaterialization.runtimeEquivalenceRequiredBeforeSelection" + )), + parseBoolean(properties.getProperty( + "typedDeadCodeMaterialization.runtimeEquivalencePayloadRequired" + )), + parseInt(properties.getProperty( + "typedDeadCodeMaterialization.runtimeEquivalencePayloadPresent.count" + ), 0), + parseInt(properties.getProperty( + "typedDeadCodeMaterialization.runtimeEquivalencePassed.count" + ), 0), + parseBoolean(properties.getProperty( + "typedDeadCodeMaterialization.approvalRequiredBeforeProduction" + )), + parseBoolean(properties.getProperty( + "typedDeadCodeMaterialization.sideEffectFreedomProven" + )), + properties.getProperty("typedDeadCodeMaterialization.status", "not-recorded"), + properties.getProperty("typedDeadCodeMaterialization.firstBlocker", "not-recorded"), parseInt(properties.getProperty("safeLocalCsePreview.pass.count"), 0), parseInt(properties.getProperty("safeLocalCsePreview.expression.count"), 0), parseInt(properties.getProperty("safeLocalCsePreview.candidateExpression.count"), 0), @@ -844,8 +2303,15 @@ static Entry from(String kernelResource, Properties properties) { parseInt(properties.getProperty("reviewPackage.proposalPass.count"), 0), parseInt(properties.getProperty("reviewPackage.pendingApproval.count"), 0), properties.getProperty("reviewPackage.runtimeEquivalence.status", "unknown"), + properties.getProperty("reviewPackage.approvalManifest.status", "not-recorded"), + parseBoolean(properties.getProperty("reviewPackage.approvalManifest.required")), + parseInt(properties.getProperty("reviewPackage.approvalManifest.present.count"), 0), + parseInt(properties.getProperty("reviewPackage.approvalManifest.accepted.count"), 0), + properties.getProperty("reviewPackage.approvalManifest.resourcePath.summary", "none"), + properties.getProperty("reviewPackage.approvalManifest.firstBlocker", "none"), parseBoolean(properties.getProperty("reviewPackage.manualReviewOnly")), - parseProviderCounts(properties) + parseProviderCounts(properties), + copyProperties(properties) ); } @@ -1021,5 +2487,32 @@ private static Map normalizeProviderCounts(Map }); return java.util.Collections.unmodifiableMap(normalized); } + + private static Map copyProperties(Properties properties) { + if (properties == null || properties.isEmpty()) { + return Map.of(); + } + LinkedHashMap copied = new LinkedHashMap<>(); + for (String name : properties.stringPropertyNames()) { + String value = properties.getProperty(name); + if (name != null && !name.isBlank() && value != null) { + copied.put(name, value); + } + } + return java.util.Collections.unmodifiableMap(copied); + } + + private static Map normalizeEvidenceProperties(Map values) { + if (values == null || values.isEmpty()) { + return Map.of(); + } + LinkedHashMap normalized = new LinkedHashMap<>(); + values.forEach((key, value) -> { + if (key != null && !key.isBlank() && value != null) { + normalized.put(key, value); + } + }); + return java.util.Collections.unmodifiableMap(normalized); + } } } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCli.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCli.java index 415a8753..80dcc467 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCli.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCli.java @@ -88,6 +88,31 @@ private static void validateGuardrails(Path artifact, Properties properties) { requireValue(artifact, properties, "reviewPackage.optimizedIrRequired", "true"); requireValue(artifact, properties, "reviewPackage.proofSummaryRequired", "true"); validateOptimizedArtifactCandidateGuardrails(artifact, properties); + validateSafeLocalCseMaterializationEvidence(artifact, properties); + validateMadFmaMaterializationEvidence(artifact, properties); + validateIntrinsicMaterializationEvidence( + artifact, + properties, + "clampMaterialization", + "clamp", + IntrinsicMaterializationKind.CLAMP + ); + validateIntrinsicMaterializationEvidence( + artifact, + properties, + "stepMaterialization", + "step", + IntrinsicMaterializationKind.STEP + ); + validateIntrinsicMaterializationEvidence( + artifact, + properties, + "mixMaterialization", + "mix", + IntrinsicMaterializationKind.MIX + ); + validateLoopVectorizationMaterializationEvidence(artifact, properties); + validateTypedDeadCodeMaterializationEvidence(artifact, properties); String reviewPackageStatus = requirePresent(artifact, properties, "reviewPackage.status"); boolean required = parseBoolean(requirePresent(artifact, properties, "reviewPackage.required")); @@ -125,6 +150,149 @@ private static void validateGuardrails(Path artifact, Properties properties) { } } + private static void validateSafeLocalCseMaterializationEvidence(Path artifact, Properties properties) { + if (!properties.containsKey("safeLocalCseMaterialization.pass.count") + && !properties.containsKey("safeLocalCseMaterialization.status")) { + return; + } + + int passCount = parseNonNegativeInt(artifact, properties, "safeLocalCseMaterialization.pass.count"); + int localBindingCount = parseNonNegativeInt(artifact, properties, "safeLocalCseMaterialization.localBinding.count"); + parseNonNegativeInt(artifact, properties, "safeLocalCseMaterialization.candidate.count"); + int transformedNodeCount = parseNonNegativeInt( + artifact, + properties, + "safeLocalCseMaterialization.transformedNode.count" + ); + int bodyTextReplacementCount = parseNonNegativeInt( + artifact, + properties, + "safeLocalCseMaterialization.bodyTextReplacement.count" + ); + parseNonNegativeInt(artifact, properties, "safeLocalCseMaterialization.changedMethodBody.count"); + parseNonNegativeInt(artifact, properties, "safeLocalCseMaterialization.fixedPoint.pass.count"); + int skippedControlFlowBoundaryCount = parseNonNegativeInt( + artifact, + properties, + "safeLocalCseMaterialization.skipped.controlFlowBoundary.count" + ); + int skippedUnsupportedOperatorCount = parseNonNegativeInt( + artifact, + properties, + "safeLocalCseMaterialization.skipped.unsupportedOperator.count" + ); + int skippedImpureOperandCount = parseNonNegativeInt( + artifact, + properties, + "safeLocalCseMaterialization.skipped.impureOperand.count" + ); + int skippedBodyTextPatternMissingCount = parseNonNegativeInt( + artifact, + properties, + "safeLocalCseMaterialization.skipped.bodyTextPatternMissing.count" + ); + int payloadPresentCount = parseNonNegativeInt( + artifact, + properties, + "safeLocalCseMaterialization.runtimeEquivalencePayloadPresent.count" + ); + int payloadPassedCount = parseNonNegativeInt( + artifact, + properties, + "safeLocalCseMaterialization.runtimeEquivalencePassed.count" + ); + boolean payloadRequired = parseBoolean(requirePresent( + artifact, + properties, + "safeLocalCseMaterialization.runtimeEquivalencePayloadRequired" + )); + boolean approvalRequired = parseBoolean(requirePresent( + artifact, + properties, + "safeLocalCseMaterialization.approvalRequiredBeforeProduction" + )); + boolean dominanceProven = parseBoolean(requirePresent( + artifact, + properties, + "safeLocalCseMaterialization.dominanceProven" + )); + boolean sideEffectFreedomProven = parseBoolean(requirePresent( + artifact, + properties, + "safeLocalCseMaterialization.sideEffectFreedomProven" + )); + String status = requirePresent(artifact, properties, "safeLocalCseMaterialization.status"); + String firstBlocker = requirePresent(artifact, properties, "safeLocalCseMaterialization.firstBlocker"); + + validateMaterializationStatus(artifact, "safe-local-CSE", status); + if (passCount == 0 && (!"not-recorded".equals(status) || !"not-recorded".equals(firstBlocker))) { + throw new IllegalStateException("Missing safe-local-CSE materialization evidence must use not-recorded state for " + + artifact + ": status=" + status + ", firstBlocker=" + firstBlocker); + } + if (passCount > 0 && "not-recorded".equals(status)) { + throw new IllegalStateException("Recorded safe-local-CSE materialization evidence cannot use not-recorded status for " + + artifact); + } + if (payloadPassedCount > payloadPresentCount) { + throw new IllegalStateException("safe-local-CSE runtime-equivalence passed count exceeds present count for " + + artifact + ": passed=" + payloadPassedCount + ", present=" + payloadPresentCount); + } + if (transformedNodeCount > 0 && (localBindingCount <= 0 || bodyTextReplacementCount <= 0)) { + throw new IllegalStateException("safe-local-CSE materialized nodes must have existing local bindings and text replacements for " + + artifact); + } + if (transformedNodeCount > 0 && (!payloadRequired || !approvalRequired)) { + throw new IllegalStateException("safe-local-CSE materialized nodes must require runtime-equivalence and approval for " + + artifact); + } + + int skippedCount = skippedControlFlowBoundaryCount + + skippedUnsupportedOperatorCount + + skippedImpureOperandCount + + skippedBodyTextPatternMissingCount; + boolean proofClean = dominanceProven && sideEffectFreedomProven; + switch (status) { + case "no-candidates" -> { + if (transformedNodeCount > 0 || skippedCount > 0 || !"no-materialized-candidates".equals(firstBlocker)) { + throw new IllegalStateException("safe-local-CSE no-candidates evidence is inconsistent for " + artifact); + } + } + case "blocked" -> { + if (transformedNodeCount <= 0 && skippedCount <= 0) { + throw new IllegalStateException("safe-local-CSE blocked evidence must expose a skipped blocker for " + artifact); + } + if ("none".equals(firstBlocker) || "not-recorded".equals(firstBlocker)) { + throw new IllegalStateException("safe-local-CSE blocked evidence must expose a first blocker for " + artifact); + } + } + case "pending-runtime-equivalence" -> { + if (transformedNodeCount <= 0 || payloadPresentCount > 0 || !proofClean + || !"runtime-equivalence-payload-not-recorded".equals(firstBlocker)) { + throw new IllegalStateException("safe-local-CSE pending runtime-equivalence evidence is inconsistent for " + + artifact); + } + } + case "runtime-equivalence-not-passed" -> { + if (transformedNodeCount <= 0 || payloadPresentCount <= 0 + || payloadPassedCount >= payloadPresentCount || !proofClean + || !"runtime-equivalence-not-passed".equals(firstBlocker)) { + throw new IllegalStateException("safe-local-CSE failed runtime-equivalence evidence is inconsistent for " + + artifact); + } + } + case "review-ready" -> { + if (transformedNodeCount <= 0 || payloadPresentCount <= 0 + || payloadPassedCount < payloadPresentCount || !proofClean + || !"none".equals(firstBlocker)) { + throw new IllegalStateException("safe-local-CSE review-ready evidence must include proofs and passed payloads for " + + artifact); + } + } + default -> { + } + } + } + private static void validateOptimizedArtifactCandidateGuardrails(Path artifact, Properties properties) { String status = requirePresent(artifact, properties, "optimizedArtifactCandidate.status"); int count = parseNonNegativeInt(artifact, properties, "optimizedArtifactCandidate.count"); @@ -188,6 +356,675 @@ private static void validateOptimizedArtifactCandidateGuardrails(Path artifact, } } + private static void validateMadFmaMaterializationEvidence(Path artifact, Properties properties) { + if (!properties.containsKey("madFmaMaterialization.pass.count") + && !properties.containsKey("madFmaMaterialization.status")) { + return; + } + + int passCount = parseNonNegativeInt(artifact, properties, "madFmaMaterialization.pass.count"); + parseNonNegativeInt(artifact, properties, "madFmaMaterialization.candidate.count"); + int transformedNodeCount = parseNonNegativeInt( + artifact, + properties, + "madFmaMaterialization.transformedNode.count" + ); + int bodyTextReplacementCount = parseNonNegativeInt( + artifact, + properties, + "madFmaMaterialization.bodyTextReplacement.count" + ); + parseNonNegativeInt(artifact, properties, "madFmaMaterialization.changedMethodBody.count"); + parseNonNegativeInt(artifact, properties, "madFmaMaterialization.fixedPoint.pass.count"); + int skippedFastMathPolicyCount = parseNonNegativeInt( + artifact, + properties, + "madFmaMaterialization.skipped.fastMathPolicy.count" + ); + int skippedBodyTextPatternMissingCount = parseNonNegativeInt( + artifact, + properties, + "madFmaMaterialization.skipped.bodyTextPatternMissing.count" + ); + int payloadPresentCount = parseNonNegativeInt( + artifact, + properties, + "madFmaMaterialization.runtimeEquivalencePayloadPresent.count" + ); + int payloadPassedCount = parseNonNegativeInt( + artifact, + properties, + "madFmaMaterialization.runtimeEquivalencePassed.count" + ); + boolean payloadRequired = parseBoolean(requirePresent( + artifact, + properties, + "madFmaMaterialization.runtimeEquivalencePayloadRequired" + )); + boolean approvalRequired = parseBoolean(requirePresent( + artifact, + properties, + "madFmaMaterialization.approvalRequiredBeforeProduction" + )); + boolean fastMathAllowed = parseBoolean(requirePresent( + artifact, + properties, + "madFmaMaterialization.fastMathAllowed" + )); + String status = requirePresent(artifact, properties, "madFmaMaterialization.status"); + String firstBlocker = requirePresent(artifact, properties, "madFmaMaterialization.firstBlocker"); + + validateMaterializationStatus(artifact, "mad/FMA", status); + if (passCount == 0 && (!"not-recorded".equals(status) || !"not-recorded".equals(firstBlocker))) { + throw new IllegalStateException("Missing mad/FMA materialization evidence must use not-recorded state for " + + artifact + ": status=" + status + ", firstBlocker=" + firstBlocker); + } + if (passCount > 0 && "not-recorded".equals(status)) { + throw new IllegalStateException("Recorded mad/FMA materialization evidence cannot use not-recorded status for " + + artifact); + } + if (payloadPassedCount > payloadPresentCount) { + throw new IllegalStateException("mad/FMA runtime-equivalence passed count exceeds present count for " + + artifact + ": passed=" + payloadPassedCount + ", present=" + payloadPresentCount); + } + if (transformedNodeCount > 0 && bodyTextReplacementCount <= 0) { + throw new IllegalStateException("mad/FMA materialized nodes must have matching body text replacements for " + + artifact); + } + if (transformedNodeCount > 0 && (!payloadRequired || !approvalRequired)) { + throw new IllegalStateException("mad/FMA materialized nodes must require runtime-equivalence and approval for " + + artifact); + } + if (transformedNodeCount > 0 && !fastMathAllowed) { + throw new IllegalStateException("mad/FMA materialization requires fast-math policy evidence for " + artifact); + } + + int skippedCount = skippedFastMathPolicyCount + skippedBodyTextPatternMissingCount; + switch (status) { + case "no-candidates" -> { + if (transformedNodeCount > 0 || skippedCount > 0 || !"no-materialized-candidates".equals(firstBlocker)) { + throw new IllegalStateException("mad/FMA no-candidates evidence is inconsistent for " + artifact); + } + } + case "blocked" -> { + if (transformedNodeCount <= 0 && skippedCount <= 0) { + throw new IllegalStateException("mad/FMA blocked evidence must expose a skipped blocker for " + artifact); + } + if ("none".equals(firstBlocker) || "not-recorded".equals(firstBlocker)) { + throw new IllegalStateException("mad/FMA blocked evidence must expose a first blocker for " + artifact); + } + } + case "pending-runtime-equivalence" -> { + if (transformedNodeCount <= 0 || payloadPresentCount > 0 + || !"runtime-equivalence-payload-not-recorded".equals(firstBlocker)) { + throw new IllegalStateException("mad/FMA pending runtime-equivalence evidence is inconsistent for " + + artifact); + } + } + case "runtime-equivalence-not-passed" -> { + if (transformedNodeCount <= 0 || payloadPresentCount <= 0 + || payloadPassedCount >= payloadPresentCount + || !"runtime-equivalence-not-passed".equals(firstBlocker)) { + throw new IllegalStateException("mad/FMA failed runtime-equivalence evidence is inconsistent for " + + artifact); + } + } + case "review-ready" -> { + if (transformedNodeCount <= 0 || payloadPresentCount <= 0 + || payloadPassedCount < payloadPresentCount + || !"none".equals(firstBlocker)) { + throw new IllegalStateException("mad/FMA review-ready evidence must include passed payloads for " + + artifact); + } + } + default -> { + } + } + } + + private static void validateIntrinsicMaterializationEvidence( + Path artifact, + Properties properties, + String prefix, + String family, + IntrinsicMaterializationKind kind + ) { + if (!properties.containsKey(prefix + ".pass.count") + && !properties.containsKey(prefix + ".status")) { + return; + } + + int passCount = parseNonNegativeInt(artifact, properties, prefix + ".pass.count"); + parseNonNegativeInt(artifact, properties, prefix + ".candidate.count"); + int transformedNodeCount = parseNonNegativeInt(artifact, properties, prefix + ".transformedNode.count"); + int bodyTextReplacementCount = parseNonNegativeInt( + artifact, + properties, + prefix + ".bodyTextReplacement.count" + ); + parseNonNegativeInt(artifact, properties, prefix + ".changedMethodBody.count"); + parseNonNegativeInt(artifact, properties, prefix + ".fixedPoint.pass.count"); + int skippedCount = parseNonNegativeInt(artifact, properties, prefix + ".skipped.typedBodyMissing.count") + + parseNonNegativeInt(artifact, properties, prefix + ".skipped.unsupportedFormat.count") + + parseNonNegativeInt(artifact, properties, prefix + ".skipped.fastMathPolicy.count") + + parseNonNegativeInt(artifact, properties, prefix + ".skipped.missingChildReference.count") + + parseNonNegativeInt(artifact, properties, prefix + ".skipped.unsupportedShape.count") + + parseNonNegativeInt(artifact, properties, prefix + ".skipped.bodyTextPatternMissing.count"); + int payloadPresentCount = parseNonNegativeInt( + artifact, + properties, + prefix + ".runtimeEquivalencePayloadPresent.count" + ); + int payloadPassedCount = parseNonNegativeInt( + artifact, + properties, + prefix + ".runtimeEquivalencePassed.count" + ); + boolean runtimeEquivalenceRequired = parseBoolean(requirePresent( + artifact, + properties, + prefix + ".runtimeEquivalenceRequiredBeforeSelection" + )); + boolean payloadRequired = parseBoolean(requirePresent( + artifact, + properties, + prefix + ".runtimeEquivalencePayloadRequired" + )); + boolean approvalRequired = parseBoolean(requirePresent( + artifact, + properties, + prefix + ".approvalRequiredBeforeProduction" + )); + String status = requirePresent(artifact, properties, prefix + ".status"); + String firstBlocker = requirePresent(artifact, properties, prefix + ".firstBlocker"); + + validateMaterializationStatus(artifact, family, status); + if (passCount == 0 && (!"not-recorded".equals(status) || !"not-recorded".equals(firstBlocker))) { + throw new IllegalStateException("Missing " + family + + " materialization evidence must use not-recorded state for " + + artifact + ": status=" + status + ", firstBlocker=" + firstBlocker); + } + if (passCount > 0 && "not-recorded".equals(status)) { + throw new IllegalStateException("Recorded " + family + + " materialization evidence cannot use not-recorded status for " + artifact); + } + if (payloadPassedCount > payloadPresentCount) { + throw new IllegalStateException(family + " runtime-equivalence passed count exceeds present count for " + + artifact + ": passed=" + payloadPassedCount + ", present=" + payloadPresentCount); + } + if (transformedNodeCount > 0 && bodyTextReplacementCount <= 0) { + throw new IllegalStateException(family + + " materialized nodes must have matching body text replacements for " + artifact); + } + if (transformedNodeCount > 0 && (!runtimeEquivalenceRequired || !payloadRequired || !approvalRequired)) { + throw new IllegalStateException(family + + " materialized nodes must require runtime-equivalence payloads and approval for " + artifact); + } + validateIntrinsicMaterializationSafety(artifact, properties, prefix, family, kind, transformedNodeCount); + + switch (status) { + case "no-candidates" -> { + if (transformedNodeCount > 0 || skippedCount > 0 || !"no-materialized-candidates".equals(firstBlocker)) { + throw new IllegalStateException(family + " no-candidates evidence is inconsistent for " + artifact); + } + } + case "blocked" -> { + if ("none".equals(firstBlocker) || "not-recorded".equals(firstBlocker)) { + throw new IllegalStateException(family + " blocked evidence must expose a first blocker for " + + artifact); + } + } + case "pending-runtime-equivalence" -> { + if (transformedNodeCount <= 0 || payloadPresentCount > 0 + || !"runtime-equivalence-payload-not-recorded".equals(firstBlocker)) { + throw new IllegalStateException(family + + " pending runtime-equivalence evidence is inconsistent for " + artifact); + } + } + case "runtime-equivalence-not-passed" -> { + if (transformedNodeCount <= 0 || payloadPresentCount <= 0 + || payloadPassedCount >= payloadPresentCount + || !"runtime-equivalence-not-passed".equals(firstBlocker)) { + throw new IllegalStateException(family + + " failed runtime-equivalence evidence is inconsistent for " + artifact); + } + } + case "review-ready" -> { + if (transformedNodeCount <= 0 || payloadPresentCount <= 0 + || payloadPassedCount < payloadPresentCount + || !"none".equals(firstBlocker)) { + throw new IllegalStateException(family + + " review-ready evidence must include passed payloads for " + artifact); + } + } + default -> { + } + } + } + + private static void validateIntrinsicMaterializationSafety( + Path artifact, + Properties properties, + String prefix, + String family, + IntrinsicMaterializationKind kind, + int transformedNodeCount + ) { + if (transformedNodeCount <= 0) { + return; + } + switch (kind) { + case CLAMP -> validateClampMaterializationSafety(artifact, properties, prefix, family); + case STEP -> validateStepMaterializationSafety(artifact, properties, prefix, family, transformedNodeCount); + case MIX -> validateMixMaterializationSafety(artifact, properties, prefix, family, transformedNodeCount); + } + } + + private static void validateClampMaterializationSafety( + Path artifact, + Properties properties, + String prefix, + String family + ) { + boolean strictFloatPreserved = parseBoolean(requirePresent( + artifact, + properties, + prefix + ".strictFloatPreserved" + )); + boolean argumentOrderPreserved = parseBoolean(requirePresent( + artifact, + properties, + prefix + ".argumentOrderPreserved" + )); + boolean fastMathRequired = parseBoolean(requirePresent(artifact, properties, prefix + ".fastMathRequired")); + if (!strictFloatPreserved || !argumentOrderPreserved || fastMathRequired) { + throw new IllegalStateException(family + + " materialization must preserve strict float behavior and argument order without fast-math for " + + artifact); + } + } + + private static void validateStepMaterializationSafety( + Path artifact, + Properties properties, + String prefix, + String family, + int transformedNodeCount + ) { + int directStepCount = parseNonNegativeInt(artifact, properties, prefix + ".directStep.count"); + int invertedStepCount = parseNonNegativeInt(artifact, properties, prefix + ".invertedStep.count"); + boolean strictFloatPreserved = parseBoolean(requirePresent( + artifact, + properties, + prefix + ".strictFloatPreserved" + )); + boolean strictComparisonPreserved = parseBoolean(requirePresent( + artifact, + properties, + prefix + ".strictComparisonPreserved" + )); + boolean equalityBehaviorPreserved = parseBoolean(requirePresent( + artifact, + properties, + prefix + ".equalityBehaviorPreserved" + )); + boolean nanComparisonPreserved = parseBoolean(requirePresent( + artifact, + properties, + prefix + ".nanComparisonPreserved" + )); + boolean fastMathRequired = parseBoolean(requirePresent(artifact, properties, prefix + ".fastMathRequired")); + if (directStepCount + invertedStepCount != transformedNodeCount) { + throw new IllegalStateException(family + + " materialized nodes must be accounted as direct or inverted step expressions for " + artifact); + } + if (!strictFloatPreserved || !strictComparisonPreserved + || !equalityBehaviorPreserved || !nanComparisonPreserved || fastMathRequired) { + throw new IllegalStateException(family + + " materialization must preserve comparison/equality/NaN behavior without fast-math for " + + artifact); + } + } + + private static void validateMixMaterializationSafety( + Path artifact, + Properties properties, + String prefix, + String family, + int transformedNodeCount + ) { + int canonicalMixCount = parseNonNegativeInt(artifact, properties, prefix + ".canonicalMix.count"); + int expandedMixCount = parseNonNegativeInt(artifact, properties, prefix + ".expandedMix.count"); + int madExpandedMixCount = parseNonNegativeInt(artifact, properties, prefix + ".madExpandedMix.count"); + boolean fastMathAllowed = parseBoolean(requirePresent(artifact, properties, prefix + ".fastMathAllowed")); + boolean fastMathRequired = parseBoolean(requirePresent(artifact, properties, prefix + ".fastMathRequired")); + boolean strictFloatPreserved = parseBoolean(requirePresent( + artifact, + properties, + prefix + ".strictFloatPreserved" + )); + boolean algebraicReassociationRequired = parseBoolean(requirePresent( + artifact, + properties, + prefix + ".algebraicReassociationRequired" + )); + boolean mixArgumentOrderPreserved = parseBoolean(requirePresent( + artifact, + properties, + prefix + ".mixArgumentOrderPreserved" + )); + boolean expandedRewrite = expandedMixCount > 0 + || madExpandedMixCount > 0 + || fastMathRequired + || algebraicReassociationRequired; + if (canonicalMixCount + expandedMixCount + madExpandedMixCount != transformedNodeCount) { + throw new IllegalStateException(family + + " materialized nodes must be accounted as canonical/expanded/MAD-expanded mix expressions for " + + artifact); + } + if (!mixArgumentOrderPreserved) { + throw new IllegalStateException(family + " materialization must preserve mix argument order for " + artifact); + } + if (expandedRewrite && (!fastMathAllowed || !fastMathRequired || !algebraicReassociationRequired)) { + throw new IllegalStateException(family + + " expanded materialization must carry fast-math and reassociation evidence for " + artifact); + } + if (!expandedRewrite && (!strictFloatPreserved || fastMathRequired || algebraicReassociationRequired)) { + throw new IllegalStateException(family + + " canonical materialization must preserve strict float behavior without fast-math for " + artifact); + } + } + + private static void validateLoopVectorizationMaterializationEvidence(Path artifact, Properties properties) { + if (!properties.containsKey("loopVectorizationMaterialization.pass.count") + && !properties.containsKey("loopVectorizationMaterialization.status")) { + return; + } + + int passCount = parseNonNegativeInt(artifact, properties, "loopVectorizationMaterialization.pass.count"); + parseNonNegativeInt(artifact, properties, "loopVectorizationMaterialization.candidate.count"); + int transformedLoopCount = parseNonNegativeInt( + artifact, + properties, + "loopVectorizationMaterialization.transformedLoop.count" + ); + int bodyTextReplacementCount = parseNonNegativeInt( + artifact, + properties, + "loopVectorizationMaterialization.bodyTextReplacement.count" + ); + parseNonNegativeInt(artifact, properties, "loopVectorizationMaterialization.changedMethodBody.count"); + parseNonNegativeInt(artifact, properties, "loopVectorizationMaterialization.typedBody.materialized.count"); + parseNonNegativeInt(artifact, properties, "loopVectorizationMaterialization.typedBody.invalidated.count"); + int skippedLoopShapeCount = parseNonNegativeInt( + artifact, + properties, + "loopVectorizationMaterialization.skipped.loopShape.count" + ); + int skippedUnsupportedWidthCount = parseNonNegativeInt( + artifact, + properties, + "loopVectorizationMaterialization.skipped.unsupportedWidth.count" + ); + int skippedUnsafeLoadPatternCount = parseNonNegativeInt( + artifact, + properties, + "loopVectorizationMaterialization.skipped.unsafeLoadPattern.count" + ); + int payloadPresentCount = parseNonNegativeInt( + artifact, + properties, + "loopVectorizationMaterialization.runtimeEquivalencePayloadPresent.count" + ); + int payloadPassedCount = parseNonNegativeInt( + artifact, + properties, + "loopVectorizationMaterialization.runtimeEquivalencePassed.count" + ); + boolean payloadRequired = parseBoolean(requirePresent( + artifact, + properties, + "loopVectorizationMaterialization.runtimeEquivalencePayloadRequired" + )); + boolean approvalRequired = parseBoolean(requirePresent( + artifact, + properties, + "loopVectorizationMaterialization.approvalRequiredBeforeProduction" + )); + boolean loopTripCountProven = parseBoolean(requirePresent( + artifact, + properties, + "loopVectorizationMaterialization.loopTripCountProven" + )); + boolean contiguousLoadProven = parseBoolean(requirePresent( + artifact, + properties, + "loopVectorizationMaterialization.contiguousLoadProven" + )); + boolean orderedReductionPreserved = parseBoolean(requirePresent( + artifact, + properties, + "loopVectorizationMaterialization.orderedReductionPreserved" + )); + String status = requirePresent(artifact, properties, "loopVectorizationMaterialization.status"); + String firstBlocker = requirePresent(artifact, properties, "loopVectorizationMaterialization.firstBlocker"); + + validateMaterializationStatus(artifact, "loop-vectorization", status); + if (passCount == 0 && (!"not-recorded".equals(status) || !"not-recorded".equals(firstBlocker))) { + throw new IllegalStateException("Missing loop-vectorization materialization evidence must use not-recorded state for " + + artifact + ": status=" + status + ", firstBlocker=" + firstBlocker); + } + if (passCount > 0 && "not-recorded".equals(status)) { + throw new IllegalStateException("Recorded loop-vectorization materialization evidence cannot use not-recorded status for " + + artifact); + } + if (payloadPassedCount > payloadPresentCount) { + throw new IllegalStateException("loop-vectorization runtime-equivalence passed count exceeds present count for " + + artifact + ": passed=" + payloadPassedCount + ", present=" + payloadPresentCount); + } + if (transformedLoopCount > 0 && bodyTextReplacementCount <= 0) { + throw new IllegalStateException("loop-vectorization transformed loops must have matching body text replacements for " + + artifact); + } + if (transformedLoopCount > 0 && (!payloadRequired || !approvalRequired)) { + throw new IllegalStateException("loop-vectorization transformed loops must require runtime-equivalence and approval for " + + artifact); + } + + int skippedCount = skippedLoopShapeCount + skippedUnsupportedWidthCount + skippedUnsafeLoadPatternCount; + boolean proofClean = loopTripCountProven && contiguousLoadProven && orderedReductionPreserved; + switch (status) { + case "no-candidates" -> { + if (transformedLoopCount > 0 || skippedCount > 0 || !"no-materialized-candidates".equals(firstBlocker)) { + throw new IllegalStateException("loop-vectorization no-candidates evidence is inconsistent for " + artifact); + } + } + case "blocked" -> { + if (transformedLoopCount <= 0 && skippedCount <= 0) { + throw new IllegalStateException("loop-vectorization blocked evidence must expose a skipped blocker for " + artifact); + } + if ("none".equals(firstBlocker) || "not-recorded".equals(firstBlocker)) { + throw new IllegalStateException("loop-vectorization blocked evidence must expose a first blocker for " + artifact); + } + } + case "pending-runtime-equivalence" -> { + if (transformedLoopCount <= 0 || payloadPresentCount > 0 || !proofClean + || !"runtime-equivalence-payload-not-recorded".equals(firstBlocker)) { + throw new IllegalStateException("loop-vectorization pending runtime-equivalence evidence is inconsistent for " + + artifact); + } + } + case "runtime-equivalence-not-passed" -> { + if (transformedLoopCount <= 0 || payloadPresentCount <= 0 + || payloadPassedCount >= payloadPresentCount || !proofClean + || !"runtime-equivalence-not-passed".equals(firstBlocker)) { + throw new IllegalStateException("loop-vectorization failed runtime-equivalence evidence is inconsistent for " + + artifact); + } + } + case "review-ready" -> { + if (transformedLoopCount <= 0 || payloadPresentCount <= 0 + || payloadPassedCount < payloadPresentCount || !proofClean + || !"none".equals(firstBlocker)) { + throw new IllegalStateException("loop-vectorization review-ready evidence must include proofs and passed payloads for " + + artifact); + } + } + default -> { + } + } + } + + private static void validateTypedDeadCodeMaterializationEvidence(Path artifact, Properties properties) { + if (!properties.containsKey("typedDeadCodeMaterialization.pass.count") + && !properties.containsKey("typedDeadCodeMaterialization.status")) { + return; + } + + int passCount = parseNonNegativeInt(artifact, properties, "typedDeadCodeMaterialization.pass.count"); + int nodeCount = parseNonNegativeInt(artifact, properties, "typedDeadCodeMaterialization.node.count"); + int unreachableNodeCount = parseNonNegativeInt( + artifact, + properties, + "typedDeadCodeMaterialization.unreachableNode.count" + ); + int removedNodeCount = parseNonNegativeInt( + artifact, + properties, + "typedDeadCodeMaterialization.removedNode.count" + ); + parseNonNegativeInt(artifact, properties, "typedDeadCodeMaterialization.changedMethodBody.count"); + int blockedMissingRootCount = parseNonNegativeInt( + artifact, + properties, + "typedDeadCodeMaterialization.blocked.missingRoot.count" + ); + int blockedMissingChildReferenceCount = parseNonNegativeInt( + artifact, + properties, + "typedDeadCodeMaterialization.blocked.missingChildReference.count" + ); + int blockedSideEffectingUnreachableNodeCount = parseNonNegativeInt( + artifact, + properties, + "typedDeadCodeMaterialization.blocked.sideEffectingUnreachableNode.count" + ); + int payloadPresentCount = parseNonNegativeInt( + artifact, + properties, + "typedDeadCodeMaterialization.runtimeEquivalencePayloadPresent.count" + ); + int payloadPassedCount = parseNonNegativeInt( + artifact, + properties, + "typedDeadCodeMaterialization.runtimeEquivalencePassed.count" + ); + boolean payloadRequired = parseBoolean(requirePresent( + artifact, + properties, + "typedDeadCodeMaterialization.runtimeEquivalencePayloadRequired" + )); + boolean approvalRequired = parseBoolean(requirePresent( + artifact, + properties, + "typedDeadCodeMaterialization.approvalRequiredBeforeProduction" + )); + boolean sideEffectFreedomProven = parseBoolean(requirePresent( + artifact, + properties, + "typedDeadCodeMaterialization.sideEffectFreedomProven" + )); + String status = requirePresent(artifact, properties, "typedDeadCodeMaterialization.status"); + String firstBlocker = requirePresent(artifact, properties, "typedDeadCodeMaterialization.firstBlocker"); + + validateMaterializationStatus(artifact, "typed-dead-code", status); + if (passCount == 0 && (!"not-recorded".equals(status) || !"not-recorded".equals(firstBlocker))) { + throw new IllegalStateException("Missing typed-dead-code materialization evidence must use not-recorded state for " + + artifact + ": status=" + status + ", firstBlocker=" + firstBlocker); + } + if (passCount > 0 && "not-recorded".equals(status)) { + throw new IllegalStateException("Recorded typed-dead-code materialization evidence cannot use not-recorded status for " + + artifact); + } + if (unreachableNodeCount > nodeCount || removedNodeCount > unreachableNodeCount) { + throw new IllegalStateException("typed-dead-code materialization node counts are inconsistent for " + + artifact + ": nodes=" + nodeCount + ", unreachable=" + unreachableNodeCount + + ", removed=" + removedNodeCount); + } + if (payloadPassedCount > payloadPresentCount) { + throw new IllegalStateException("typed-dead-code runtime-equivalence passed count exceeds present count for " + + artifact + ": passed=" + payloadPassedCount + ", present=" + payloadPresentCount); + } + if (removedNodeCount > 0 && (!payloadRequired || !approvalRequired)) { + throw new IllegalStateException("typed-dead-code removed nodes must require runtime-equivalence and approval for " + + artifact); + } + + int blockedCount = blockedMissingRootCount + + blockedMissingChildReferenceCount + + blockedSideEffectingUnreachableNodeCount; + switch (status) { + case "no-candidates" -> { + if (removedNodeCount > 0 || blockedCount > 0 || !"no-materialized-candidates".equals(firstBlocker)) { + throw new IllegalStateException("typed-dead-code no-candidates evidence is inconsistent for " + artifact); + } + } + case "blocked" -> { + if (removedNodeCount <= 0 && blockedCount <= 0) { + throw new IllegalStateException("typed-dead-code blocked evidence must expose a blocker for " + artifact); + } + if ("none".equals(firstBlocker) || "not-recorded".equals(firstBlocker)) { + throw new IllegalStateException("typed-dead-code blocked evidence must expose a first blocker for " + artifact); + } + } + case "pending-runtime-equivalence" -> { + if (removedNodeCount <= 0 || payloadPresentCount > 0 || !sideEffectFreedomProven + || !"runtime-equivalence-payload-not-recorded".equals(firstBlocker)) { + throw new IllegalStateException("typed-dead-code pending runtime-equivalence evidence is inconsistent for " + + artifact); + } + } + case "runtime-equivalence-not-passed" -> { + if (removedNodeCount <= 0 || payloadPresentCount <= 0 + || payloadPassedCount >= payloadPresentCount || !sideEffectFreedomProven + || !"runtime-equivalence-not-passed".equals(firstBlocker)) { + throw new IllegalStateException("typed-dead-code failed runtime-equivalence evidence is inconsistent for " + + artifact); + } + } + case "review-ready" -> { + if (removedNodeCount <= 0 || payloadPresentCount <= 0 + || payloadPassedCount < payloadPresentCount || !sideEffectFreedomProven + || !"none".equals(firstBlocker)) { + throw new IllegalStateException("typed-dead-code review-ready evidence must include side-effect proof and passed payloads for " + + artifact); + } + } + default -> { + } + } + } + + private static void validateMaterializationStatus(Path artifact, String family, String status) { + if (!List.of( + "not-recorded", + "no-candidates", + "blocked", + "pending-runtime-equivalence", + "runtime-equivalence-not-passed", + "review-ready" + ).contains(status)) { + throw new IllegalStateException("Unsupported " + family + " materialization status for " + artifact + + ": " + status); + } + } + + private enum IntrinsicMaterializationKind { + CLAMP, + STEP, + MIX + } + private static String requirePresent(Path artifact, Properties properties, String key) { String value = properties.getProperty(key); if (value == null || value.isBlank()) { diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java index 24775c0e..a12e2b16 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java @@ -2657,6 +2657,10 @@ void dumpsRuntimeIrOptimizerEvidenceArtifactForProposalBridgeReports() { assertTrue(evidence.contains("selectedOptimized.count=0")); assertTrue(evidence.contains("approvalTemplate.pending.count=1")); assertTrue(evidence.contains("approvalTemplate.notApplicable.count=4")); + assertTrue(evidence.contains("approvalTemplate.runtimeEquivalencePayloadRequired.count=0")); + assertTrue(evidence.contains("approvalTemplate.runtimeEquivalencePayloadPresent.count=0")); + assertTrue(evidence.contains("approvalTemplate.runtimeEquivalencePayloadPassed.count=0")); + assertTrue(evidence.contains("approvalTemplate.runtimeEquivalencePayloadComplete.count=0")); assertTrue(evidence.contains("optimizedArtifactCandidate.status=candidate-ready")); assertTrue(evidence.contains("optimizedArtifactCandidate.count=1")); assertTrue(evidence.contains("optimizedArtifactCandidate.ready.count=1")); @@ -2711,7 +2715,18 @@ void dumpsRuntimeIrOptimizerEvidenceArtifactForProposalBridgeReports() { assertTrue(evidence.contains("runtimeEquivalenceReview.eligible=false")); assertTrue(evidence.contains("runtimeEquivalenceReview.required=true")); assertTrue(evidence.contains("runtimeEquivalenceReview.firstBlocker=preview-readiness-blocked-by-proof")); - assertTrue(evidence.contains("runtimeEquivalenceReview.familySummary=constant-folding=blocked-by-proof, safe-local-cse=blocked-by-proof, typed-dead-code=blocked-by-proof")); + assertTrue(evidence.contains( + "runtimeEquivalenceReview.familySummary=constant-folding=blocked-by-proof, " + + "safe-local-cse=blocked-by-proof, typed-dead-code=blocked-by-proof, " + + "constant-folding-materialization=not-recorded, " + + "safe-local-cse-materialization=not-recorded, " + + "mad-fma-materialization=not-recorded, " + + "clamp-materialization=not-recorded, " + + "step-materialization=not-recorded, " + + "mix-materialization=not-recorded, " + + "loop-vectorization-materialization=not-recorded, " + + "typed-dead-code-materialization=not-recorded" + )); assertTrue(evidence.contains("runtimeEquivalenceReview.productionMutation=disabled")); assertTrue(evidence.contains("runtimeEquivalenceReview.selectedIrReplacement=disabled")); assertTrue(evidence.contains("runtimeEquivalenceReview.manualReviewOnly=true")); @@ -2728,6 +2743,12 @@ void dumpsRuntimeIrOptimizerEvidenceArtifactForProposalBridgeReports() { assertTrue(evidence.contains("reviewPackage.manualReviewOnly=true")); assertTrue(evidence.contains("reviewPackage.productionMutation=disabled")); assertTrue(evidence.contains("reviewPackage.selectedIrReplacement=disabled")); + assertTrue(evidence.contains("reviewPackage.approvalManifest.status=pending-resource-path")); + assertTrue(evidence.contains("reviewPackage.approvalManifest.required=true")); + assertTrue(evidence.contains("reviewPackage.approvalManifest.present.count=0")); + assertTrue(evidence.contains("reviewPackage.approvalManifest.accepted.count=0")); + assertTrue(evidence.contains("reviewPackage.approvalManifest.firstBlocker=approval-manifest-resource-path-missing")); + assertTrue(evidence.contains("reviewPackage.approvalManifest.resourcePath.summary=none")); assertTrue(evidence.contains("pass.0.passVersion=javatogpu.ir-optimizer.noop:1")); assertTrue(evidence.contains("pass.1.passVersion=javatogpu.ir-optimizer.text-canonicalization:1")); assertTrue(evidence.contains("pass.2.passVersion=javatogpu.ir-optimizer.constant-folding-preview:1")); @@ -2758,6 +2779,1048 @@ void dumpsRuntimeIrOptimizerEvidenceArtifactForProposalBridgeReports() { assertFalse(evidence.contains("optimizer:other")); } + @Test + void dumpsBackendNeutralSourceMaterializationEvidenceWithoutSelectingOptimizedIr() { + IrGpuArtifact original = artifact("body\n set output[0] = 1\n"); + GpuBackendModuleArtifact backendArtifact = GpuBackendModuleArtifact.openClSource( + "__kernel void kernel(__global int* out) { out[0] = 1; }", + "runtime/lowered/kernel.cl", + "test-lowerer-v1" + ); + GpuRuntimeCompileRequest request = new GpuRuntimeCompileRequest( + descriptor(), + new GpuRuntimeCompileOptions(GpuBackendTarget.OPENCL, List.of(), "diagnostic"), + GpuRuntimeDeviceProfile.generic(GpuBackendTarget.OPENCL, "OpenCL"), + Optional.of(original) + ); + GpuRuntimeIrOptimizationPassReport materializationReport = new GpuRuntimeIrOptimizationPassReport( + GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, + "javatogpu.ir-optimizer.backend-neutral-source-materialization:1", + GpuRuntimeIrOptimizationOutcome.SKIPPED, + "irgpu:sha256:original", + "irgpu:sha256:backend-neutral-ready", + "proposal-only", + "", + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.backend-neutral-source-materialization", + "review-only-source-materialized", + Map.ofEntries( + Map.entry("backendTarget", "OPENCL"), + Map.entry("optimizerFamily", "backend-neutral-source-materialization"), + Map.entry("rewrite.proposed", "true"), + Map.entry("rewrite.materialized", "true"), + Map.entry("sourceGenerated", "true"), + Map.entry("sourceReady", "true"), + Map.entry("sourceLength", "123"), + Map.entry("materializationOnly", "true"), + Map.entry("mutationRequired", "false"), + Map.entry("productionAffecting", "false"), + Map.entry("provider.mutatesOriginal", "false"), + Map.entry("proof.approvalRequiredBeforeProduction", "true"), + Map.entry("optimizedArtifactCandidate.status", "candidate-ready"), + Map.entry("optimizedArtifactCandidate.candidateBuilt", "true"), + Map.entry("optimizedArtifactCandidate.optimizedValidationPassed", "true"), + Map.entry("optimizedArtifactCandidate.proofPresent", "true"), + Map.entry("optimizedArtifactCandidate.rollbackRequired", "true"), + Map.entry("optimizedArtifactCandidate.mutationAllowed", "false"), + Map.entry("optimizedArtifactCandidate.selectionReady", "false"), + Map.entry("optimizedArtifactCandidate.selectionApplied", "false"), + Map.entry("optimizedArtifactCandidate.selectedIrReplacement", "false"), + Map.entry("optimizedArtifactCandidate.firstBlocker", "none"), + Map.entry("optimizedArtifactCandidate.selectionFirstBlocker", "mutation-disabled") + ) + ), + List.of("materialized backend-neutral source metadata for review") + ); + GpuRuntimeIrOptimizationReport optimizationReport = new GpuRuntimeIrOptimizationReport( + Optional.of(original), + List.of(materializationReport), + GpuOptimizationStrategyDecision.none(request) + ); + GpuRuntimeCompileArtifactSnapshot snapshot = GpuRuntimeCompileArtifactSnapshot.from( + request, + request, + backendArtifact, + GpuRuntimeCompileInvalidationStamp.from(request, backendArtifact, "optimizer:source-materialization"), + GpuRuntimeCompileProvenance.from(request), + optimizationReport + ); + + GpuRuntimeCompileArtifactDump dump = GpuRuntimeCompileArtifactDumper.dump(snapshot); + String evidence = dump.artifact(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + + assertTrue(evidence.contains("proposalOnly.count=1")); + assertTrue(evidence.contains("selectedOptimized.count=0")); + assertTrue(evidence.contains("backendNeutralSourceMaterialization.pass.count=1")); + assertTrue(evidence.contains("backendNeutralSourceMaterialization.candidate.count=1")); + assertTrue(evidence.contains("backendNeutralSourceMaterialization.sourceReady.count=1")); + assertTrue(evidence.contains("backendNeutralSourceMaterialization.sourceLength.total=123")); + assertTrue(evidence.contains("backendNeutralSourceMaterialization.materializationOnly.count=1")); + assertTrue(evidence.contains("backendNeutralSourceMaterialization.status=review-ready")); + assertTrue(evidence.contains("backendNeutralSourceMaterialization.firstBlocker=none")); + assertTrue(evidence.contains("runtimeEquivalenceReview.required=false")); + assertTrue(evidence.contains("reviewPackage.status=pending-manual-review")); + assertTrue(evidence.contains("reviewPackage.firstBlocker=approval-template-pending")); + assertTrue(evidence.contains("reviewPackage.pendingApproval.count=1")); + assertTrue(evidence.contains("optimizedArtifactCandidate.selectionApplied=false")); + assertTrue(evidence.contains("optimizedArtifactCandidate.selectedIrReplacement=false")); + assertTrue(evidence.contains("pass.0.approvalTemplate.status=pending")); + assertTrue(evidence.contains("pass.0.proofArtifact.field.productionAffecting=false")); + } + + @Test + void dumpsConstantFoldingMaterializationRuntimeEquivalenceReviewEvidence() { + IrGpuArtifact original = artifact("body\n set output[0] = (2 + 3)\n"); + GpuBackendModuleArtifact backendArtifact = GpuBackendModuleArtifact.openClSource( + "__kernel void kernel(__global int* out) { out[0] = 1; }", + "runtime/lowered/kernel.cl", + "test-lowerer-v1" + ); + GpuRuntimeCompileRequest request = new GpuRuntimeCompileRequest( + descriptor(), + new GpuRuntimeCompileOptions(GpuBackendTarget.OPENCL, List.of(), "diagnostic"), + GpuRuntimeDeviceProfile.generic(GpuBackendTarget.OPENCL, "OpenCL"), + Optional.of(original) + ); + GpuRuntimeIrOptimizationPassReport materializationReport = new GpuRuntimeIrOptimizationPassReport( + GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, + "javatogpu.ir-optimizer.constant-folding-materialization:1", + GpuRuntimeIrOptimizationOutcome.SKIPPED, + "irgpu:sha256:original", + "irgpu:sha256:folded", + "proposal-only", + "", + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.constant-folding-materialization", + "review-only-constant-folding-materialized", + Map.ofEntries( + Map.entry("candidate.count", "1"), + Map.entry("transformedNode.count", "1"), + Map.entry("literalRewrite.count", "1"), + Map.entry("identityRewrite.count", "0"), + Map.entry("fixedPoint.pass.count", "2"), + Map.entry("changedMethodBody.count", "1"), + Map.entry("bodyTextReplacement.count", "1"), + Map.entry("skipped.divideByZero.count", "1"), + Map.entry("skipped.nonEvenDivision.count", "2"), + Map.entry("rewrite.proposed", "true"), + Map.entry("rewrite.materialized", "true"), + Map.entry("optimizerFamily", "constant-folding-materialization"), + Map.entry("proof.runtimeEquivalenceRequiredBeforeSelection", "true"), + Map.entry("proof.runtimeEquivalencePayloadRequiredBeforeSelection", "true"), + Map.entry("proof.approvalRequiredBeforeProduction", "true"), + Map.entry("runtimeEquivalencePayload.required", "true"), + Map.entry("runtimeEquivalencePayload.present", "false"), + Map.entry("runtimeEquivalencePayload.passed", "false"), + Map.entry( + "runtimeEquivalencePayload.firstBlocker", + "runtime-equivalence-payload-not-recorded" + ), + Map.entry("optimizedArtifactCandidate.status", "candidate-ready"), + Map.entry("optimizedArtifactCandidate.candidateBuilt", "true"), + Map.entry("optimizedArtifactCandidate.optimizedValidationPassed", "true"), + Map.entry("optimizedArtifactCandidate.proofPresent", "true"), + Map.entry("optimizedArtifactCandidate.rollbackRequired", "true"), + Map.entry("optimizedArtifactCandidate.mutationAllowed", "false"), + Map.entry("optimizedArtifactCandidate.selectionReady", "false"), + Map.entry("optimizedArtifactCandidate.selectionApplied", "false"), + Map.entry("optimizedArtifactCandidate.selectedIrReplacement", "false"), + Map.entry("optimizedArtifactCandidate.firstBlocker", "none"), + Map.entry("optimizedArtifactCandidate.selectionFirstBlocker", "mutation-disabled") + ) + ), + List.of("materialized one constant-folding review candidate") + ); + GpuRuntimeIrOptimizationReport optimizationReport = new GpuRuntimeIrOptimizationReport( + Optional.of(original), + List.of(materializationReport), + GpuOptimizationStrategyDecision.none(request) + ); + GpuRuntimeCompileArtifactSnapshot snapshot = GpuRuntimeCompileArtifactSnapshot.from( + request, + request, + backendArtifact, + GpuRuntimeCompileInvalidationStamp.from(request, backendArtifact, "optimizer:cf-materialization"), + GpuRuntimeCompileProvenance.from(request), + optimizationReport + ); + + String evidence = GpuRuntimeCompileArtifactDumper.dump(snapshot) + .artifact(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + + assertTrue(evidence.contains("constantFoldingMaterialization.pass.count=1")); + assertTrue(evidence.contains("constantFoldingMaterialization.transformedNode.count=1")); + assertTrue(evidence.contains("constantFoldingMaterialization.literalRewrite.count=1")); + assertTrue(evidence.contains("constantFoldingMaterialization.identityRewrite.count=0")); + assertTrue(evidence.contains("constantFoldingMaterialization.fixedPointPass.count=2")); + assertTrue(evidence.contains("constantFoldingMaterialization.skipped.divideByZero.count=1")); + assertTrue(evidence.contains("constantFoldingMaterialization.skipped.nonEvenDivision.count=2")); + assertTrue(evidence.contains("constantFoldingMaterialization.runtimeEquivalencePayloadRequired=true")); + assertTrue(evidence.contains("constantFoldingMaterialization.runtimeEquivalencePayloadPresent.count=0")); + assertTrue(evidence.contains("constantFoldingMaterialization.runtimeEquivalencePassed.count=0")); + assertTrue(evidence.contains("constantFoldingMaterialization.status=pending-runtime-equivalence")); + assertTrue(evidence.contains( + "constantFoldingMaterialization.firstBlocker=runtime-equivalence-payload-not-recorded" + )); + assertTrue(evidence.contains("runtimeEquivalenceReview.required=true")); + assertTrue(evidence.contains("runtimeEquivalenceReview.eligible=false")); + assertTrue(evidence.contains( + "runtimeEquivalenceReview.firstBlocker=runtime-equivalence-payload-not-recorded" + )); + assertTrue(evidence.contains("reviewPackage.status=pending-manual-review")); + assertTrue(evidence.contains("reviewPackage.pendingApproval.count=1")); + assertTrue(evidence.contains("reviewPackage.firstBlocker=runtime-equivalence-payload-not-recorded")); + assertTrue(evidence.contains("reviewPackage.approvalManifest.status=pending-resource-path")); + assertTrue(evidence.contains("reviewPackage.approvalManifest.required=true")); + assertTrue(evidence.contains("reviewPackage.approvalManifest.firstBlocker=approval-manifest-resource-path-missing")); + assertTrue(evidence.contains("approvalTemplate.runtimeEquivalencePayloadRequired.count=1")); + assertTrue(evidence.contains("approvalTemplate.runtimeEquivalencePayloadPresent.count=0")); + assertTrue(evidence.contains("approvalTemplate.runtimeEquivalencePayloadPassed.count=0")); + assertTrue(evidence.contains("approvalTemplate.runtimeEquivalencePayloadComplete.count=0")); + assertTrue(evidence.contains("pass.0.proofArtifact.field.runtimeEquivalencePayload.present=false")); + assertTrue(evidence.contains("pass.0.approvalTemplate.status=pending")); + assertTrue(evidence.contains("pass.0.approvalTemplate.field.runtimeEquivalencePayload.required=true")); + assertTrue(evidence.contains("pass.0.approvalTemplate.field.runtimeEquivalencePayload.present=false")); + assertTrue(evidence.contains("pass.0.approvalTemplate.field.runtimeEquivalencePayload.componentsComplete=false")); + assertTrue(evidence.contains("pass.0.approvalTemplate.field.runtimeEquivalencePayload.comparisonMode=missing")); + assertTrue(evidence.contains("pass.0.approvalTemplate.field.resourcePath=missing")); + } + + @Test + void dumpsConstantFoldingMaterializationReviewReadyRuntimeEquivalencePayload() { + IrGpuArtifact original = artifact("body\n set output[0] = (2 + 3)\n"); + GpuBackendModuleArtifact backendArtifact = GpuBackendModuleArtifact.openClSource( + "__kernel void kernel(__global int* out) { out[0] = 1; }", + "runtime/lowered/kernel.cl", + "test-lowerer-v1" + ); + GpuRuntimeCompileRequest request = new GpuRuntimeCompileRequest( + descriptor(), + new GpuRuntimeCompileOptions(GpuBackendTarget.OPENCL, List.of(), "diagnostic"), + GpuRuntimeDeviceProfile.generic(GpuBackendTarget.OPENCL, "OpenCL"), + Optional.of(original) + ); + GpuRuntimeIrOptimizationPassReport materializationReport = new GpuRuntimeIrOptimizationPassReport( + GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, + "javatogpu.ir-optimizer.constant-folding-materialization:1", + GpuRuntimeIrOptimizationOutcome.SKIPPED, + "irgpu:sha256:original", + "irgpu:sha256:folded", + "proposal-only", + "", + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.constant-folding-materialization", + "accepted", + Map.ofEntries( + Map.entry("candidate.count", "1"), + Map.entry("transformedNode.count", "1"), + Map.entry("literalRewrite.count", "1"), + Map.entry("identityRewrite.count", "0"), + Map.entry("fixedPoint.pass.count", "1"), + Map.entry("changedMethodBody.count", "1"), + Map.entry("bodyTextReplacement.count", "1"), + Map.entry("rewrite.proposed", "true"), + Map.entry("rewrite.materialized", "true"), + Map.entry("optimizerFamily", "constant-folding-materialization"), + Map.entry("proof.runtimeEquivalenceRequiredBeforeSelection", "true"), + Map.entry("proof.runtimeEquivalencePayloadRequiredBeforeSelection", "true"), + Map.entry("proof.approvalRequiredBeforeProduction", "true"), + Map.entry("runtimeEquivalencePayload.status", "recorded"), + Map.entry("runtimeEquivalencePayload.required", "true"), + Map.entry("runtimeEquivalencePayload.present", "true"), + Map.entry("runtimeEquivalencePayload.passed", "true"), + Map.entry("runtimeEquivalencePayload.firstBlocker", "none"), + Map.entry("runtimeEquivalencePayload.cpuReference.present", "true"), + Map.entry("runtimeEquivalencePayload.preOptimizationOutput.present", "true"), + Map.entry("runtimeEquivalencePayload.postOptimizationOutput.present", "true"), + Map.entry("runtimeEquivalencePayload.tolerance.present", "true"), + Map.entry("runtimeEquivalencePayload.failureFixture.present", "true"), + Map.entry( + "runtimeEquivalencePayload.resource", + "ir-optimizer://constant-folding-materialization/review-candidate" + ), + Map.entry( + "runtimeEquivalencePayload.comparisonMode", + "optimizer-family:constant-folding-materialization:review-candidate" + ), + Map.entry( + "runtimeEquivalencePayload.cpuReference.resource", + "ir-optimizer://constant-folding-materialization/review-candidate/cpu-reference" + ), + Map.entry( + "runtimeEquivalencePayload.preOptimizationOutput.resource", + "ir-optimizer://constant-folding-materialization/review-candidate/pre-optimization" + ), + Map.entry( + "runtimeEquivalencePayload.postOptimizationOutput.resource", + "ir-optimizer://constant-folding-materialization/review-candidate/post-optimization" + ), + Map.entry( + "runtimeEquivalencePayload.tolerance.resource", + "ir-optimizer://constant-folding-materialization/review-candidate/tolerance" + ), + Map.entry( + "runtimeEquivalencePayload.failureFixture.resource", + "ir-optimizer://constant-folding-materialization/review-candidate/failure-fixture" + ), + Map.entry("runtimeEquivalencePayload.CpuReference", "folds=1, mode=static-exact-int32, first=5"), + Map.entry("runtimeEquivalencePayload.PreOptimizationOutput", "expressions=1, first=(2 + 3)"), + Map.entry("runtimeEquivalencePayload.PostOptimizationOutput", "foldedValues=1, first=5"), + Map.entry("runtimeEquivalencePayload.Tolerance", "mode=exact-int32, overflow=false"), + Map.entry("runtimeEquivalencePayload.FailureFixture", "none"), + Map.entry("runtimeEquivalencePayload.Case.Count", "1"), + Map.entry("runtimeEquivalencePayload.Case.0.Name", "kernel#1=2+3->5"), + Map.entry("runtimeEquivalencePayload.Case.0.Successful", "true"), + Map.entry("runtimeEquivalencePayload.Case.0.Output.0.CpuReference", "5"), + Map.entry("runtimeEquivalencePayload.Case.0.Output.0.PreOptimization", "(2 + 3)"), + Map.entry("runtimeEquivalencePayload.Case.0.Output.0.PostOptimization", "5"), + Map.entry("runtimeEquivalencePayload.Case.0.Output.0.Tolerance", "exact-int32"), + Map.entry("runtimeEquivalencePayload.Case.0.Output.0.Equivalent", "true"), + Map.entry("optimizedArtifactCandidate.status", "candidate-ready"), + Map.entry("optimizedArtifactCandidate.candidateBuilt", "true"), + Map.entry("optimizedArtifactCandidate.optimizedValidationPassed", "true"), + Map.entry("optimizedArtifactCandidate.proofPresent", "true"), + Map.entry("optimizedArtifactCandidate.rollbackRequired", "true"), + Map.entry("optimizedArtifactCandidate.mutationAllowed", "false"), + Map.entry("optimizedArtifactCandidate.selectionReady", "false"), + Map.entry("optimizedArtifactCandidate.selectionApplied", "false"), + Map.entry("optimizedArtifactCandidate.selectedIrReplacement", "false"), + Map.entry("optimizedArtifactCandidate.firstBlocker", "none"), + Map.entry("optimizedArtifactCandidate.selectionFirstBlocker", "mutation-disabled") + ) + ), + List.of("materialized one constant-folding review candidate with exact-int payload") + ); + GpuRuntimeIrOptimizationReport optimizationReport = new GpuRuntimeIrOptimizationReport( + Optional.of(original), + List.of(materializationReport), + GpuOptimizationStrategyDecision.none(request) + ); + GpuRuntimeCompileArtifactSnapshot snapshot = GpuRuntimeCompileArtifactSnapshot.from( + request, + request, + backendArtifact, + GpuRuntimeCompileInvalidationStamp.from(request, backendArtifact, "optimizer:cf-materialization"), + GpuRuntimeCompileProvenance.from(request), + optimizationReport, + GpuRuntimeEquivalenceEvidence.passed(request, 1, 1, List.of("constant folding payload matched")) + ); + + GpuRuntimeCompileArtifactDump dump = GpuRuntimeCompileArtifactDumper.dump(snapshot); + String evidence = dump.artifact(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + String payload = dump.artifact(GpuPromotionArtifactRegistry.RUNTIME_OPTIMIZER_FAMILY_EQUIVALENCE_PAYLOAD); + + assertTrue(evidence.contains("constantFoldingMaterialization.runtimeEquivalencePayloadPresent.count=1")); + assertTrue(evidence.contains("constantFoldingMaterialization.runtimeEquivalencePassed.count=1")); + assertTrue(evidence.contains("constantFoldingMaterialization.literalRewrite.count=1")); + assertTrue(evidence.contains("constantFoldingMaterialization.identityRewrite.count=0")); + assertTrue(evidence.contains("constantFoldingMaterialization.status=review-ready")); + assertTrue(evidence.contains("constantFoldingMaterialization.firstBlocker=none")); + assertTrue(evidence.contains("runtimeEquivalenceReview.status=review-ready")); + assertTrue(evidence.contains("runtimeEquivalenceReview.eligible=true")); + assertTrue(evidence.contains("runtimeEquivalenceReview.firstBlocker=none")); + assertTrue(evidence.contains("reviewPackage.status=pending-manual-review")); + assertTrue(evidence.contains("reviewPackage.firstBlocker=approval-template-pending")); + assertTrue(evidence.contains("reviewPackage.approvalManifest.status=pending-resource-path")); + assertTrue(evidence.contains("reviewPackage.approvalManifest.required=true")); + assertTrue(evidence.contains("reviewPackage.approvalManifest.accepted.count=0")); + assertTrue(evidence.contains("reviewPackage.approvalManifest.firstBlocker=approval-manifest-resource-path-missing")); + assertTrue(evidence.contains("approvalTemplate.runtimeEquivalencePayloadRequired.count=1")); + assertTrue(evidence.contains("approvalTemplate.runtimeEquivalencePayloadPresent.count=1")); + assertTrue(evidence.contains("approvalTemplate.runtimeEquivalencePayloadPassed.count=1")); + assertTrue(evidence.contains("approvalTemplate.runtimeEquivalencePayloadComplete.count=1")); + assertTrue(evidence.contains("pass.0.approvalTemplate.status=pending")); + assertTrue(evidence.contains("pass.0.approvalTemplate.field.runtimeEquivalencePayload.required=true")); + assertTrue(evidence.contains("pass.0.approvalTemplate.field.runtimeEquivalencePayload.present=true")); + assertTrue(evidence.contains("pass.0.approvalTemplate.field.runtimeEquivalencePayload.passed=true")); + assertTrue(evidence.contains("pass.0.approvalTemplate.field.runtimeEquivalencePayload.componentsComplete=true")); + assertTrue(evidence.contains("pass.0.approvalTemplate.field.runtimeEquivalencePayload.caseCount=1")); + assertTrue(evidence.contains( + "pass.0.approvalTemplate.field.runtimeEquivalencePayload.resource=ir-optimizer://constant-folding-materialization/review-candidate" + )); + assertTrue(evidence.contains( + "pass.0.approvalTemplate.field.runtimeEquivalencePayload.comparisonMode=optimizer-family:constant-folding-materialization:review-candidate" + )); + assertTrue(evidence.contains("pass.0.approvalTemplate.field.resourcePath=missing")); + assertTrue(evidence.contains("optimizedArtifactCandidate.selectionApplied=false")); + assertTrue(evidence.contains("optimizedArtifactCandidate.selectedIrReplacement=false")); + assertTrue(payload.contains("family.0.name=constant-folding-materialization")); + assertTrue(payload.contains("family.0.complete=true")); + assertTrue(payload.contains("family.0.firstMissing=none")); + assertTrue(payload.contains("family.0.pass.0.cpuReference.payload=folds=1, mode=static-exact-int32, first=5")); + assertTrue(payload.contains("family.0.pass.0.postOptimizationOutput.payload=foldedValues=1, first=5")); + String familyDirectory = "runtime-optimizer-family-equivalence-payload/family-0-constant-folding-materialization/pass-0"; + assertTrue(dump.hasArtifact(familyDirectory + "/cpu-reference.properties")); + assertTrue(dump.artifact(familyDirectory + "/cpu-reference.properties").contains( + "runtimeEquivalencePayload.Case.0.Output.0.CpuReference" + )); + assertTrue(dump.artifact(familyDirectory + "/post-optimization-output.properties").contains( + "runtimeEquivalencePayload.Case.0.Output.0.PostOptimization" + )); + assertTrue(dump.artifact(familyDirectory + "/tolerance.properties").contains( + "runtimeEquivalencePayload.Case.0.Output.0.Tolerance" + )); + assertTrue(dump.artifact(familyDirectory + "/failure-fixture.properties").contains("payload=none")); + } + + @Test + void dumpsSafeLocalCseMaterializationReviewReadyRuntimeEquivalencePayload() { + IrGpuArtifact original = artifact("body\n var int tmp = (a + b)\n set output[0] = (a + b)\n"); + GpuBackendModuleArtifact backendArtifact = GpuBackendModuleArtifact.openClSource( + "__kernel void kernel(__global int* out) { out[0] = 1; }", + "runtime/lowered/kernel.cl", + "test-lowerer-v1" + ); + GpuRuntimeCompileRequest request = new GpuRuntimeCompileRequest( + descriptor(), + new GpuRuntimeCompileOptions(GpuBackendTarget.OPENCL, List.of(), "diagnostic"), + GpuRuntimeDeviceProfile.generic(GpuBackendTarget.OPENCL, "OpenCL"), + Optional.of(original) + ); + GpuRuntimeIrOptimizationPassReport materializationReport = new GpuRuntimeIrOptimizationPassReport( + GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, + "javatogpu.ir-optimizer.safe-local-cse-materialization:1", + GpuRuntimeIrOptimizationOutcome.SKIPPED, + "irgpu:sha256:original", + "irgpu:sha256:safe-local-cse", + "proposal-only", + "", + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.safe-local-cse-materialization", + "review-only-safe-local-cse-materialized", + Map.ofEntries( + Map.entry("localBinding.count", "1"), + Map.entry("candidate.count", "1"), + Map.entry("transformedNode.count", "1"), + Map.entry("changedMethodBody.count", "1"), + Map.entry("bodyTextReplacement.count", "1"), + Map.entry("fixedPoint.pass.count", "1"), + Map.entry("skipped.controlFlowBoundary.count", "0"), + Map.entry("skipped.unsupportedOperator.count", "0"), + Map.entry("skipped.impureOperand.count", "0"), + Map.entry("skipped.bodyTextPatternMissing.count", "0"), + Map.entry("rewrite.proposed", "true"), + Map.entry("rewrite.materialized", "true"), + Map.entry("optimizerFamily", "safe-local-cse-materialization"), + Map.entry("proof.runtimeEquivalenceRequiredBeforeSelection", "true"), + Map.entry("proof.runtimeEquivalencePayloadRequiredBeforeSelection", "true"), + Map.entry("proof.approvalRequiredBeforeProduction", "true"), + Map.entry("runtimeEquivalencePayload.status", "recorded"), + Map.entry("runtimeEquivalencePayload.required", "true"), + Map.entry("runtimeEquivalencePayload.present", "true"), + Map.entry("runtimeEquivalencePayload.passed", "true"), + Map.entry("runtimeEquivalencePayload.firstBlocker", "none"), + Map.entry("runtimeEquivalencePayload.cpuReference.present", "true"), + Map.entry("runtimeEquivalencePayload.preOptimizationOutput.present", "true"), + Map.entry("runtimeEquivalencePayload.postOptimizationOutput.present", "true"), + Map.entry("runtimeEquivalencePayload.tolerance.present", "true"), + Map.entry("runtimeEquivalencePayload.failureFixture.present", "true"), + Map.entry( + "runtimeEquivalencePayload.resource", + "ir-optimizer://safe-local-cse-materialization/review-candidate" + ), + Map.entry( + "runtimeEquivalencePayload.comparisonMode", + "optimizer-family:safe-local-cse-materialization:review-candidate" + ), + Map.entry("runtimeEquivalencePayload.CpuReference", "reuseExistingLocal=1"), + Map.entry("runtimeEquivalencePayload.PreOptimizationOutput", "expressions=1, first=(a + b)"), + Map.entry("runtimeEquivalencePayload.PostOptimizationOutput", "localRefs=1, first=tmp"), + Map.entry("runtimeEquivalencePayload.Tolerance", "mode=exact-expression-reuse, floatingPoint=unchanged"), + Map.entry("runtimeEquivalencePayload.FailureFixture", "none"), + Map.entry("runtimeEquivalencePayload.ReferenceMode", "static-existing-local-expression-reuse"), + Map.entry("runtimeEquivalencePayload.Case.Count", "1"), + Map.entry("runtimeEquivalencePayload.Case.0.Name", "kernel#6=(a + b)->tmp"), + Map.entry("runtimeEquivalencePayload.Case.0.MethodName", "kernel"), + Map.entry("runtimeEquivalencePayload.Case.0.NodeId", "6"), + Map.entry("runtimeEquivalencePayload.Case.0.RewriteKind", "safe-local-cse-reuse-existing-local"), + Map.entry("runtimeEquivalencePayload.Case.0.Successful", "true"), + Map.entry("runtimeEquivalencePayload.Case.0.Output.0.CpuReference", "tmp"), + Map.entry("runtimeEquivalencePayload.Case.0.Output.0.PreOptimization", "(a + b)"), + Map.entry("runtimeEquivalencePayload.Case.0.Output.0.PostOptimization", "tmp"), + Map.entry("runtimeEquivalencePayload.Case.0.Output.0.Tolerance", "exact-expression-reuse"), + Map.entry("runtimeEquivalencePayload.Case.0.Output.0.Equivalent", "true"), + Map.entry("safety.dominanceProven", "true"), + Map.entry("safety.sideEffectFreedomProven", "true"), + Map.entry("firstBlocker", "none") + ) + ), + List.of("materialized safe local CSE review candidate") + ); + GpuRuntimeIrOptimizationReport optimizationReport = new GpuRuntimeIrOptimizationReport( + Optional.of(original), + List.of(materializationReport), + GpuOptimizationStrategyDecision.none(request) + ); + GpuRuntimeCompileArtifactSnapshot snapshot = GpuRuntimeCompileArtifactSnapshot.from( + request, + request, + backendArtifact, + GpuRuntimeCompileInvalidationStamp.from(request, backendArtifact, "optimizer:safe-cse-materialization"), + GpuRuntimeCompileProvenance.from(request), + optimizationReport, + GpuRuntimeEquivalenceEvidence.passed(request, 1, 1, List.of("safe local CSE payload matched")) + ); + + GpuRuntimeCompileArtifactDump dump = GpuRuntimeCompileArtifactDumper.dump(snapshot); + String evidence = dump.artifact(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + String payload = dump.artifact(GpuPromotionArtifactRegistry.RUNTIME_OPTIMIZER_FAMILY_EQUIVALENCE_PAYLOAD); + + assertTrue(evidence.contains("safeLocalCseMaterialization.pass.count=1")); + assertTrue(evidence.contains("safeLocalCseMaterialization.localBinding.count=1")); + assertTrue(evidence.contains("safeLocalCseMaterialization.transformedNode.count=1")); + assertTrue(evidence.contains("safeLocalCseMaterialization.bodyTextReplacement.count=1")); + assertTrue(evidence.contains("safeLocalCseMaterialization.fixedPoint.pass.count=1")); + assertTrue(evidence.contains("safeLocalCseMaterialization.runtimeEquivalencePayloadPresent.count=1")); + assertTrue(evidence.contains("safeLocalCseMaterialization.runtimeEquivalencePassed.count=1")); + assertTrue(evidence.contains("safeLocalCseMaterialization.dominanceProven=true")); + assertTrue(evidence.contains("safeLocalCseMaterialization.sideEffectFreedomProven=true")); + assertTrue(evidence.contains("safeLocalCseMaterialization.status=review-ready")); + assertTrue(evidence.contains("safeLocalCseMaterialization.firstBlocker=none")); + assertTrue(evidence.contains("runtimeEquivalenceReview.status=review-ready")); + assertTrue(evidence.contains("runtimeEquivalenceReview.eligible=true")); + assertTrue(evidence.contains("runtimeEquivalenceReview.firstBlocker=none")); + assertTrue(evidence.contains( + "runtimeEquivalenceReview.familySummary=constant-folding=not-recorded, " + + "safe-local-cse=not-recorded, typed-dead-code=not-recorded, " + + "constant-folding-materialization=not-recorded, " + + "safe-local-cse-materialization=review-ready, " + + "mad-fma-materialization=not-recorded, " + + "clamp-materialization=not-recorded, " + + "step-materialization=not-recorded, " + + "mix-materialization=not-recorded, " + + "loop-vectorization-materialization=not-recorded, " + + "typed-dead-code-materialization=not-recorded" + )); + assertTrue(payload.contains("family.0.name=safe-local-cse-materialization")); + assertTrue(payload.contains("family.0.complete=true")); + assertTrue(payload.contains("family.0.pass.0.cpuReference.payload=reuseExistingLocal=1")); + assertTrue(payload.contains("family.0.pass.0.postOptimizationOutput.payload=localRefs=1, first=tmp")); + String familyDirectory = "runtime-optimizer-family-equivalence-payload/family-0-safe-local-cse-materialization/pass-0"; + assertTrue(dump.hasArtifact(familyDirectory + "/cpu-reference.properties")); + assertTrue(dump.artifact(familyDirectory + "/post-optimization-output.properties").contains( + "runtimeEquivalencePayload.Case.0.Output.0.PostOptimization" + )); + } + + @Test + void dumpsMadFmaMaterializationRuntimeEquivalenceReviewEvidence() { + IrGpuArtifact original = artifact("body\n set output[0] = ((a * b) + c)\n"); + GpuBackendModuleArtifact backendArtifact = GpuBackendModuleArtifact.openClSource( + "__kernel void kernel(__global float* out) { out[0] = 1.0f; }", + "runtime/lowered/kernel.cl", + "test-lowerer-v1" + ); + GpuRuntimeCompileRequest request = new GpuRuntimeCompileRequest( + descriptor(), + new GpuRuntimeCompileOptions(GpuBackendTarget.OPENCL, List.of(), "diagnostic"), + GpuRuntimeDeviceProfile.generic(GpuBackendTarget.OPENCL, "OpenCL"), + Optional.of(original) + ); + GpuRuntimeIrOptimizationPassReport materializationReport = new GpuRuntimeIrOptimizationPassReport( + GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, + "javatogpu.ir-optimizer.mad-fma-materialization:1", + GpuRuntimeIrOptimizationOutcome.SKIPPED, + "irgpu:sha256:original", + "irgpu:sha256:mad-fma", + "proposal-only", + "", + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.mad-fma-materialization", + "review-only-mad-fma-materialized", + Map.ofEntries( + Map.entry("candidate.count", "1"), + Map.entry("transformedNode.count", "1"), + Map.entry("changedMethodBody.count", "1"), + Map.entry("bodyTextReplacement.count", "1"), + Map.entry("fixedPoint.pass.count", "1"), + Map.entry("skipped.fastMathPolicy.count", "0"), + Map.entry("skipped.bodyTextPatternMissing.count", "0"), + Map.entry("rewrite.proposed", "true"), + Map.entry("rewrite.materialized", "true"), + Map.entry("optimizerFamily", "mad-fma-materialization"), + Map.entry("targetIntrinsic", "mad"), + Map.entry("policy.fastMathAllowed", "true"), + Map.entry("safety.fastMathAllowed", "true"), + Map.entry("safety.fastMathRequired", "true"), + Map.entry("safety.strictFloatPreserved", "false"), + Map.entry("proof.runtimeEquivalenceRequiredBeforeSelection", "true"), + Map.entry("proof.runtimeEquivalencePayloadRequiredBeforeSelection", "true"), + Map.entry("proof.approvalRequiredBeforeProduction", "true"), + Map.entry("runtimeEquivalencePayload.required", "true"), + Map.entry("runtimeEquivalencePayload.present", "false"), + Map.entry("runtimeEquivalencePayload.passed", "false"), + Map.entry( + "runtimeEquivalencePayload.firstBlocker", + "runtime-equivalence-payload-not-recorded" + ), + Map.entry("optimizedArtifactCandidate.status", "candidate-ready"), + Map.entry("optimizedArtifactCandidate.candidateBuilt", "true"), + Map.entry("optimizedArtifactCandidate.optimizedValidationPassed", "true"), + Map.entry("optimizedArtifactCandidate.proofPresent", "true"), + Map.entry("optimizedArtifactCandidate.rollbackRequired", "true"), + Map.entry("optimizedArtifactCandidate.mutationAllowed", "false"), + Map.entry("optimizedArtifactCandidate.selectionReady", "false"), + Map.entry("optimizedArtifactCandidate.selectionApplied", "false"), + Map.entry("optimizedArtifactCandidate.selectedIrReplacement", "false"), + Map.entry("optimizedArtifactCandidate.firstBlocker", "none"), + Map.entry("optimizedArtifactCandidate.selectionFirstBlocker", "mutation-disabled") + ) + ), + List.of("materialized one mad/fma review candidate") + ); + GpuRuntimeIrOptimizationReport optimizationReport = new GpuRuntimeIrOptimizationReport( + Optional.of(original), + List.of(materializationReport), + GpuOptimizationStrategyDecision.none(request) + ); + GpuRuntimeCompileArtifactSnapshot snapshot = GpuRuntimeCompileArtifactSnapshot.from( + request, + request, + backendArtifact, + GpuRuntimeCompileInvalidationStamp.from(request, backendArtifact, "optimizer:mad-fma-materialization"), + GpuRuntimeCompileProvenance.from(request), + optimizationReport + ); + + String evidence = GpuRuntimeCompileArtifactDumper.dump(snapshot) + .artifact(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + + assertTrue(evidence.contains("madFmaMaterialization.pass.count=1")); + assertTrue(evidence.contains("madFmaMaterialization.candidate.count=1")); + assertTrue(evidence.contains("madFmaMaterialization.transformedNode.count=1")); + assertTrue(evidence.contains("madFmaMaterialization.bodyTextReplacement.count=1")); + assertTrue(evidence.contains("madFmaMaterialization.fixedPoint.pass.count=1")); + assertTrue(evidence.contains("madFmaMaterialization.skipped.fastMathPolicy.count=0")); + assertTrue(evidence.contains("madFmaMaterialization.runtimeEquivalencePayloadRequired=true")); + assertTrue(evidence.contains("madFmaMaterialization.runtimeEquivalencePayloadPresent.count=0")); + assertTrue(evidence.contains("madFmaMaterialization.runtimeEquivalencePassed.count=0")); + assertTrue(evidence.contains("madFmaMaterialization.fastMathAllowed=true")); + assertTrue(evidence.contains("madFmaMaterialization.status=pending-runtime-equivalence")); + assertTrue(evidence.contains("madFmaMaterialization.firstBlocker=runtime-equivalence-payload-not-recorded")); + assertTrue(evidence.contains("runtimeEquivalenceReview.required=true")); + assertTrue(evidence.contains("runtimeEquivalenceReview.eligible=false")); + assertTrue(evidence.contains("runtimeEquivalenceReview.firstBlocker=runtime-equivalence-payload-not-recorded")); + assertTrue(evidence.contains( + "runtimeEquivalenceReview.familySummary=constant-folding=not-recorded, " + + "safe-local-cse=not-recorded, typed-dead-code=not-recorded, " + + "constant-folding-materialization=not-recorded, " + + "safe-local-cse-materialization=not-recorded, " + + "mad-fma-materialization=pending-runtime-equivalence, " + + "clamp-materialization=not-recorded, " + + "step-materialization=not-recorded, " + + "mix-materialization=not-recorded, " + + "loop-vectorization-materialization=not-recorded, " + + "typed-dead-code-materialization=not-recorded" + )); + assertTrue(evidence.contains("reviewPackage.firstBlocker=runtime-equivalence-payload-not-recorded")); + assertTrue(evidence.contains("pass.0.approvalTemplate.status=pending")); + assertTrue(evidence.contains("pass.0.approvalTemplate.field.runtimeEquivalencePayload.present=false")); + } + + @Test + void dumpsMadFmaMaterializationReviewReadyRuntimeEquivalencePayload() { + IrGpuArtifact original = artifact("body\n set output[0] = ((a * b) + c)\n"); + GpuBackendModuleArtifact backendArtifact = GpuBackendModuleArtifact.openClSource( + "__kernel void kernel(__global float* out) { out[0] = 1.0f; }", + "runtime/lowered/kernel.cl", + "test-lowerer-v1" + ); + GpuRuntimeCompileRequest request = new GpuRuntimeCompileRequest( + descriptor(), + new GpuRuntimeCompileOptions(GpuBackendTarget.OPENCL, List.of(), "diagnostic"), + GpuRuntimeDeviceProfile.generic(GpuBackendTarget.OPENCL, "OpenCL"), + Optional.of(original) + ); + GpuRuntimeIrOptimizationPassReport materializationReport = new GpuRuntimeIrOptimizationPassReport( + GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, + "javatogpu.ir-optimizer.mad-fma-materialization:1", + GpuRuntimeIrOptimizationOutcome.SKIPPED, + "irgpu:sha256:original", + "irgpu:sha256:mad-fma", + "proposal-only", + "", + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.mad-fma-materialization", + "materialized-review-candidate", + Map.ofEntries( + Map.entry("candidate.count", "1"), + Map.entry("transformedNode.count", "1"), + Map.entry("changedMethodBody.count", "1"), + Map.entry("bodyTextReplacement.count", "1"), + Map.entry("fixedPoint.pass.count", "1"), + Map.entry("skipped.fastMathPolicy.count", "0"), + Map.entry("skipped.bodyTextPatternMissing.count", "0"), + Map.entry("rewrite.proposed", "true"), + Map.entry("rewrite.materialized", "true"), + Map.entry("optimizerFamily", "mad-fma-materialization"), + Map.entry("targetIntrinsic", "mad"), + Map.entry("policy.fastMathAllowed", "true"), + Map.entry("safety.fastMathAllowed", "true"), + Map.entry("safety.fastMathRequired", "true"), + Map.entry("safety.strictFloatPreserved", "false"), + Map.entry("proof.runtimeEquivalenceRequiredBeforeSelection", "true"), + Map.entry("proof.runtimeEquivalencePayloadRequiredBeforeSelection", "true"), + Map.entry("proof.approvalRequiredBeforeProduction", "true"), + Map.entry("runtimeEquivalencePayload.status", "recorded"), + Map.entry("runtimeEquivalencePayload.required", "true"), + Map.entry("runtimeEquivalencePayload.present", "true"), + Map.entry("runtimeEquivalencePayload.passed", "true"), + Map.entry("runtimeEquivalencePayload.firstBlocker", "none"), + Map.entry("runtimeEquivalencePayload.cpuReference.present", "true"), + Map.entry("runtimeEquivalencePayload.preOptimizationOutput.present", "true"), + Map.entry("runtimeEquivalencePayload.postOptimizationOutput.present", "true"), + Map.entry("runtimeEquivalencePayload.tolerance.present", "true"), + Map.entry("runtimeEquivalencePayload.failureFixture.present", "true"), + Map.entry( + "runtimeEquivalencePayload.resource", + "ir-optimizer://mad-fma-materialization/review-candidate" + ), + Map.entry( + "runtimeEquivalencePayload.comparisonMode", + "optimizer-family:mad-fma-materialization:review-candidate" + ), + Map.entry("runtimeEquivalencePayload.CpuReference", "inputs=a,b,c, mode=fast-math-mad"), + Map.entry("runtimeEquivalencePayload.PreOptimizationOutput", "expr=((a * b) + c)"), + Map.entry("runtimeEquivalencePayload.PostOptimizationOutput", "expr=mad(a, b, c)"), + Map.entry("runtimeEquivalencePayload.Tolerance", "mode=fast-math-opencl-mad"), + Map.entry("runtimeEquivalencePayload.FailureFixture", "none"), + Map.entry("runtimeEquivalencePayload.Case.Count", "1"), + Map.entry("runtimeEquivalencePayload.Case.0.Name", "kernel#7=a*b+c->mad"), + Map.entry("runtimeEquivalencePayload.Case.0.MethodName", "kernel"), + Map.entry("runtimeEquivalencePayload.Case.0.NodeId", "7"), + Map.entry("runtimeEquivalencePayload.Case.0.RewriteKind", "mad-fma-to-opencl-mad"), + Map.entry("runtimeEquivalencePayload.Case.0.Successful", "true"), + Map.entry("runtimeEquivalencePayload.Case.0.Output.0.CpuReference", "a*b+c"), + Map.entry("runtimeEquivalencePayload.Case.0.Output.0.PreOptimization", "((a * b) + c)"), + Map.entry("runtimeEquivalencePayload.Case.0.Output.0.PostOptimization", "mad(a, b, c)"), + Map.entry("runtimeEquivalencePayload.Case.0.Output.0.Tolerance", "fast-math-opencl-mad"), + Map.entry("runtimeEquivalencePayload.Case.0.Output.0.Equivalent", "true"), + Map.entry("firstBlocker", "none") + ) + ), + List.of("materialized mad/fma review candidate") + ); + GpuRuntimeIrOptimizationReport optimizationReport = new GpuRuntimeIrOptimizationReport( + Optional.of(original), + List.of(materializationReport), + GpuOptimizationStrategyDecision.none(request) + ); + GpuRuntimeCompileArtifactSnapshot snapshot = GpuRuntimeCompileArtifactSnapshot.from( + request, + request, + backendArtifact, + GpuRuntimeCompileInvalidationStamp.from(request, backendArtifact, "optimizer:mad-fma-materialization"), + GpuRuntimeCompileProvenance.from(request), + optimizationReport, + GpuRuntimeEquivalenceEvidence.passed(request, 1, 1, List.of("mad/fma payload matched")) + ); + + GpuRuntimeCompileArtifactDump dump = GpuRuntimeCompileArtifactDumper.dump(snapshot); + String evidence = dump.artifact(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + String payload = dump.artifact(GpuPromotionArtifactRegistry.RUNTIME_OPTIMIZER_FAMILY_EQUIVALENCE_PAYLOAD); + + assertTrue(evidence.contains("madFmaMaterialization.runtimeEquivalencePayloadPresent.count=1")); + assertTrue(evidence.contains("madFmaMaterialization.runtimeEquivalencePassed.count=1")); + assertTrue(evidence.contains("madFmaMaterialization.fastMathAllowed=true")); + assertTrue(evidence.contains("madFmaMaterialization.status=review-ready")); + assertTrue(evidence.contains("madFmaMaterialization.firstBlocker=none")); + assertTrue(evidence.contains("runtimeEquivalenceReview.status=review-ready")); + assertTrue(evidence.contains("runtimeEquivalenceReview.eligible=true")); + assertTrue(evidence.contains("runtimeEquivalenceReview.firstBlocker=none")); + assertTrue(evidence.contains( + "runtimeEquivalenceReview.familySummary=constant-folding=not-recorded, " + + "safe-local-cse=not-recorded, typed-dead-code=not-recorded, " + + "constant-folding-materialization=not-recorded, " + + "safe-local-cse-materialization=not-recorded, " + + "mad-fma-materialization=review-ready, " + + "clamp-materialization=not-recorded, " + + "step-materialization=not-recorded, " + + "mix-materialization=not-recorded, " + + "loop-vectorization-materialization=not-recorded, " + + "typed-dead-code-materialization=not-recorded" + )); + assertTrue(payload.contains("family.0.name=mad-fma-materialization")); + assertTrue(payload.contains("family.0.complete=true")); + assertTrue(payload.contains("family.0.pass.0.postOptimizationOutput.payload=expr=mad(a, b, c)")); + String familyDirectory = "runtime-optimizer-family-equivalence-payload/family-0-mad-fma-materialization/pass-0"; + assertTrue(dump.hasArtifact(familyDirectory + "/cpu-reference.properties")); + assertTrue(dump.artifact(familyDirectory + "/post-optimization-output.properties").contains( + "runtimeEquivalencePayload.Case.0.Output.0.PostOptimization" + )); + } + + @Test + void dumpsIntrinsicMaterializationRuntimeEquivalenceReviewEvidence() { + IrGpuArtifact original = artifact("body\n return intrinsic candidates\n"); + GpuBackendModuleArtifact backendArtifact = GpuBackendModuleArtifact.openClSource( + "__kernel void kernel(__global float* out) { out[0] = 1.0f; }", + "runtime/lowered/kernel.cl", + "test-lowerer-v1" + ); + GpuRuntimeCompileRequest request = new GpuRuntimeCompileRequest( + descriptor(), + new GpuRuntimeCompileOptions(GpuBackendTarget.OPENCL, List.of(), "diagnostic"), + GpuRuntimeDeviceProfile.generic(GpuBackendTarget.OPENCL, "OpenCL"), + Optional.of(original) + ); + GpuRuntimeIrOptimizationReport optimizationReport = new GpuRuntimeIrOptimizationReport( + Optional.of(original), + List.of( + intrinsicMaterializationReport( + "clamp-materialization", + "javatogpu.ir-optimizer.clamp-materialization:1", + Map.ofEntries( + Map.entry("candidate.count", "1"), + Map.entry("transformedNode.count", "1"), + Map.entry("changedMethodBody.count", "1"), + Map.entry("bodyTextReplacement.count", "1"), + Map.entry("fixedPoint.pass.count", "1"), + Map.entry("safety.fastMathRequired", "false"), + Map.entry("safety.strictFloatPreserved", "true"), + Map.entry("safety.argumentOrderPreserved", "true") + ) + ), + intrinsicMaterializationReport( + "step-materialization", + "javatogpu.ir-optimizer.step-materialization:1", + Map.ofEntries( + Map.entry("candidate.count", "2"), + Map.entry("transformedNode.count", "2"), + Map.entry("changedMethodBody.count", "1"), + Map.entry("bodyTextReplacement.count", "2"), + Map.entry("fixedPoint.pass.count", "2"), + Map.entry("directStep.count", "1"), + Map.entry("invertedStep.count", "1"), + Map.entry("safety.fastMathRequired", "false"), + Map.entry("safety.strictFloatPreserved", "true"), + Map.entry("safety.strictComparisonPreserved", "true"), + Map.entry("safety.equalityBehaviorPreserved", "true"), + Map.entry("safety.nanComparisonPreserved", "true") + ) + ), + intrinsicMaterializationReport( + "mix-materialization", + "javatogpu.ir-optimizer.mix-materialization:1", + Map.ofEntries( + Map.entry("candidate.count", "3"), + Map.entry("transformedNode.count", "3"), + Map.entry("changedMethodBody.count", "1"), + Map.entry("bodyTextReplacement.count", "3"), + Map.entry("fixedPoint.pass.count", "3"), + Map.entry("canonicalMix.count", "1"), + Map.entry("expandedMix.count", "1"), + Map.entry("madExpandedMix.count", "1"), + Map.entry("policy.fastMathAllowed", "true"), + Map.entry("safety.fastMathRequired", "true"), + Map.entry("safety.strictFloatPreserved", "false"), + Map.entry("safety.algebraicReassociationRequired", "true"), + Map.entry("safety.mixArgumentOrderPreserved", "true") + ) + ) + ), + GpuOptimizationStrategyDecision.none(request) + ); + GpuRuntimeCompileArtifactSnapshot snapshot = GpuRuntimeCompileArtifactSnapshot.from( + request, + request, + backendArtifact, + GpuRuntimeCompileInvalidationStamp.from(request, backendArtifact, "optimizer:intrinsic-materialization"), + GpuRuntimeCompileProvenance.from(request), + optimizationReport + ); + + String evidence = GpuRuntimeCompileArtifactDumper.dump(snapshot) + .artifact(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + + assertTrue(evidence.contains("clampMaterialization.pass.count=1")); + assertTrue(evidence.contains("clampMaterialization.transformedNode.count=1")); + assertTrue(evidence.contains("clampMaterialization.strictFloatPreserved=true")); + assertTrue(evidence.contains("clampMaterialization.argumentOrderPreserved=true")); + assertTrue(evidence.contains("clampMaterialization.fastMathRequired=false")); + assertTrue(evidence.contains("clampMaterialization.status=review-ready")); + assertTrue(evidence.contains("stepMaterialization.pass.count=1")); + assertTrue(evidence.contains("stepMaterialization.transformedNode.count=2")); + assertTrue(evidence.contains("stepMaterialization.directStep.count=1")); + assertTrue(evidence.contains("stepMaterialization.invertedStep.count=1")); + assertTrue(evidence.contains("stepMaterialization.strictComparisonPreserved=true")); + assertTrue(evidence.contains("stepMaterialization.equalityBehaviorPreserved=true")); + assertTrue(evidence.contains("stepMaterialization.nanComparisonPreserved=true")); + assertTrue(evidence.contains("stepMaterialization.status=review-ready")); + assertTrue(evidence.contains("mixMaterialization.pass.count=1")); + assertTrue(evidence.contains("mixMaterialization.transformedNode.count=3")); + assertTrue(evidence.contains("mixMaterialization.canonicalMix.count=1")); + assertTrue(evidence.contains("mixMaterialization.expandedMix.count=1")); + assertTrue(evidence.contains("mixMaterialization.madExpandedMix.count=1")); + assertTrue(evidence.contains("mixMaterialization.fastMathAllowed=true")); + assertTrue(evidence.contains("mixMaterialization.fastMathRequired=true")); + assertTrue(evidence.contains("mixMaterialization.algebraicReassociationRequired=true")); + assertTrue(evidence.contains("mixMaterialization.status=review-ready")); + assertTrue(evidence.contains("runtimeEquivalenceReview.status=review-ready")); + assertTrue(evidence.contains("runtimeEquivalenceReview.eligible=true")); + assertTrue(evidence.contains("runtimeEquivalenceReview.firstBlocker=none")); + assertTrue(evidence.contains( + "runtimeEquivalenceReview.familySummary=constant-folding=not-recorded, " + + "safe-local-cse=not-recorded, typed-dead-code=not-recorded, " + + "constant-folding-materialization=not-recorded, " + + "safe-local-cse-materialization=not-recorded, " + + "mad-fma-materialization=not-recorded, " + + "clamp-materialization=review-ready, " + + "step-materialization=review-ready, " + + "mix-materialization=review-ready, " + + "loop-vectorization-materialization=not-recorded, " + + "typed-dead-code-materialization=not-recorded" + )); + } + + @Test + void dumpsTypedDeadCodeMaterializationReviewReadyRuntimeEquivalencePayload() { + IrGpuArtifact original = artifact("body\n return live\n"); + GpuBackendModuleArtifact backendArtifact = GpuBackendModuleArtifact.openClSource( + "__kernel void kernel(__global int* out) { out[0] = 1; }", + "runtime/lowered/kernel.cl", + "test-lowerer-v1" + ); + GpuRuntimeCompileRequest request = new GpuRuntimeCompileRequest( + descriptor(), + new GpuRuntimeCompileOptions(GpuBackendTarget.OPENCL, List.of(), "diagnostic"), + GpuRuntimeDeviceProfile.generic(GpuBackendTarget.OPENCL, "OpenCL"), + Optional.of(original) + ); + GpuRuntimeIrOptimizationPassReport materializationReport = new GpuRuntimeIrOptimizationPassReport( + GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, + "javatogpu.ir-optimizer.typed-dead-code-materialization:1", + GpuRuntimeIrOptimizationOutcome.SKIPPED, + "irgpu:sha256:original", + "irgpu:sha256:typed-dead-code", + "proposal-only", + "", + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.typed-dead-code-materialization", + "materialized-review-candidate", + Map.ofEntries( + Map.entry("node.count", "5"), + Map.entry("reachableNode.count", "2"), + Map.entry("unreachableNode.count", "3"), + Map.entry("removedNode.count", "3"), + Map.entry("changedMethodBody.count", "1"), + Map.entry("blocked.missingRoot.count", "0"), + Map.entry("blocked.missingChildReference.count", "0"), + Map.entry("blocked.sideEffectingUnreachableNode.count", "0"), + Map.entry("rewrite.proposed", "true"), + Map.entry("rewrite.materialized", "true"), + Map.entry("optimizerFamily", "typed-dead-code-materialization"), + Map.entry("proof.runtimeEquivalenceRequiredBeforeSelection", "true"), + Map.entry("proof.runtimeEquivalencePayloadRequiredBeforeSelection", "true"), + Map.entry("proof.approvalRequiredBeforeProduction", "true"), + Map.entry("runtimeEquivalencePayload.status", "recorded"), + Map.entry("runtimeEquivalencePayload.required", "true"), + Map.entry("runtimeEquivalencePayload.present", "true"), + Map.entry("runtimeEquivalencePayload.passed", "true"), + Map.entry("runtimeEquivalencePayload.firstBlocker", "none"), + Map.entry("runtimeEquivalencePayload.cpuReference.present", "true"), + Map.entry("runtimeEquivalencePayload.preOptimizationOutput.present", "true"), + Map.entry("runtimeEquivalencePayload.postOptimizationOutput.present", "true"), + Map.entry("runtimeEquivalencePayload.tolerance.present", "true"), + Map.entry("runtimeEquivalencePayload.failureFixture.present", "true"), + Map.entry( + "runtimeEquivalencePayload.resource", + "ir-optimizer://typed-dead-code-materialization/review-candidate" + ), + Map.entry( + "runtimeEquivalencePayload.comparisonMode", + "typed-structure-unreachable-pure-node-removal" + ), + Map.entry("runtimeEquivalencePayload.CpuReference", "removedUnreachablePureNodes=3"), + Map.entry("runtimeEquivalencePayload.PreOptimizationOutput", "typedNodes=5"), + Map.entry("runtimeEquivalencePayload.PostOptimizationOutput", "typedNodes=2"), + Map.entry("runtimeEquivalencePayload.Tolerance", "mode=typed-structure-no-runtime-output-change"), + Map.entry("runtimeEquivalencePayload.FailureFixture", "none"), + Map.entry("runtimeEquivalencePayload.Case.Count", "1"), + Map.entry("runtimeEquivalencePayload.Case.0.Name", "kernel#2->removed"), + Map.entry("runtimeEquivalencePayload.Case.0.MethodName", "kernel"), + Map.entry("runtimeEquivalencePayload.Case.0.NodeId", "2"), + Map.entry("runtimeEquivalencePayload.Case.0.RewriteKind", "typed-dead-code-removal"), + Map.entry("runtimeEquivalencePayload.Case.0.Successful", "true"), + Map.entry("runtimeEquivalencePayload.Case.0.Output.0.CpuReference", "removed-unreachable-pure-node"), + Map.entry("runtimeEquivalencePayload.Case.0.Output.0.PreOptimization", "present"), + Map.entry("runtimeEquivalencePayload.Case.0.Output.0.PostOptimization", "removed"), + Map.entry("runtimeEquivalencePayload.Case.0.Output.0.Tolerance", "typed-structure"), + Map.entry("runtimeEquivalencePayload.Case.0.Output.0.Equivalent", "true"), + Map.entry("safety.sideEffectFreedomProven", "true"), + Map.entry("firstBlocker", "none") + ) + ), + List.of("materialized typed dead-code removal review candidate") + ); + GpuRuntimeIrOptimizationReport optimizationReport = new GpuRuntimeIrOptimizationReport( + Optional.of(original), + List.of(materializationReport), + GpuOptimizationStrategyDecision.none(request) + ); + GpuRuntimeCompileArtifactSnapshot snapshot = GpuRuntimeCompileArtifactSnapshot.from( + request, + request, + backendArtifact, + GpuRuntimeCompileInvalidationStamp.from(request, backendArtifact, "optimizer:tdc-materialization"), + GpuRuntimeCompileProvenance.from(request), + optimizationReport, + GpuRuntimeEquivalenceEvidence.passed(request, 1, 1, List.of("typed dead-code payload matched")) + ); + + GpuRuntimeCompileArtifactDump dump = GpuRuntimeCompileArtifactDumper.dump(snapshot); + String evidence = dump.artifact(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + String payload = dump.artifact(GpuPromotionArtifactRegistry.RUNTIME_OPTIMIZER_FAMILY_EQUIVALENCE_PAYLOAD); + + assertTrue(evidence.contains("typedDeadCodeMaterialization.pass.count=1")); + assertTrue(evidence.contains("typedDeadCodeMaterialization.node.count=5")); + assertTrue(evidence.contains("typedDeadCodeMaterialization.unreachableNode.count=3")); + assertTrue(evidence.contains("typedDeadCodeMaterialization.removedNode.count=3")); + assertTrue(evidence.contains("typedDeadCodeMaterialization.changedMethodBody.count=1")); + assertTrue(evidence.contains("typedDeadCodeMaterialization.runtimeEquivalenceRequiredBeforeSelection=true")); + assertTrue(evidence.contains("typedDeadCodeMaterialization.runtimeEquivalencePayloadRequired=true")); + assertTrue(evidence.contains("typedDeadCodeMaterialization.runtimeEquivalencePayloadPresent.count=1")); + assertTrue(evidence.contains("typedDeadCodeMaterialization.runtimeEquivalencePassed.count=1")); + assertTrue(evidence.contains("typedDeadCodeMaterialization.sideEffectFreedomProven=true")); + assertTrue(evidence.contains("typedDeadCodeMaterialization.status=review-ready")); + assertTrue(evidence.contains("typedDeadCodeMaterialization.firstBlocker=none")); + assertTrue(evidence.contains("runtimeEquivalenceReview.status=review-ready")); + assertTrue(evidence.contains("runtimeEquivalenceReview.eligible=true")); + assertTrue(evidence.contains("runtimeEquivalenceReview.firstBlocker=none")); + assertTrue(evidence.contains( + "runtimeEquivalenceReview.familySummary=constant-folding=not-recorded, " + + "safe-local-cse=not-recorded, typed-dead-code=not-recorded, " + + "constant-folding-materialization=not-recorded, " + + "safe-local-cse-materialization=not-recorded, " + + "mad-fma-materialization=not-recorded, " + + "clamp-materialization=not-recorded, " + + "step-materialization=not-recorded, " + + "mix-materialization=not-recorded, " + + "loop-vectorization-materialization=not-recorded, " + + "typed-dead-code-materialization=review-ready" + )); + assertTrue(evidence.contains("reviewPackage.status=pending-manual-review")); + assertTrue(evidence.contains("reviewPackage.firstBlocker=approval-template-pending")); + assertTrue(evidence.contains("pass.0.approvalTemplate.status=pending")); + assertTrue(evidence.contains("pass.0.approvalTemplate.field.runtimeEquivalencePayload.required=true")); + assertTrue(evidence.contains("pass.0.approvalTemplate.field.runtimeEquivalencePayload.present=true")); + assertTrue(evidence.contains("pass.0.approvalTemplate.field.runtimeEquivalencePayload.passed=true")); + assertTrue(evidence.contains("pass.0.approvalTemplate.field.runtimeEquivalencePayload.componentsComplete=true")); + assertTrue(evidence.contains("pass.0.approvalTemplate.field.runtimeEquivalencePayload.caseCount=1")); + assertTrue(payload.contains("family.0.name=typed-dead-code-materialization")); + assertTrue(payload.contains("family.0.complete=true")); + assertTrue(payload.contains("family.0.pass.0.cpuReference.payload=removedUnreachablePureNodes=3")); + assertTrue(payload.contains("family.0.pass.0.postOptimizationOutput.payload=typedNodes=2")); + String familyDirectory = "runtime-optimizer-family-equivalence-payload/family-0-typed-dead-code-materialization/pass-0"; + assertTrue(dump.hasArtifact(familyDirectory + "/cpu-reference.properties")); + assertTrue(dump.artifact(familyDirectory + "/post-optimization-output.properties").contains( + "runtimeEquivalencePayload.Case.0.Output.0.PostOptimization" + )); + assertTrue(dump.artifact(familyDirectory + "/tolerance.properties").contains( + "runtimeEquivalencePayload.Case.0.Output.0.Tolerance" + )); + } + private static GpuKernelDescriptor descriptor() { return new GpuKernelDescriptor( "kernel", @@ -2767,6 +3830,52 @@ private static GpuKernelDescriptor descriptor() { ); } + private static GpuRuntimeIrOptimizationPassReport intrinsicMaterializationReport( + String family, + String version, + Map overrides + ) { + Map fields = new java.util.LinkedHashMap<>(Map.ofEntries( + Map.entry("methodBody.count", "1"), + Map.entry("methodBody.rewriteScope", "all-method-bodies"), + Map.entry("typedBody.count", "1"), + Map.entry("skipped.typedBodyMissing.count", "0"), + Map.entry("skipped.unsupportedFormat.count", "0"), + Map.entry("skipped.fastMathPolicy.count", "0"), + Map.entry("skipped.missingChildReference.count", "0"), + Map.entry("skipped.unsupportedShape.count", "0"), + Map.entry("skipped.bodyTextPatternMissing.count", "0"), + Map.entry("rewrite.proposed", "true"), + Map.entry("rewrite.materialized", "true"), + Map.entry("optimizerFamily", family), + Map.entry("proof.runtimeEquivalenceRequiredBeforeSelection", "true"), + Map.entry("proof.runtimeEquivalencePayloadRequiredBeforeSelection", "true"), + Map.entry("proof.approvalRequiredBeforeProduction", "true"), + Map.entry("runtimeEquivalencePayload.required", "true"), + Map.entry("runtimeEquivalencePayload.status", "recorded"), + Map.entry("runtimeEquivalencePayload.present", "true"), + Map.entry("runtimeEquivalencePayload.passed", "true"), + Map.entry("runtimeEquivalencePayload.firstBlocker", "none"), + Map.entry("firstBlocker", "none") + )); + fields.putAll(overrides); + return new GpuRuntimeIrOptimizationPassReport( + GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, + version, + GpuRuntimeIrOptimizationOutcome.SKIPPED, + "irgpu:sha256:original", + "irgpu:sha256:" + family, + "proposal-only", + "", + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer." + family, + "materialized-review-candidate", + fields + ), + List.of("materialized " + family + " review candidate") + ); + } + private static GpuExtensionExecutionReport execution( String extensionId, GpuExtensionPhase phase, diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrSelectionTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrSelectionTest.java index 28c5c99c..987a4e6a 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrSelectionTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrSelectionTest.java @@ -2,6 +2,7 @@ import net.sixik.ga_utils.javatogpu.api.GpuBackendTarget; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactIdentity; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactHeader; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuBackendOutput; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuMethodBody; @@ -35,6 +36,41 @@ void selectsOptimizedArtifactWhenNoFallbackOrRollbackExists() { assertTrue(selection.diagnostic().contains("optimized IrGpu is selected")); } + @Test + void materializedCandidateCanRemainReviewOnlyWhenReportSelectsOriginal() { + IrGpuArtifact original = artifact("body\n return original\n"); + IrGpuArtifact candidate = artifact("body\n return optimized candidate\n"); + GpuRuntimeIrOptimizationPassReport passReport = new GpuRuntimeIrOptimizationPassReport( + GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, + "optimizer:proposal-only", + GpuRuntimeIrOptimizationOutcome.SKIPPED, + IrGpuArtifactIdentity.stableIdentity(original), + IrGpuArtifactIdentity.stableIdentity(candidate), + "proposal-only", + "", + GpuRuntimeIrOptimizationProofArtifact.fromFields("test", "candidate-ready", java.util.Map.of()), + List.of("candidate materialized for review only") + ); + GpuRuntimeCompileArtifactSnapshot snapshot = snapshot( + original, + candidate, + new GpuRuntimeIrOptimizationReport( + Optional.of(original), + Optional.of(candidate), + List.of(passReport) + ) + ); + + GpuRuntimeIrSelection selection = GpuRuntimeIrSelection.from(snapshot); + + assertEquals("original", selection.selectedStage()); + assertEquals(IrGpuArtifactIdentity.stableIdentity(original), selection.selectedIdentity()); + assertEquals(IrGpuArtifactIdentity.stableIdentity(candidate), selection.optimizedIdentity()); + assertTrue(selection.transformed()); + assertFalse(selection.optimizedRejected()); + assertTrue(selection.diagnostic().contains("candidate is materialized for review")); + } + @Test void selectsOriginalArtifactWhenRuntimeEquivalenceFailed() { IrGpuArtifact original = artifact("body\n return original\n"); diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrTypedNodeGraphTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrTypedNodeGraphTest.java new file mode 100644 index 00000000..44be6ed1 --- /dev/null +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrTypedNodeGraphTest.java @@ -0,0 +1,37 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuRuntimeIrTypedNodeGraphTest { + + @Test + void callNameRecognizesSerializedIntrinsicBackendName() { + IrGpuTypedNode intrinsic = new IrGpuTypedNode( + 0, + "GpuIrIntrinsicCall", + Map.of("backendName", "min"), + Map.of("arguments", List.of(1, 2)) + ); + GpuRuntimeIrTypedNodeGraph graph = GpuRuntimeIrTypedNodeGraph.from(new IrGpuTypedBody( + IrGpuTypedBody.FORMAT, + List.of(0), + List.of( + intrinsic, + new IrGpuTypedNode(1, "GpuIrVariableRef", Map.of("name", "x"), Map.of()), + new IrGpuTypedNode(2, "GpuIrVariableRef", Map.of("name", "y"), Map.of()) + ) + )); + + assertEquals("min", graph.callName(intrinsic)); + assertTrue(graph.isCall(intrinsic, "min")); + assertEquals(List.of(1, 2), graph.callArguments(intrinsic)); + } +} diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackendTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackendTest.java index bf51eb0a..1eb2d212 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackendTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackendTest.java @@ -214,6 +214,116 @@ void runtimeCompileArtifactDirectoryPropertyDumpsOriginalAndOptimizedIrArtifacts } } + @Test + void runtimeCompileArtifactDirectoryDumpsMaterializedCandidateWithoutSelectingIt() throws Exception { + Path artifactRoot = Files.createTempDirectory("javatogpu-runtime-ir-candidate-dump"); + Path classpathRoot = Files.createTempDirectory("javatogpu-runtime-ir-candidate-classpath"); + Path artifactPath = classpathRoot.resolve("javatogpu/sample/Demo/kernel.irgpu.properties"); + Files.createDirectories(artifactPath.getParent()); + Files.writeString(artifactPath, irGpuArtifactProperties("return original")); + String property = "javatogpu.opencl.runtimeCompileArtifactDirectory"; + String previousArtifactRoot = System.getProperty(property); + AtomicReference capturedSnapshot = new AtomicReference<>(); + AtomicReference finalCompileRequest = new AtomicReference<>(); + try (URLClassLoader classLoader = new URLClassLoader( + new URL[]{classpathRoot.toUri().toURL()}, + OpenClGpuRuntimeBackendTest.class.getClassLoader() + )) { + System.setProperty(property, artifactRoot.toString()); + OpenClGpuRuntimeBackend backend = new SnapshotCapturingBackend(capturedSnapshot) { + @Override + protected GpuRuntimeIrOptimizationResult optimizeRuntimeIrWithReport(GpuRuntimeCompileRequest compileRequest) { + IrGpuArtifact originalArtifact = compileRequest.irGpuArtifact().orElseThrow(); + IrGpuArtifact candidateArtifact = testIrGpuArtifact("body\n return optimized candidate\n"); + String originalIdentity = IrGpuArtifactIdentity.stableIdentity(originalArtifact); + String candidateIdentity = IrGpuArtifactIdentity.stableIdentity(candidateArtifact); + GpuRuntimeIrOptimizationPassReport passReport = new GpuRuntimeIrOptimizationPassReport( + net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, + "optimizer:proposal-only-candidate", + net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationOutcome.SKIPPED, + originalIdentity, + candidateIdentity, + "proposal-only", + "", + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "test.proposalOnly", + "candidate-ready", + java.util.Map.of("optimizedArtifactCandidate.status", "candidate-ready") + ), + List.of("candidate materialized but production mutation remains disabled") + ); + return new GpuRuntimeIrOptimizationResult( + compileRequest.withIrGpuArtifact(Optional.of(candidateArtifact)), + new GpuRuntimeIrOptimizationReport( + Optional.of(originalArtifact), + Optional.of(candidateArtifact), + List.of(passReport) + ) + ); + } + + @Override + protected GpuBackendModuleArtifact lowerBackendModule(GpuRuntimeCompileRequest compileRequest) { + String body = compileRequest.irGpuArtifact() + .map(artifact -> artifact.module().methodBodies().get(0).body()) + .orElse(""); + boolean optimized = body.contains("optimized candidate"); + return GpuBackendModuleArtifact.openClSource( + optimized + ? "__kernel void kernel(__global int* output) { output[0] = 2; }" + : "__kernel void kernel(__global int* output) { output[0] = 1; }", + optimized + ? "runtime/lowered/kernel-optimized.cl" + : "runtime/lowered/kernel-original.cl", + "test-lowerer-v1" + ); + } + + @Override + protected OpenClCompiledKernel compileKernel( + GpuRuntimeCompileRequest compileRequest, + GpuBackendModuleArtifact moduleArtifact + ) { + finalCompileRequest.set(compileRequest); + return super.compileKernel(compileRequest, moduleArtifact); + } + }; + + backend.invoke(new GpuKernelInvocation( + descriptorWithIrGpuResource(), + new Object[]{new int[]{0}} + ).withArtifactClassLoader(classLoader)); + + GpuRuntimeCompileArtifactSnapshot snapshot = capturedSnapshot.get(); + String originalIdentity = IrGpuArtifactIdentity.stableIdentity(snapshot.originalIrGpuArtifact()); + String optimizedIdentity = IrGpuArtifactIdentity.stableIdentity(snapshot.optimizedIrGpuArtifact()); + assertNotEquals(originalIdentity, optimizedIdentity); + assertEquals(originalIdentity, IrGpuArtifactIdentity.stableIdentity(finalCompileRequest.get().irGpuArtifact())); + assertEquals("original", snapshot.runtimeIrSelection().selectedStage()); + assertTrue(snapshot.runtimeIrSelection().transformed()); + assertFalse(snapshot.runtimeIrSelection().optimizedRejected()); + assertTrue(snapshot.originalBackendModuleArtifact().orElseThrow().source().contains("output[0] = 1")); + assertTrue(snapshot.optimizedBackendModuleArtifact().orElseThrow().source().contains("output[0] = 2")); + assertTrue(snapshot.backendModuleArtifact().source().contains("output[0] = 1")); + + Path artifactDirectory; + try (java.util.stream.Stream directories = Files.list(artifactRoot)) { + artifactDirectory = directories.filter(Files::isDirectory).findFirst().orElseThrow(); + } + assertTrue(Files.readString(artifactDirectory.resolve("original.backend.opencl-c")).contains("output[0] = 1")); + assertTrue(Files.readString(artifactDirectory.resolve("optimized.backend.opencl-c")).contains("output[0] = 2")); + assertTrue(Files.readString(artifactDirectory.resolve("backend.opencl-c")).contains("output[0] = 1")); + assertTrue(Files.readString(artifactDirectory.resolve("runtime-ir-handoff.properties")).contains("selectedStage=original")); + assertTrue(Files.readString(artifactDirectory.resolve("runtime-ir-handoff.properties")).contains("optimizedDiffersFromOriginal=true")); + } finally { + if (previousArtifactRoot == null) { + System.clearProperty(property); + } else { + System.setProperty(property, previousArtifactRoot); + } + } + } + @Test void writesOptimizerFamilyPayloadFixtureArtifacts() throws Exception { String outputDirectory = System.getProperty(OPTIMIZER_FAMILY_PAYLOAD_FIXTURE_DIRECTORY_PROPERTY); diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java index 48aa3ddb..b7ff743b 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java @@ -42,6 +42,10 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { rolledBack.count=1 approvalTemplate.pending.count=1 approvalTemplate.notApplicable.count=2 + approvalTemplate.runtimeEquivalencePayloadRequired.count=1 + approvalTemplate.runtimeEquivalencePayloadPresent.count=0 + approvalTemplate.runtimeEquivalencePayloadPassed.count=0 + approvalTemplate.runtimeEquivalencePayloadComplete.count=0 optimizedArtifactCandidate.status=candidate-ready optimizedArtifactCandidate.count=1 optimizedArtifactCandidate.ready.count=1 @@ -54,6 +58,13 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { optimizedArtifactCandidate.selectionFirstBlocker=mutation-disabled optimizedArtifactCandidate.selectionApplied=false optimizedArtifactCandidate.selectedIrReplacement=false + backendNeutralSourceMaterialization.pass.count=1 + backendNeutralSourceMaterialization.candidate.count=1 + backendNeutralSourceMaterialization.sourceReady.count=1 + backendNeutralSourceMaterialization.sourceLength.total=321 + backendNeutralSourceMaterialization.materializationOnly.count=1 + backendNeutralSourceMaterialization.status=review-ready + backendNeutralSourceMaterialization.firstBlocker=none constantFoldingPreview.pass.count=1 constantFoldingPreview.candidate.count=3 constantFoldingPreview.skipped.nonPlainLiteral.count=1 @@ -65,6 +76,23 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { constantFoldingPreview.approvalRequiredBeforeRewrite=true constantFoldingPreview.integerOverflowProven=false constantFoldingPreview.floatingPointRoundingProven=false + constantFoldingMaterialization.pass.count=1 + constantFoldingMaterialization.candidate.count=1 + constantFoldingMaterialization.transformedNode.count=1 + constantFoldingMaterialization.literalRewrite.count=1 + constantFoldingMaterialization.identityRewrite.count=0 + constantFoldingMaterialization.fixedPointPass.count=2 + constantFoldingMaterialization.changedMethodBody.count=1 + constantFoldingMaterialization.bodyTextReplacement.count=1 + constantFoldingMaterialization.skipped.divideByZero.count=1 + constantFoldingMaterialization.skipped.nonEvenDivision.count=2 + constantFoldingMaterialization.runtimeEquivalenceRequiredBeforeSelection=true + constantFoldingMaterialization.runtimeEquivalencePayloadRequired=true + constantFoldingMaterialization.runtimeEquivalencePayloadPresent.count=0 + constantFoldingMaterialization.runtimeEquivalencePassed.count=0 + constantFoldingMaterialization.approvalRequiredBeforeProduction=true + constantFoldingMaterialization.status=pending-runtime-equivalence + constantFoldingMaterialization.firstBlocker=runtime-equivalence-payload-not-recorded safeLocalCsePreview.pass.count=1 safeLocalCsePreview.expression.count=6 safeLocalCsePreview.candidateExpression.count=4 @@ -77,6 +105,97 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { safeLocalCsePreview.approvalRequiredBeforeRewrite=true safeLocalCsePreview.dominanceProven=false safeLocalCsePreview.sideEffectFreedomProven=false + madFmaMaterialization.pass.count=1 + madFmaMaterialization.candidate.count=2 + madFmaMaterialization.transformedNode.count=2 + madFmaMaterialization.changedMethodBody.count=1 + madFmaMaterialization.bodyTextReplacement.count=2 + madFmaMaterialization.fixedPoint.pass.count=1 + madFmaMaterialization.skipped.fastMathPolicy.count=1 + madFmaMaterialization.skipped.bodyTextPatternMissing.count=0 + madFmaMaterialization.runtimeEquivalenceRequiredBeforeSelection=true + madFmaMaterialization.runtimeEquivalencePayloadRequired=true + madFmaMaterialization.runtimeEquivalencePayloadPresent.count=1 + madFmaMaterialization.runtimeEquivalencePassed.count=1 + madFmaMaterialization.approvalRequiredBeforeProduction=true + madFmaMaterialization.fastMathAllowed=true + madFmaMaterialization.status=review-ready + madFmaMaterialization.firstBlocker=none + clampMaterialization.pass.count=1 + clampMaterialization.candidate.count=1 + clampMaterialization.transformedNode.count=1 + clampMaterialization.changedMethodBody.count=1 + clampMaterialization.bodyTextReplacement.count=1 + clampMaterialization.fixedPoint.pass.count=1 + clampMaterialization.skipped.typedBodyMissing.count=0 + clampMaterialization.skipped.unsupportedFormat.count=0 + clampMaterialization.skipped.fastMathPolicy.count=0 + clampMaterialization.skipped.missingChildReference.count=0 + clampMaterialization.skipped.unsupportedShape.count=0 + clampMaterialization.skipped.bodyTextPatternMissing.count=0 + clampMaterialization.runtimeEquivalenceRequiredBeforeSelection=true + clampMaterialization.runtimeEquivalencePayloadRequired=true + clampMaterialization.runtimeEquivalencePayloadPresent.count=1 + clampMaterialization.runtimeEquivalencePassed.count=1 + clampMaterialization.approvalRequiredBeforeProduction=true + clampMaterialization.strictFloatPreserved=true + clampMaterialization.argumentOrderPreserved=true + clampMaterialization.fastMathRequired=false + clampMaterialization.status=review-ready + clampMaterialization.firstBlocker=none + stepMaterialization.pass.count=1 + stepMaterialization.candidate.count=2 + stepMaterialization.transformedNode.count=2 + stepMaterialization.changedMethodBody.count=1 + stepMaterialization.bodyTextReplacement.count=2 + stepMaterialization.fixedPoint.pass.count=2 + stepMaterialization.skipped.typedBodyMissing.count=0 + stepMaterialization.skipped.unsupportedFormat.count=0 + stepMaterialization.skipped.fastMathPolicy.count=0 + stepMaterialization.skipped.missingChildReference.count=0 + stepMaterialization.skipped.unsupportedShape.count=0 + stepMaterialization.skipped.bodyTextPatternMissing.count=0 + stepMaterialization.runtimeEquivalenceRequiredBeforeSelection=true + stepMaterialization.runtimeEquivalencePayloadRequired=true + stepMaterialization.runtimeEquivalencePayloadPresent.count=1 + stepMaterialization.runtimeEquivalencePassed.count=1 + stepMaterialization.approvalRequiredBeforeProduction=true + stepMaterialization.directStep.count=1 + stepMaterialization.invertedStep.count=1 + stepMaterialization.strictFloatPreserved=true + stepMaterialization.strictComparisonPreserved=true + stepMaterialization.equalityBehaviorPreserved=true + stepMaterialization.nanComparisonPreserved=true + stepMaterialization.fastMathRequired=false + stepMaterialization.status=review-ready + stepMaterialization.firstBlocker=none + mixMaterialization.pass.count=1 + mixMaterialization.candidate.count=3 + mixMaterialization.transformedNode.count=3 + mixMaterialization.changedMethodBody.count=1 + mixMaterialization.bodyTextReplacement.count=3 + mixMaterialization.fixedPoint.pass.count=3 + mixMaterialization.skipped.typedBodyMissing.count=0 + mixMaterialization.skipped.unsupportedFormat.count=0 + mixMaterialization.skipped.fastMathPolicy.count=0 + mixMaterialization.skipped.missingChildReference.count=0 + mixMaterialization.skipped.unsupportedShape.count=0 + mixMaterialization.skipped.bodyTextPatternMissing.count=0 + mixMaterialization.runtimeEquivalenceRequiredBeforeSelection=true + mixMaterialization.runtimeEquivalencePayloadRequired=true + mixMaterialization.runtimeEquivalencePayloadPresent.count=1 + mixMaterialization.runtimeEquivalencePassed.count=1 + mixMaterialization.approvalRequiredBeforeProduction=true + mixMaterialization.canonicalMix.count=1 + mixMaterialization.expandedMix.count=1 + mixMaterialization.madExpandedMix.count=1 + mixMaterialization.fastMathAllowed=true + mixMaterialization.fastMathRequired=true + mixMaterialization.strictFloatPreserved=false + mixMaterialization.algebraicReassociationRequired=true + mixMaterialization.mixArgumentOrderPreserved=true + mixMaterialization.status=review-ready + mixMaterialization.firstBlocker=none typedDeadCodePreview.pass.count=1 typedDeadCodePreview.node.count=8 typedDeadCodePreview.reachableNode.count=5 @@ -94,6 +213,12 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { reviewPackage.proposalPass.count=1 reviewPackage.pendingApproval.count=1 reviewPackage.runtimeEquivalence.status=blocked + reviewPackage.approvalManifest.status=pending-resource-path + reviewPackage.approvalManifest.required=true + reviewPackage.approvalManifest.present.count=0 + reviewPackage.approvalManifest.accepted.count=0 + reviewPackage.approvalManifest.resourcePath.summary=none + reviewPackage.approvalManifest.firstBlocker=approval-manifest-resource-path-missing reviewPackage.manualReviewOnly=true pass.0.passVersion=ir-optimizer:no-op:1 pass.1.passVersion=ir-optimizer:text-canonicalization:1 @@ -112,6 +237,10 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { assertEquals(1, summary.entries().get(0).rolledBackCount()); assertEquals(1, summary.entries().get(0).approvalTemplatePendingCount()); assertEquals(2, summary.entries().get(0).approvalTemplateNotApplicableCount()); + assertEquals(1, summary.entries().get(0).approvalTemplateRuntimeEquivalencePayloadRequiredCount()); + assertEquals(0, summary.entries().get(0).approvalTemplateRuntimeEquivalencePayloadPresentCount()); + assertEquals(0, summary.entries().get(0).approvalTemplateRuntimeEquivalencePayloadPassedCount()); + assertEquals(0, summary.entries().get(0).approvalTemplateRuntimeEquivalencePayloadCompleteCount()); assertEquals("candidate-ready", summary.entries().get(0).optimizedArtifactCandidateStatus()); assertEquals(1, summary.entries().get(0).optimizedArtifactCandidateCount()); assertEquals(1, summary.entries().get(0).optimizedArtifactCandidateReadyCount()); @@ -126,6 +255,25 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { assertEquals(5, summary.entries().get(0).constantFoldingPreviewSkippedCount()); assertTrue(summary.entries().get(0).constantFoldingPreviewRuntimeEquivalenceRequiredBeforeRewrite()); assertTrue(summary.entries().get(0).constantFoldingPreviewApprovalRequiredBeforeRewrite()); + assertEquals(1, summary.entries().get(0).constantFoldingMaterializationPassCount()); + assertEquals(1, summary.entries().get(0).constantFoldingMaterializationCandidateCount()); + assertEquals(1, summary.entries().get(0).constantFoldingMaterializationTransformedNodeCount()); + assertEquals(1, summary.entries().get(0).constantFoldingMaterializationLiteralRewriteCount()); + assertEquals(0, summary.entries().get(0).constantFoldingMaterializationIdentityRewriteCount()); + assertEquals(2, summary.entries().get(0).constantFoldingMaterializationFixedPointPassCount()); + assertEquals(1, summary.entries().get(0).constantFoldingMaterializationChangedMethodBodyCount()); + assertEquals(1, summary.entries().get(0).constantFoldingMaterializationBodyTextReplacementCount()); + assertEquals(3, summary.entries().get(0).constantFoldingMaterializationSkippedCount()); + assertTrue(summary.entries().get(0).constantFoldingMaterializationRuntimeEquivalenceRequiredBeforeSelection()); + assertTrue(summary.entries().get(0).constantFoldingMaterializationRuntimeEquivalencePayloadRequired()); + assertEquals(0, summary.entries().get(0).constantFoldingMaterializationRuntimeEquivalencePayloadPresentCount()); + assertEquals(0, summary.entries().get(0).constantFoldingMaterializationRuntimeEquivalencePassedCount()); + assertTrue(summary.entries().get(0).constantFoldingMaterializationApprovalRequiredBeforeProduction()); + assertEquals("pending-runtime-equivalence", summary.entries().get(0).constantFoldingMaterializationStatus()); + assertEquals( + "runtime-equivalence-payload-not-recorded", + summary.entries().get(0).constantFoldingMaterializationFirstBlocker() + ); assertEquals(1, summary.entries().get(0).safeLocalCsePreviewPassCount()); assertEquals(6, summary.entries().get(0).safeLocalCsePreviewExpressionCount()); assertEquals(4, summary.entries().get(0).safeLocalCsePreviewCandidateExpressionCount()); @@ -133,6 +281,51 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { assertEquals(6, summary.entries().get(0).safeLocalCsePreviewBlockedCount()); assertTrue(summary.entries().get(0).safeLocalCsePreviewRuntimeEquivalenceRequiredBeforeRewrite()); assertTrue(summary.entries().get(0).safeLocalCsePreviewApprovalRequiredBeforeRewrite()); + assertEquals(1, summary.entries().get(0).madFmaMaterializationPassCount()); + assertEquals(2, summary.entries().get(0).madFmaMaterializationCandidateCount()); + assertEquals(2, summary.entries().get(0).madFmaMaterializationTransformedNodeCount()); + assertEquals(1, summary.entries().get(0).madFmaMaterializationChangedMethodBodyCount()); + assertEquals(2, summary.entries().get(0).madFmaMaterializationBodyTextReplacementCount()); + assertEquals(1, summary.entries().get(0).madFmaMaterializationFixedPointPassCount()); + assertEquals(1, summary.entries().get(0).madFmaMaterializationSkippedCount()); + assertTrue(summary.entries().get(0).madFmaMaterializationRuntimeEquivalenceRequiredBeforeSelection()); + assertTrue(summary.entries().get(0).madFmaMaterializationRuntimeEquivalencePayloadRequired()); + assertEquals(1, summary.entries().get(0).madFmaMaterializationRuntimeEquivalencePayloadPresentCount()); + assertEquals(1, summary.entries().get(0).madFmaMaterializationRuntimeEquivalencePassedCount()); + assertTrue(summary.entries().get(0).madFmaMaterializationApprovalRequiredBeforeProduction()); + assertTrue(summary.entries().get(0).madFmaMaterializationFastMathAllowed()); + assertEquals("review-ready", summary.entries().get(0).madFmaMaterializationStatus()); + assertEquals("none", summary.entries().get(0).madFmaMaterializationFirstBlocker()); + assertEquals(1, summary.totalClampMaterializationPassCount()); + assertEquals(1, summary.totalClampMaterializationCandidateCount()); + assertEquals(1, summary.totalClampMaterializationTransformedNodeCount()); + assertEquals(1, summary.totalClampMaterializationBodyTextReplacementCount()); + assertEquals(1, summary.totalClampMaterializationRuntimeEquivalencePayloadPresentCount()); + assertEquals(1, summary.totalClampMaterializationRuntimeEquivalencePassedCount()); + assertEquals("review-ready", summary.clampMaterializationStatus()); + assertEquals("none", summary.clampMaterializationFirstBlocker()); + assertEquals(1, summary.totalStepMaterializationPassCount()); + assertEquals(2, summary.totalStepMaterializationCandidateCount()); + assertEquals(2, summary.totalStepMaterializationTransformedNodeCount()); + assertEquals(1, summary.totalStepMaterializationDirectStepCount()); + assertEquals(1, summary.totalStepMaterializationInvertedStepCount()); + assertEquals(1, summary.totalStepMaterializationRuntimeEquivalencePayloadPresentCount()); + assertEquals(1, summary.totalStepMaterializationRuntimeEquivalencePassedCount()); + assertEquals("review-ready", summary.stepMaterializationStatus()); + assertEquals("none", summary.stepMaterializationFirstBlocker()); + assertEquals(1, summary.totalMixMaterializationPassCount()); + assertEquals(3, summary.totalMixMaterializationCandidateCount()); + assertEquals(3, summary.totalMixMaterializationTransformedNodeCount()); + assertEquals(1, summary.totalMixMaterializationCanonicalMixCount()); + assertEquals(1, summary.totalMixMaterializationExpandedMixCount()); + assertEquals(1, summary.totalMixMaterializationMadExpandedMixCount()); + assertEquals(1, summary.totalMixMaterializationRuntimeEquivalencePayloadPresentCount()); + assertEquals(1, summary.totalMixMaterializationRuntimeEquivalencePassedCount()); + assertTrue(summary.mixMaterializationFastMathAllowed()); + assertTrue(summary.mixMaterializationFastMathRequired()); + assertTrue(summary.mixMaterializationAlgebraicReassociationRequired()); + assertEquals("review-ready", summary.mixMaterializationStatus()); + assertEquals("none", summary.mixMaterializationFirstBlocker()); assertEquals(1, summary.entries().get(0).typedDeadCodePreviewPassCount()); assertEquals(8, summary.entries().get(0).typedDeadCodePreviewNodeCount()); assertEquals(5, summary.entries().get(0).typedDeadCodePreviewReachableNodeCount()); @@ -155,15 +348,50 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { assertEquals(0, summary.totalOptimizedArtifactCandidateSelectionAppliedCount()); assertEquals(0, summary.totalOptimizedArtifactCandidateSelectedIrReplacementCount()); assertEquals(0, summary.totalOptimizedArtifactCandidateMutationAllowedCount()); + assertEquals(1, summary.totalApprovalTemplateRuntimeEquivalencePayloadRequiredCount()); + assertEquals(0, summary.totalApprovalTemplateRuntimeEquivalencePayloadPresentCount()); + assertEquals(0, summary.totalApprovalTemplateRuntimeEquivalencePayloadPassedCount()); + assertEquals(0, summary.totalApprovalTemplateRuntimeEquivalencePayloadCompleteCount()); assertEquals("none", summary.optimizedArtifactCandidateFirstBlocker()); assertEquals("mutation-disabled", summary.optimizedArtifactCandidateSelectionFirstBlocker()); + assertEquals(1, summary.totalBackendNeutralSourceMaterializationPassCount()); + assertEquals(1, summary.totalBackendNeutralSourceMaterializationCandidateCount()); + assertEquals(1, summary.totalBackendNeutralSourceMaterializationSourceReadyCount()); + assertEquals(321, summary.totalBackendNeutralSourceMaterializationSourceLength()); + assertEquals("review-ready", summary.backendNeutralSourceMaterializationStatus()); + assertEquals("none", summary.backendNeutralSourceMaterializationFirstBlocker()); assertEquals(1, summary.totalConstantFoldingPreviewPassCount()); assertEquals(3, summary.totalConstantFoldingPreviewCandidateCount()); assertEquals(5, summary.totalConstantFoldingPreviewSkippedCount()); + assertEquals(1, summary.totalConstantFoldingMaterializationPassCount()); + assertEquals(1, summary.totalConstantFoldingMaterializationTransformedNodeCount()); + assertEquals(1, summary.totalConstantFoldingMaterializationLiteralRewriteCount()); + assertEquals(0, summary.totalConstantFoldingMaterializationIdentityRewriteCount()); + assertEquals(2, summary.totalConstantFoldingMaterializationFixedPointPassCount()); + assertEquals(3, summary.totalConstantFoldingMaterializationSkippedCount()); + assertEquals(0, summary.totalConstantFoldingMaterializationRuntimeEquivalencePayloadPresentCount()); + assertEquals(0, summary.totalConstantFoldingMaterializationRuntimeEquivalencePassedCount()); + assertEquals("pending-runtime-equivalence", summary.constantFoldingMaterializationStatus()); + assertEquals("runtime-equivalence-payload-not-recorded", summary.constantFoldingMaterializationFirstBlocker()); assertEquals(1, summary.totalSafeLocalCsePreviewPassCount()); assertEquals(4, summary.totalSafeLocalCsePreviewCandidateExpressionCount()); assertEquals(2, summary.totalSafeLocalCsePreviewDuplicateExpressionCount()); assertEquals(6, summary.totalSafeLocalCsePreviewBlockedCount()); + assertEquals(0, summary.totalSafeLocalCseMaterializationPassCount()); + assertEquals(0, summary.totalSafeLocalCseMaterializationTransformedNodeCount()); + assertEquals("not-recorded", summary.safeLocalCseMaterializationStatus()); + assertEquals("not-recorded", summary.safeLocalCseMaterializationFirstBlocker()); + assertEquals(1, summary.totalMadFmaMaterializationPassCount()); + assertEquals(2, summary.totalMadFmaMaterializationCandidateCount()); + assertEquals(2, summary.totalMadFmaMaterializationTransformedNodeCount()); + assertEquals(2, summary.totalMadFmaMaterializationBodyTextReplacementCount()); + assertEquals(1, summary.totalMadFmaMaterializationFixedPointPassCount()); + assertEquals(1, summary.totalMadFmaMaterializationSkippedCount()); + assertEquals(1, summary.totalMadFmaMaterializationRuntimeEquivalencePayloadPresentCount()); + assertEquals(1, summary.totalMadFmaMaterializationRuntimeEquivalencePassedCount()); + assertTrue(summary.madFmaMaterializationFastMathAllowed()); + assertEquals("review-ready", summary.madFmaMaterializationStatus()); + assertEquals("none", summary.madFmaMaterializationFirstBlocker()); assertEquals(1, summary.totalTypedDeadCodePreviewPassCount()); assertEquals(3, summary.totalTypedDeadCodePreviewUnreachableNodeCount()); assertEquals(4, summary.totalTypedDeadCodePreviewBlockedCount()); @@ -177,12 +405,17 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { ); assertEquals("blocked", summary.runtimeEquivalenceReviewStatus()); assertTrue(summary.runtimeEquivalenceReviewRequired()); - assertEquals("preview-readiness-blocked-by-proof", summary.runtimeEquivalenceReviewFirstBlocker()); + assertEquals("runtime-equivalence-payload-not-recorded", summary.runtimeEquivalenceReviewFirstBlocker()); assertEquals("pending-manual-review", summary.reviewPackageStatus()); assertEquals(1, summary.totalReviewPackageRequiredCount()); assertEquals(0, summary.totalReviewPackageCompleteCount()); assertEquals(1, summary.totalReviewPackageProposalPassCount()); assertEquals(1, summary.totalReviewPackagePendingApprovalCount()); + assertEquals("pending-resource-path", summary.reviewPackageApprovalManifestStatus()); + assertEquals(1, summary.totalReviewPackageApprovalManifestRequiredCount()); + assertEquals(0, summary.totalReviewPackageApprovalManifestPresentCount()); + assertEquals(0, summary.totalReviewPackageApprovalManifestAcceptedCount()); + assertEquals("approval-manifest-resource-path-missing", summary.reviewPackageApprovalManifestFirstBlocker()); assertEquals("preview-readiness-blocked-by-proof", summary.reviewPackageFirstBlocker()); assertEquals( "ir-optimizer:no-op:1=1, ir-optimizer:text-canonicalization:1=2", @@ -192,6 +425,12 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { assertTrue(summary.toMarkdown().contains("- Proposal-only count: `2`")); assertTrue(summary.toMarkdown().contains("- Approval templates pending: `1`")); assertTrue(summary.toMarkdown().contains("- Approval templates not applicable: `2`")); + assertTrue(summary.toMarkdown().contains( + "- Approval templates runtime-equivalence payload required: `1`" + )); + assertTrue(summary.toMarkdown().contains( + "- Approval templates runtime-equivalence payload complete: `0`" + )); assertTrue(summary.toMarkdown().contains("- Optimized artifact candidate status: `candidate-ready`")); assertTrue(summary.toMarkdown().contains("- Optimized artifact candidates: `1`")); assertTrue(summary.toMarkdown().contains("- Optimized artifact candidates ready: `1`")); @@ -204,9 +443,54 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { assertTrue(summary.toMarkdown().contains("- Optimized artifact candidate mutation-allowed count: `0`")); assertTrue(summary.toMarkdown().contains("- Optimized artifact candidate selection applied: `false`")); assertTrue(summary.toMarkdown().contains("- Optimized artifact candidate selected IR replacement: `false`")); + assertTrue(summary.toMarkdown().contains("- Backend-neutral source materialization status: `review-ready`")); + assertTrue(summary.toMarkdown().contains("- Backend-neutral source materialization passes: `1`")); + assertTrue(summary.toMarkdown().contains("- Backend-neutral source materialization candidates: `1`")); + assertTrue(summary.toMarkdown().contains("- Backend-neutral source materialization source-ready count: `1`")); + assertTrue(summary.toMarkdown().contains("- Backend-neutral source materialization source length total: `321`")); + assertTrue(summary.toMarkdown().contains("- Backend-neutral source materialization first blocker: `none`")); assertTrue(summary.toMarkdown().contains("- Constant folding preview passes: `1`")); assertTrue(summary.toMarkdown().contains("- Constant folding preview candidates: `3`")); assertTrue(summary.toMarkdown().contains("- Constant folding preview skipped blockers: `5`")); + assertTrue(summary.toMarkdown().contains("- Constant folding materialization status: `pending-runtime-equivalence`")); + assertTrue(summary.toMarkdown().contains("- Constant folding materialization passes: `1`")); + assertTrue(summary.toMarkdown().contains("- Constant folding materialized nodes: `1`")); + assertTrue(summary.toMarkdown().contains("- Constant folding materialization literal rewrites: `1`")); + assertTrue(summary.toMarkdown().contains("- Constant folding materialization identity rewrites: `0`")); + assertTrue(summary.toMarkdown().contains("- Constant folding materialization fixed-point passes: `2`")); + assertTrue(summary.toMarkdown().contains("- Constant folding materialization skipped blockers: `3`")); + assertTrue(summary.toMarkdown().contains("- Constant folding materialization runtime-equivalence payloads: `0`")); + assertTrue(summary.toMarkdown().contains("- Constant folding materialization runtime-equivalence passed: `0`")); + assertTrue(summary.toMarkdown().contains("- Constant folding materialization first blocker: `runtime-equivalence-payload-not-recorded`")); + assertTrue(summary.toMarkdown().contains("- Safe local CSE materialization status: `not-recorded`")); + assertTrue(summary.toMarkdown().contains("- Safe local CSE materialized nodes: `0`")); + assertTrue(summary.toMarkdown().contains("- Safe local CSE materialization first blocker: `not-recorded`")); + assertTrue(summary.toMarkdown().contains("- Mad/FMA materialization status: `review-ready`")); + assertTrue(summary.toMarkdown().contains("- Mad/FMA materialization passes: `1`")); + assertTrue(summary.toMarkdown().contains("- Mad/FMA materialization candidates: `2`")); + assertTrue(summary.toMarkdown().contains("- Mad/FMA materialized nodes: `2`")); + assertTrue(summary.toMarkdown().contains("- Mad/FMA materialization text replacements: `2`")); + assertTrue(summary.toMarkdown().contains("- Mad/FMA materialization fixed-point passes: `1`")); + assertTrue(summary.toMarkdown().contains("- Mad/FMA materialization skipped blockers: `1`")); + assertTrue(summary.toMarkdown().contains("- Mad/FMA materialization runtime-equivalence payloads: `1`")); + assertTrue(summary.toMarkdown().contains("- Mad/FMA materialization runtime-equivalence passed: `1`")); + assertTrue(summary.toMarkdown().contains("- Mad/FMA materialization fast-math allowed: `true`")); + assertTrue(summary.toMarkdown().contains("- Mad/FMA materialization first blocker: `none`")); + assertTrue(summary.toMarkdown().contains("- Clamp materialization status: `review-ready`")); + assertTrue(summary.toMarkdown().contains("- Clamp materialized nodes: `1`")); + assertTrue(summary.toMarkdown().contains("- Clamp materialization runtime-equivalence payloads: `1`")); + assertTrue(summary.toMarkdown().contains("- Step materialization status: `review-ready`")); + assertTrue(summary.toMarkdown().contains("- Step materialization direct step count: `1`")); + assertTrue(summary.toMarkdown().contains("- Step materialization inverted step count: `1`")); + assertTrue(summary.toMarkdown().contains("- Mix materialization status: `review-ready`")); + assertTrue(summary.toMarkdown().contains("- Mix materialization canonical count: `1`")); + assertTrue(summary.toMarkdown().contains("- Mix materialization expanded count: `1`")); + assertTrue(summary.toMarkdown().contains("- Mix materialization MAD-expanded count: `1`")); + assertTrue(summary.toMarkdown().contains("- Mix materialization fast-math allowed: `true`")); + assertTrue(summary.toMarkdown().contains("- Mix materialization algebraic reassociation required: `true`")); + assertTrue(summary.toMarkdown().contains("- Typed dead-code materialization status: `not-recorded`")); + assertTrue(summary.toMarkdown().contains("- Typed dead-code materialization removed nodes: `0`")); + assertTrue(summary.toMarkdown().contains("- Typed dead-code materialization first blocker: `not-recorded`")); assertTrue(summary.toMarkdown().contains("- Safe local CSE preview passes: `1`")); assertTrue(summary.toMarkdown().contains("- Safe local CSE preview candidate expressions: `4`")); assertTrue(summary.toMarkdown().contains("- Safe local CSE preview duplicate expressions: `2`")); @@ -222,7 +506,7 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { assertTrue(summary.toMarkdown().contains("- Runtime-equivalence review status: `blocked`")); assertTrue(summary.toMarkdown().contains("- Runtime-equivalence review eligible: `false`")); assertTrue(summary.toMarkdown().contains("- Runtime-equivalence review required: `true`")); - assertTrue(summary.toMarkdown().contains("- Runtime-equivalence review first blocker: `preview-readiness-blocked-by-proof`")); + assertTrue(summary.toMarkdown().contains("- Runtime-equivalence review first blocker: `runtime-equivalence-payload-not-recorded`")); assertTrue(summary.toMarkdown().contains("- Runtime-equivalence review production mutation: `disabled`")); assertTrue(summary.toMarkdown().contains("- Runtime-equivalence review selected IR replacement: `disabled`")); assertTrue(summary.toMarkdown().contains("- Review package status: `pending-manual-review`")); @@ -230,13 +514,18 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { assertTrue(summary.toMarkdown().contains("- Review package complete kernels: `0`")); assertTrue(summary.toMarkdown().contains("- Review package proposal passes: `1`")); assertTrue(summary.toMarkdown().contains("- Review package pending approvals: `1`")); + assertTrue(summary.toMarkdown().contains("- Review package approval manifest status: `pending-resource-path`")); + assertTrue(summary.toMarkdown().contains("- Review package approval manifests required: `1`")); + assertTrue(summary.toMarkdown().contains( + "- Review package approval manifest first blocker: `approval-manifest-resource-path-missing`" + )); assertTrue(summary.toMarkdown().contains("- Review package first blocker: `preview-readiness-blocked-by-proof`")); assertTrue(summary.toMarkdown().contains("- Review package manual review only: `true`")); assertTrue(summary.toMarkdown().contains("- Review package production mutation: `disabled`")); assertTrue(summary.toMarkdown().contains("- Review package selected IR replacement: `disabled`")); assertTrue(summary.toMarkdown().contains("- Providers: `ir-optimizer:no-op:1=1, ir-optimizer:text-canonicalization:1=2`")); - assertTrue(summary.toMarkdown().contains("| `kernel-a.cl` | `recorded` | `3` | `2` | `1` | `1` | `1` | `2` | `candidate-ready` | `none` | `mutation-disabled` | `3` | `5` | `4` | `2` | `6` | `3` | `4` | `pending-manual-review` | `preview-readiness-blocked-by-proof` | `ir-optimizer:no-op:1=1, ir-optimizer:text-canonicalization:1=2` |")); - assertTrue(summary.toMarkdown().contains("| `kernel-b.cl` | `missing` | `0` | `0` | `0` | `0` | `0` | `0` | `not-recorded` | `no-candidates` | `no-candidates` | `0` | `0` | `0` | `0` | `0` | `0` | `0` | `not-recorded` | `review-package-not-recorded` | `none` |")); + assertTrue(summary.toMarkdown().contains("| `kernel-a.cl` | `recorded` | `3` | `2` | `1` | `1` | `1` | `2` | `candidate-ready` | `none` | `mutation-disabled` | `3` | `5` | `1` | `runtime-equivalence-payload-not-recorded` | `4` | `2` | `6` | `0` | `not-recorded` | `3` | `4` | `0` | `not-recorded` | `pending-manual-review` | `preview-readiness-blocked-by-proof` | `ir-optimizer:no-op:1=1, ir-optimizer:text-canonicalization:1=2` |")); + assertTrue(summary.toMarkdown().contains("| `kernel-b.cl` | `missing` | `0` | `0` | `0` | `0` | `0` | `0` | `not-recorded` | `no-candidates` | `no-candidates` | `0` | `0` | `0` | `not-recorded` | `0` | `0` | `0` | `0` | `not-recorded` | `0` | `0` | `0` | `not-recorded` | `not-recorded` | `review-package-not-recorded` | `none` |")); } @Test @@ -305,4 +594,326 @@ void marksPreviewReadinessReadyWhenCandidatesHaveNoBlockers() throws Exception { assertTrue(summary.toMarkdown().contains("- Runtime-equivalence review eligible: `true`")); assertTrue(summary.toMarkdown().contains("- Runtime-equivalence review first blocker: `none`")); } + + @Test + void marksMaterializedConstantFoldingReadyWhenRuntimeEquivalencePayloadPassed() throws Exception { + Path reportDirectory = temporaryDirectory.resolve("materialized-ready-reports").resolve("opencl"); + Files.createDirectories(reportDirectory); + Path workloadGate = reportDirectory.resolve("backend-source-promotion-workload-gate.properties"); + Files.writeString(workloadGate, """ + kernel.count=1 + kernel.0.sourceKernelResource=kernel-folded.cl + """); + Path artifactDirectory = reportDirectory + .resolve("runtime-compile-artifacts") + .resolve("kernel-folded"); + Files.createDirectories(artifactDirectory); + Files.writeString(artifactDirectory.resolve("backend-module.properties"), """ + resource=kernel-folded.cl + backendTarget=OPENCL + """); + Files.writeString(artifactDirectory.resolve( + GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT), """ + status=recorded + pass.count=1 + proposalOnly.count=1 + approvalTemplate.runtimeEquivalencePayloadRequired.count=1 + approvalTemplate.runtimeEquivalencePayloadPresent.count=1 + approvalTemplate.runtimeEquivalencePayloadPassed.count=1 + approvalTemplate.runtimeEquivalencePayloadComplete.count=1 + constantFoldingMaterialization.pass.count=1 + constantFoldingMaterialization.candidate.count=1 + constantFoldingMaterialization.transformedNode.count=1 + constantFoldingMaterialization.literalRewrite.count=0 + constantFoldingMaterialization.identityRewrite.count=1 + constantFoldingMaterialization.fixedPointPass.count=1 + constantFoldingMaterialization.changedMethodBody.count=1 + constantFoldingMaterialization.bodyTextReplacement.count=1 + constantFoldingMaterialization.runtimeEquivalenceRequiredBeforeSelection=true + constantFoldingMaterialization.runtimeEquivalencePayloadRequired=true + constantFoldingMaterialization.runtimeEquivalencePayloadPresent.count=1 + constantFoldingMaterialization.runtimeEquivalencePassed.count=1 + constantFoldingMaterialization.approvalRequiredBeforeProduction=true + constantFoldingMaterialization.status=review-ready + constantFoldingMaterialization.firstBlocker=none + reviewPackage.status=pending-manual-review + reviewPackage.required=true + reviewPackage.complete=false + reviewPackage.firstBlocker=approval-template-pending + reviewPackage.proposalPass.count=1 + reviewPackage.pendingApproval.count=1 + reviewPackage.runtimeEquivalence.status=review-ready + reviewPackage.approvalManifest.status=pending-manifest-validation + reviewPackage.approvalManifest.required=true + reviewPackage.approvalManifest.present.count=0 + reviewPackage.approvalManifest.accepted.count=0 + reviewPackage.approvalManifest.resourcePath.summary=META-INF/javatogpu/ir-optimization-approvals/approval-test.properties=1 + reviewPackage.approvalManifest.firstBlocker=approval-manifest-not-loaded + reviewPackage.manualReviewOnly=true + pass.0.passVersion=ir-optimizer:constant-folding-materialization:1 + """); + + OpenClRuntimeIrOptimizerEvidenceSummary summary = OpenClRuntimeIrOptimizerEvidenceSummary.read(workloadGate); + + assertEquals("review-ready", summary.constantFoldingMaterializationStatus()); + assertEquals(0, summary.totalConstantFoldingMaterializationLiteralRewriteCount()); + assertEquals(1, summary.totalConstantFoldingMaterializationIdentityRewriteCount()); + assertEquals("none", summary.constantFoldingMaterializationFirstBlocker()); + assertEquals("review-ready", summary.runtimeEquivalenceReviewStatus()); + assertTrue(summary.runtimeEquivalenceReviewEligible()); + assertTrue(summary.runtimeEquivalenceReviewRequired()); + assertEquals("none", summary.runtimeEquivalenceReviewFirstBlocker()); + assertEquals("pending-manual-review", summary.reviewPackageStatus()); + assertEquals("approval-template-pending", summary.reviewPackageFirstBlocker()); + assertEquals("pending-manifest-validation", summary.reviewPackageApprovalManifestStatus()); + assertEquals(1, summary.totalReviewPackageApprovalManifestRequiredCount()); + assertEquals(0, summary.totalReviewPackageApprovalManifestAcceptedCount()); + assertEquals("approval-manifest-not-loaded", summary.reviewPackageApprovalManifestFirstBlocker()); + assertEquals(1, summary.totalApprovalTemplateRuntimeEquivalencePayloadRequiredCount()); + assertEquals(1, summary.totalApprovalTemplateRuntimeEquivalencePayloadPresentCount()); + assertEquals(1, summary.totalApprovalTemplateRuntimeEquivalencePayloadPassedCount()); + assertEquals(1, summary.totalApprovalTemplateRuntimeEquivalencePayloadCompleteCount()); + assertTrue(summary.toMarkdown().contains("- Constant folding materialization status: `review-ready`")); + assertTrue(summary.toMarkdown().contains("- Constant folding materialization identity rewrites: `1`")); + assertTrue(summary.toMarkdown().contains("- Constant folding materialization runtime-equivalence payloads: `1`")); + assertTrue(summary.toMarkdown().contains( + "- Approval templates runtime-equivalence payload complete: `1`" + )); + assertTrue(summary.toMarkdown().contains( + "- Review package approval manifest status: `pending-manifest-validation`" + )); + assertTrue(summary.toMarkdown().contains("- Runtime-equivalence review status: `review-ready`")); + assertTrue(summary.toMarkdown().contains("- Review package first blocker: `approval-template-pending`")); + } + + @Test + void marksMadFmaMaterializationPendingUntilRuntimeEquivalencePayloadPassed() throws Exception { + Path reportDirectory = temporaryDirectory.resolve("mad-fma-materialized-pending-reports").resolve("opencl"); + Files.createDirectories(reportDirectory); + Path workloadGate = reportDirectory.resolve("backend-source-promotion-workload-gate.properties"); + Files.writeString(workloadGate, """ + kernel.count=1 + kernel.0.sourceKernelResource=kernel-mad-fma.cl + """); + Path artifactDirectory = reportDirectory + .resolve("runtime-compile-artifacts") + .resolve("kernel-mad-fma"); + Files.createDirectories(artifactDirectory); + Files.writeString(artifactDirectory.resolve("backend-module.properties"), """ + resource=kernel-mad-fma.cl + backendTarget=OPENCL + """); + Files.writeString(artifactDirectory.resolve( + GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT), """ + status=recorded + pass.count=1 + proposalOnly.count=1 + approvalTemplate.runtimeEquivalencePayloadRequired.count=1 + approvalTemplate.runtimeEquivalencePayloadPresent.count=0 + approvalTemplate.runtimeEquivalencePayloadPassed.count=0 + approvalTemplate.runtimeEquivalencePayloadComplete.count=0 + madFmaMaterialization.pass.count=1 + madFmaMaterialization.candidate.count=1 + madFmaMaterialization.transformedNode.count=1 + madFmaMaterialization.changedMethodBody.count=1 + madFmaMaterialization.bodyTextReplacement.count=1 + madFmaMaterialization.fixedPoint.pass.count=1 + madFmaMaterialization.skipped.fastMathPolicy.count=0 + madFmaMaterialization.skipped.bodyTextPatternMissing.count=0 + madFmaMaterialization.runtimeEquivalenceRequiredBeforeSelection=true + madFmaMaterialization.runtimeEquivalencePayloadRequired=true + madFmaMaterialization.runtimeEquivalencePayloadPresent.count=0 + madFmaMaterialization.runtimeEquivalencePassed.count=0 + madFmaMaterialization.approvalRequiredBeforeProduction=true + madFmaMaterialization.fastMathAllowed=true + madFmaMaterialization.status=pending-runtime-equivalence + madFmaMaterialization.firstBlocker=runtime-equivalence-payload-not-recorded + reviewPackage.status=pending-manual-review + reviewPackage.required=true + reviewPackage.complete=false + reviewPackage.firstBlocker=runtime-equivalence-payload-not-recorded + reviewPackage.proposalPass.count=1 + reviewPackage.pendingApproval.count=1 + reviewPackage.runtimeEquivalence.status=blocked + reviewPackage.manualReviewOnly=true + pass.0.passVersion=ir-optimizer:mad-fma-materialization:1 + """); + + OpenClRuntimeIrOptimizerEvidenceSummary summary = OpenClRuntimeIrOptimizerEvidenceSummary.read(workloadGate); + + assertEquals("pending-runtime-equivalence", summary.madFmaMaterializationStatus()); + assertEquals(1, summary.totalMadFmaMaterializationTransformedNodeCount()); + assertEquals(0, summary.totalMadFmaMaterializationRuntimeEquivalencePayloadPresentCount()); + assertEquals("runtime-equivalence-payload-not-recorded", summary.madFmaMaterializationFirstBlocker()); + assertEquals("blocked", summary.runtimeEquivalenceReviewStatus()); + assertTrue(summary.runtimeEquivalenceReviewRequired()); + assertEquals("runtime-equivalence-payload-not-recorded", summary.runtimeEquivalenceReviewFirstBlocker()); + assertTrue(summary.toMarkdown().contains("- Mad/FMA materialization status: `pending-runtime-equivalence`")); + assertTrue(summary.toMarkdown().contains("- Runtime-equivalence review first blocker: `runtime-equivalence-payload-not-recorded`")); + } + + @Test + void marksMaterializedTypedDeadCodeReadyWhenRuntimeEquivalencePayloadPassed() throws Exception { + Path reportDirectory = temporaryDirectory.resolve("typed-materialized-ready-reports").resolve("opencl"); + Files.createDirectories(reportDirectory); + Path workloadGate = reportDirectory.resolve("backend-source-promotion-workload-gate.properties"); + Files.writeString(workloadGate, """ + kernel.count=1 + kernel.0.sourceKernelResource=kernel-tdc.cl + """); + Path artifactDirectory = reportDirectory + .resolve("runtime-compile-artifacts") + .resolve("kernel-tdc"); + Files.createDirectories(artifactDirectory); + Files.writeString(artifactDirectory.resolve("backend-module.properties"), """ + resource=kernel-tdc.cl + backendTarget=OPENCL + """); + Files.writeString(artifactDirectory.resolve( + GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT), """ + status=recorded + pass.count=1 + proposalOnly.count=1 + approvalTemplate.runtimeEquivalencePayloadRequired.count=1 + approvalTemplate.runtimeEquivalencePayloadPresent.count=1 + approvalTemplate.runtimeEquivalencePayloadPassed.count=1 + approvalTemplate.runtimeEquivalencePayloadComplete.count=1 + typedDeadCodeMaterialization.pass.count=1 + typedDeadCodeMaterialization.node.count=5 + typedDeadCodeMaterialization.unreachableNode.count=3 + typedDeadCodeMaterialization.removedNode.count=3 + typedDeadCodeMaterialization.changedMethodBody.count=1 + typedDeadCodeMaterialization.blocked.missingRoot.count=0 + typedDeadCodeMaterialization.blocked.missingChildReference.count=0 + typedDeadCodeMaterialization.blocked.sideEffectingUnreachableNode.count=0 + typedDeadCodeMaterialization.runtimeEquivalenceRequiredBeforeSelection=true + typedDeadCodeMaterialization.runtimeEquivalencePayloadRequired=true + typedDeadCodeMaterialization.runtimeEquivalencePayloadPresent.count=1 + typedDeadCodeMaterialization.runtimeEquivalencePassed.count=1 + typedDeadCodeMaterialization.approvalRequiredBeforeProduction=true + typedDeadCodeMaterialization.sideEffectFreedomProven=true + typedDeadCodeMaterialization.status=review-ready + typedDeadCodeMaterialization.firstBlocker=none + runtimeEquivalenceReview.status=review-ready + runtimeEquivalenceReview.eligible=true + runtimeEquivalenceReview.required=true + runtimeEquivalenceReview.firstBlocker=none + runtimeEquivalenceReview.familySummary=constant-folding=not-recorded, safe-local-cse=not-recorded, typed-dead-code=not-recorded, constant-folding-materialization=not-recorded, typed-dead-code-materialization=review-ready + reviewPackage.status=pending-manual-review + reviewPackage.required=true + reviewPackage.complete=false + reviewPackage.firstBlocker=approval-template-pending + reviewPackage.proposalPass.count=1 + reviewPackage.pendingApproval.count=1 + reviewPackage.runtimeEquivalence.status=review-ready + reviewPackage.manualReviewOnly=true + pass.0.passVersion=ir-optimizer:typed-dead-code-materialization:1 + """); + + OpenClRuntimeIrOptimizerEvidenceSummary summary = OpenClRuntimeIrOptimizerEvidenceSummary.read(workloadGate); + + assertEquals("review-ready", summary.typedDeadCodeMaterializationStatus()); + assertEquals(1, summary.totalTypedDeadCodeMaterializationPassCount()); + assertEquals(5, summary.totalTypedDeadCodeMaterializationNodeCount()); + assertEquals(3, summary.totalTypedDeadCodeMaterializationUnreachableNodeCount()); + assertEquals(3, summary.totalTypedDeadCodeMaterializationRemovedNodeCount()); + assertEquals(1, summary.totalTypedDeadCodeMaterializationRuntimeEquivalencePayloadPresentCount()); + assertEquals(1, summary.totalTypedDeadCodeMaterializationRuntimeEquivalencePassedCount()); + assertEquals("none", summary.typedDeadCodeMaterializationFirstBlocker()); + assertEquals("review-ready", summary.runtimeEquivalenceReviewStatus()); + assertTrue(summary.runtimeEquivalenceReviewEligible()); + assertTrue(summary.runtimeEquivalenceReviewRequired()); + assertEquals("none", summary.runtimeEquivalenceReviewFirstBlocker()); + assertTrue(summary.toMarkdown().contains("- Typed dead-code materialization status: `review-ready`")); + assertTrue(summary.toMarkdown().contains("- Typed dead-code materialization removed nodes: `3`")); + assertTrue(summary.toMarkdown().contains("- Typed dead-code materialization runtime-equivalence payloads: `1`")); + assertTrue(summary.toMarkdown().contains("- Runtime-equivalence review status: `review-ready`")); + } + + @Test + void recordsAcceptedApprovalManifestAsReviewOnlyEvidence() throws Exception { + Path reportDirectory = temporaryDirectory.resolve("approved-manifest-reports").resolve("opencl"); + Files.createDirectories(reportDirectory); + Path workloadGate = reportDirectory.resolve("backend-source-promotion-workload-gate.properties"); + Files.writeString(workloadGate, """ + kernel.count=1 + kernel.0.sourceKernelResource=kernel-approved-fold.cl + """); + Path artifactDirectory = reportDirectory + .resolve("runtime-compile-artifacts") + .resolve("kernel-approved-fold"); + Files.createDirectories(artifactDirectory); + Files.writeString(artifactDirectory.resolve("backend-module.properties"), """ + resource=kernel-approved-fold.cl + backendTarget=OPENCL + """); + Files.writeString(artifactDirectory.resolve( + GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT), """ + status=recorded + pass.count=1 + proposalOnly.count=1 + selectedOptimized.count=0 + approvalTemplate.pending.count=1 + approvalTemplate.runtimeEquivalencePayloadRequired.count=1 + approvalTemplate.runtimeEquivalencePayloadPresent.count=1 + approvalTemplate.runtimeEquivalencePayloadPassed.count=1 + approvalTemplate.runtimeEquivalencePayloadComplete.count=1 + optimizedArtifactCandidate.selectionApplied=false + optimizedArtifactCandidate.selectedIrReplacement=false + constantFoldingMaterialization.pass.count=1 + constantFoldingMaterialization.candidate.count=1 + constantFoldingMaterialization.transformedNode.count=1 + constantFoldingMaterialization.literalRewrite.count=0 + constantFoldingMaterialization.identityRewrite.count=1 + constantFoldingMaterialization.fixedPointPass.count=1 + constantFoldingMaterialization.runtimeEquivalencePayloadRequired=true + constantFoldingMaterialization.runtimeEquivalencePayloadPresent.count=1 + constantFoldingMaterialization.runtimeEquivalencePassed.count=1 + constantFoldingMaterialization.status=review-ready + constantFoldingMaterialization.firstBlocker=none + runtimeEquivalenceReview.status=review-ready + runtimeEquivalenceReview.eligible=true + runtimeEquivalenceReview.required=true + runtimeEquivalenceReview.firstBlocker=none + runtimeEquivalenceReview.productionMutation=disabled + runtimeEquivalenceReview.selectedIrReplacement=disabled + reviewPackage.status=pending-manual-review + reviewPackage.required=true + reviewPackage.complete=false + reviewPackage.firstBlocker=manual-review-required + reviewPackage.proposalPass.count=1 + reviewPackage.pendingApproval.count=1 + reviewPackage.runtimeEquivalence.status=review-ready + reviewPackage.approvalManifest.status=accepted + reviewPackage.approvalManifest.required=true + reviewPackage.approvalManifest.present.count=1 + reviewPackage.approvalManifest.accepted.count=1 + reviewPackage.approvalManifest.resourcePath.summary=META-INF/javatogpu/ir-optimization-approvals/accepted-fold.properties=1 + reviewPackage.approvalManifest.firstBlocker=none + reviewPackage.manualReviewOnly=true + reviewPackage.productionMutation=disabled + reviewPackage.selectedIrReplacement=disabled + pass.0.passVersion=ir-optimizer:constant-folding-materialization:1 + """); + + OpenClRuntimeIrOptimizerEvidenceSummary summary = OpenClRuntimeIrOptimizerEvidenceSummary.read(workloadGate); + + assertEquals("accepted", summary.reviewPackageApprovalManifestStatus()); + assertEquals(1, summary.totalReviewPackageApprovalManifestRequiredCount()); + assertEquals(1, summary.totalReviewPackageApprovalManifestPresentCount()); + assertEquals(1, summary.totalReviewPackageApprovalManifestAcceptedCount()); + assertEquals("none", summary.reviewPackageApprovalManifestFirstBlocker()); + assertEquals("pending-manual-review", summary.reviewPackageStatus()); + assertEquals(0, summary.totalReviewPackageCompleteCount()); + assertEquals("manual-review-required", summary.reviewPackageFirstBlocker()); + assertEquals(0, summary.totalSelectedOptimizedCount()); + assertEquals(0, summary.totalOptimizedArtifactCandidateSelectionAppliedCount()); + assertEquals(0, summary.totalOptimizedArtifactCandidateSelectedIrReplacementCount()); + assertTrue(summary.toMarkdown().contains("- Review package approval manifest status: `accepted`")); + assertTrue(summary.toMarkdown().contains("- Review package approval manifests present: `1`")); + assertTrue(summary.toMarkdown().contains("- Review package approval manifests accepted: `1`")); + assertTrue(summary.toMarkdown().contains("- Review package first blocker: `manual-review-required`")); + assertTrue(summary.toMarkdown().contains("- Review package production mutation: `disabled`")); + assertTrue(summary.toMarkdown().contains("- Review package selected IR replacement: `disabled`")); + } } diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCliTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCliTest.java index 09be07fb..24ea538a 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCliTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCliTest.java @@ -115,6 +115,299 @@ void rejectsRequiredPackageWithoutBlocker() throws Exception { ); } + @Test + void acceptsMadFmaReviewReadyEvidenceAsManualReviewOnly() throws Exception { + Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + Files.writeString(artifact, failClosedEvidence( + "pending-manual-review", + "true", + "approval-template-pending", + "1", + "1" + ) + madFmaMaterializationEvidence( + "review-ready", + "none", + "1", + "1", + "1", + "true" + )); + + assertDoesNotThrow(() -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main(new String[]{artifact.toString()})); + } + + @Test + void rejectsMadFmaReviewReadyWithoutRuntimeEquivalencePayload() throws Exception { + Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + Files.writeString(artifact, failClosedEvidence( + "pending-manual-review", + "true", + "approval-template-pending", + "1", + "1" + ) + madFmaMaterializationEvidence( + "review-ready", + "none", + "1", + "0", + "0", + "true" + )); + + assertThrows( + IllegalStateException.class, + () -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main(new String[]{artifact.toString()}) + ); + } + + @Test + void rejectsMadFmaMaterializationWithoutFastMathPolicyEvidence() throws Exception { + Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + Files.writeString(artifact, failClosedEvidence( + "pending-manual-review", + "true", + "runtime-equivalence-payload-not-recorded", + "1", + "1" + ) + madFmaMaterializationEvidence( + "pending-runtime-equivalence", + "runtime-equivalence-payload-not-recorded", + "1", + "0", + "0", + "false" + )); + + assertThrows( + IllegalStateException.class, + () -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main(new String[]{artifact.toString()}) + ); + } + + @Test + void acceptsIntrinsicMaterializationReviewReadyEvidenceAsManualReviewOnly() throws Exception { + Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + Files.writeString(artifact, failClosedEvidence( + "pending-manual-review", + "true", + "approval-template-pending", + "3", + "3" + ) + clampMaterializationEvidence( + "review-ready", + "none", + "1", + "1", + "1", + "true", + "true", + "false" + ) + stepMaterializationEvidence( + "review-ready", + "none", + "2", + "1", + "1", + "1", + "1", + "true", + "true", + "true", + "true", + "false" + ) + mixMaterializationEvidence( + "review-ready", + "none", + "3", + "1", + "1", + "1", + "1", + "1", + "true", + "true", + "false", + "true", + "true" + )); + + assertDoesNotThrow(() -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main(new String[]{artifact.toString()})); + } + + @Test + void rejectsMixExpandedMaterializationWithoutFastMathPolicyEvidence() throws Exception { + Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + Files.writeString(artifact, failClosedEvidence( + "pending-manual-review", + "true", + "approval-template-pending", + "1", + "1" + ) + mixMaterializationEvidence( + "review-ready", + "none", + "3", + "1", + "1", + "1", + "1", + "1", + "false", + "true", + "false", + "true", + "true" + )); + + assertThrows( + IllegalStateException.class, + () -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main(new String[]{artifact.toString()}) + ); + } + + @Test + void acceptsLoopVectorizationReviewReadyEvidenceAsManualReviewOnly() throws Exception { + Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + Files.writeString(artifact, failClosedEvidence( + "pending-manual-review", + "true", + "approval-template-pending", + "1", + "1" + ) + loopVectorizationMaterializationEvidence( + "review-ready", + "none", + "1", + "1", + "1", + "true", + "true", + "true" + )); + + assertDoesNotThrow(() -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main(new String[]{artifact.toString()})); + } + + @Test + void rejectsLoopVectorizationReviewReadyWithoutProofs() throws Exception { + Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + Files.writeString(artifact, failClosedEvidence( + "pending-manual-review", + "true", + "approval-template-pending", + "1", + "1" + ) + loopVectorizationMaterializationEvidence( + "review-ready", + "none", + "1", + "1", + "1", + "true", + "false", + "true" + )); + + assertThrows( + IllegalStateException.class, + () -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main(new String[]{artifact.toString()}) + ); + } + + @Test + void acceptsSafeLocalCseReviewReadyEvidenceAsManualReviewOnly() throws Exception { + Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + Files.writeString(artifact, failClosedEvidence( + "pending-manual-review", + "true", + "approval-template-pending", + "1", + "1" + ) + safeLocalCseMaterializationEvidence( + "review-ready", + "none", + "1", + "1", + "1", + "true", + "true" + )); + + assertDoesNotThrow(() -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main(new String[]{artifact.toString()})); + } + + @Test + void rejectsSafeLocalCseReviewReadyWithoutProofs() throws Exception { + Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + Files.writeString(artifact, failClosedEvidence( + "pending-manual-review", + "true", + "approval-template-pending", + "1", + "1" + ) + safeLocalCseMaterializationEvidence( + "review-ready", + "none", + "1", + "1", + "1", + "false", + "true" + )); + + assertThrows( + IllegalStateException.class, + () -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main(new String[]{artifact.toString()}) + ); + } + + @Test + void acceptsTypedDeadCodeReviewReadyEvidenceAsManualReviewOnly() throws Exception { + Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + Files.writeString(artifact, failClosedEvidence( + "pending-manual-review", + "true", + "approval-template-pending", + "1", + "1" + ) + typedDeadCodeMaterializationEvidence( + "review-ready", + "none", + "3", + "3", + "1", + "1", + "true" + )); + + assertDoesNotThrow(() -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main(new String[]{artifact.toString()})); + } + + @Test + void rejectsTypedDeadCodeReviewReadyWithoutSideEffectProof() throws Exception { + Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + Files.writeString(artifact, failClosedEvidence( + "pending-manual-review", + "true", + "approval-template-pending", + "1", + "1" + ) + typedDeadCodeMaterializationEvidence( + "review-ready", + "none", + "3", + "3", + "1", + "1", + "false" + )); + + assertThrows( + IllegalStateException.class, + () -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main(new String[]{artifact.toString()}) + ); + } + private static String failClosedEvidence( String status, String required, @@ -155,4 +448,261 @@ private static String failClosedEvidence( "" ); } + + private static String madFmaMaterializationEvidence( + String status, + String firstBlocker, + String transformedNodeCount, + String payloadPresentCount, + String payloadPassedCount, + String fastMathAllowed + ) { + return String.join("\n", + "madFmaMaterialization.pass.count=1", + "madFmaMaterialization.candidate.count=1", + "madFmaMaterialization.transformedNode.count=" + transformedNodeCount, + "madFmaMaterialization.changedMethodBody.count=1", + "madFmaMaterialization.bodyTextReplacement.count=1", + "madFmaMaterialization.fixedPoint.pass.count=1", + "madFmaMaterialization.skipped.fastMathPolicy.count=0", + "madFmaMaterialization.skipped.bodyTextPatternMissing.count=0", + "madFmaMaterialization.runtimeEquivalenceRequiredBeforeSelection=true", + "madFmaMaterialization.runtimeEquivalencePayloadRequired=true", + "madFmaMaterialization.runtimeEquivalencePayloadPresent.count=" + payloadPresentCount, + "madFmaMaterialization.runtimeEquivalencePassed.count=" + payloadPassedCount, + "madFmaMaterialization.approvalRequiredBeforeProduction=true", + "madFmaMaterialization.fastMathAllowed=" + fastMathAllowed, + "madFmaMaterialization.status=" + status, + "madFmaMaterialization.firstBlocker=" + firstBlocker, + "" + ); + } + + private static String clampMaterializationEvidence( + String status, + String firstBlocker, + String transformedNodeCount, + String payloadPresentCount, + String payloadPassedCount, + String strictFloatPreserved, + String argumentOrderPreserved, + String fastMathRequired + ) { + return intrinsicMaterializationEvidence( + "clampMaterialization", + status, + firstBlocker, + "1", + transformedNodeCount, + payloadPresentCount, + payloadPassedCount + ) + String.join("\n", + "clampMaterialization.strictFloatPreserved=" + strictFloatPreserved, + "clampMaterialization.argumentOrderPreserved=" + argumentOrderPreserved, + "clampMaterialization.fastMathRequired=" + fastMathRequired, + "" + ); + } + + private static String stepMaterializationEvidence( + String status, + String firstBlocker, + String transformedNodeCount, + String payloadPresentCount, + String payloadPassedCount, + String directStepCount, + String invertedStepCount, + String strictFloatPreserved, + String strictComparisonPreserved, + String equalityBehaviorPreserved, + String nanComparisonPreserved, + String fastMathRequired + ) { + return intrinsicMaterializationEvidence( + "stepMaterialization", + status, + firstBlocker, + "2", + transformedNodeCount, + payloadPresentCount, + payloadPassedCount + ) + String.join("\n", + "stepMaterialization.directStep.count=" + directStepCount, + "stepMaterialization.invertedStep.count=" + invertedStepCount, + "stepMaterialization.strictFloatPreserved=" + strictFloatPreserved, + "stepMaterialization.strictComparisonPreserved=" + strictComparisonPreserved, + "stepMaterialization.equalityBehaviorPreserved=" + equalityBehaviorPreserved, + "stepMaterialization.nanComparisonPreserved=" + nanComparisonPreserved, + "stepMaterialization.fastMathRequired=" + fastMathRequired, + "" + ); + } + + private static String mixMaterializationEvidence( + String status, + String firstBlocker, + String transformedNodeCount, + String payloadPresentCount, + String payloadPassedCount, + String canonicalMixCount, + String expandedMixCount, + String madExpandedMixCount, + String fastMathAllowed, + String fastMathRequired, + String strictFloatPreserved, + String algebraicReassociationRequired, + String mixArgumentOrderPreserved + ) { + return intrinsicMaterializationEvidence( + "mixMaterialization", + status, + firstBlocker, + "3", + transformedNodeCount, + payloadPresentCount, + payloadPassedCount + ) + String.join("\n", + "mixMaterialization.canonicalMix.count=" + canonicalMixCount, + "mixMaterialization.expandedMix.count=" + expandedMixCount, + "mixMaterialization.madExpandedMix.count=" + madExpandedMixCount, + "mixMaterialization.fastMathAllowed=" + fastMathAllowed, + "mixMaterialization.fastMathRequired=" + fastMathRequired, + "mixMaterialization.strictFloatPreserved=" + strictFloatPreserved, + "mixMaterialization.algebraicReassociationRequired=" + algebraicReassociationRequired, + "mixMaterialization.mixArgumentOrderPreserved=" + mixArgumentOrderPreserved, + "" + ); + } + + private static String intrinsicMaterializationEvidence( + String prefix, + String status, + String firstBlocker, + String candidateCount, + String transformedNodeCount, + String payloadPresentCount, + String payloadPassedCount + ) { + return String.join("\n", + prefix + ".pass.count=1", + prefix + ".candidate.count=" + candidateCount, + prefix + ".transformedNode.count=" + transformedNodeCount, + prefix + ".changedMethodBody.count=1", + prefix + ".bodyTextReplacement.count=" + transformedNodeCount, + prefix + ".fixedPoint.pass.count=1", + prefix + ".skipped.typedBodyMissing.count=0", + prefix + ".skipped.unsupportedFormat.count=0", + prefix + ".skipped.fastMathPolicy.count=0", + prefix + ".skipped.missingChildReference.count=0", + prefix + ".skipped.unsupportedShape.count=0", + prefix + ".skipped.bodyTextPatternMissing.count=0", + prefix + ".runtimeEquivalenceRequiredBeforeSelection=true", + prefix + ".runtimeEquivalencePayloadRequired=true", + prefix + ".runtimeEquivalencePayloadPresent.count=" + payloadPresentCount, + prefix + ".runtimeEquivalencePassed.count=" + payloadPassedCount, + prefix + ".approvalRequiredBeforeProduction=true", + prefix + ".status=" + status, + prefix + ".firstBlocker=" + firstBlocker, + "" + ); + } + + private static String safeLocalCseMaterializationEvidence( + String status, + String firstBlocker, + String transformedNodeCount, + String payloadPresentCount, + String payloadPassedCount, + String dominanceProven, + String sideEffectFreedomProven + ) { + return String.join("\n", + "safeLocalCseMaterialization.pass.count=1", + "safeLocalCseMaterialization.localBinding.count=1", + "safeLocalCseMaterialization.candidate.count=1", + "safeLocalCseMaterialization.transformedNode.count=" + transformedNodeCount, + "safeLocalCseMaterialization.changedMethodBody.count=1", + "safeLocalCseMaterialization.bodyTextReplacement.count=1", + "safeLocalCseMaterialization.fixedPoint.pass.count=1", + "safeLocalCseMaterialization.skipped.controlFlowBoundary.count=0", + "safeLocalCseMaterialization.skipped.unsupportedOperator.count=0", + "safeLocalCseMaterialization.skipped.impureOperand.count=0", + "safeLocalCseMaterialization.skipped.bodyTextPatternMissing.count=0", + "safeLocalCseMaterialization.runtimeEquivalenceRequiredBeforeSelection=true", + "safeLocalCseMaterialization.runtimeEquivalencePayloadRequired=true", + "safeLocalCseMaterialization.runtimeEquivalencePayloadPresent.count=" + payloadPresentCount, + "safeLocalCseMaterialization.runtimeEquivalencePassed.count=" + payloadPassedCount, + "safeLocalCseMaterialization.approvalRequiredBeforeProduction=true", + "safeLocalCseMaterialization.dominanceProven=" + dominanceProven, + "safeLocalCseMaterialization.sideEffectFreedomProven=" + sideEffectFreedomProven, + "safeLocalCseMaterialization.status=" + status, + "safeLocalCseMaterialization.firstBlocker=" + firstBlocker, + "" + ); + } + + private static String loopVectorizationMaterializationEvidence( + String status, + String firstBlocker, + String transformedLoopCount, + String payloadPresentCount, + String payloadPassedCount, + String loopTripCountProven, + String contiguousLoadProven, + String orderedReductionPreserved + ) { + return String.join("\n", + "loopVectorizationMaterialization.pass.count=1", + "loopVectorizationMaterialization.candidate.count=1", + "loopVectorizationMaterialization.transformedLoop.count=" + transformedLoopCount, + "loopVectorizationMaterialization.changedMethodBody.count=1", + "loopVectorizationMaterialization.bodyTextReplacement.count=1", + "loopVectorizationMaterialization.typedBody.materialized.count=1", + "loopVectorizationMaterialization.typedBody.invalidated.count=0", + "loopVectorizationMaterialization.skipped.loopShape.count=0", + "loopVectorizationMaterialization.skipped.unsupportedWidth.count=0", + "loopVectorizationMaterialization.skipped.unsafeLoadPattern.count=0", + "loopVectorizationMaterialization.runtimeEquivalenceRequiredBeforeSelection=true", + "loopVectorizationMaterialization.runtimeEquivalencePayloadRequired=true", + "loopVectorizationMaterialization.runtimeEquivalencePayloadPresent.count=" + payloadPresentCount, + "loopVectorizationMaterialization.runtimeEquivalencePassed.count=" + payloadPassedCount, + "loopVectorizationMaterialization.approvalRequiredBeforeProduction=true", + "loopVectorizationMaterialization.loopTripCountProven=" + loopTripCountProven, + "loopVectorizationMaterialization.contiguousLoadProven=" + contiguousLoadProven, + "loopVectorizationMaterialization.orderedReductionPreserved=" + orderedReductionPreserved, + "loopVectorizationMaterialization.status=" + status, + "loopVectorizationMaterialization.firstBlocker=" + firstBlocker, + "" + ); + } + + private static String typedDeadCodeMaterializationEvidence( + String status, + String firstBlocker, + String unreachableNodeCount, + String removedNodeCount, + String payloadPresentCount, + String payloadPassedCount, + String sideEffectFreedomProven + ) { + return String.join("\n", + "typedDeadCodeMaterialization.pass.count=1", + "typedDeadCodeMaterialization.node.count=5", + "typedDeadCodeMaterialization.unreachableNode.count=" + unreachableNodeCount, + "typedDeadCodeMaterialization.removedNode.count=" + removedNodeCount, + "typedDeadCodeMaterialization.changedMethodBody.count=1", + "typedDeadCodeMaterialization.blocked.missingRoot.count=0", + "typedDeadCodeMaterialization.blocked.missingChildReference.count=0", + "typedDeadCodeMaterialization.blocked.sideEffectingUnreachableNode.count=0", + "typedDeadCodeMaterialization.runtimeEquivalenceRequiredBeforeSelection=true", + "typedDeadCodeMaterialization.runtimeEquivalencePayloadRequired=true", + "typedDeadCodeMaterialization.runtimeEquivalencePayloadPresent.count=" + payloadPresentCount, + "typedDeadCodeMaterialization.runtimeEquivalencePassed.count=" + payloadPassedCount, + "typedDeadCodeMaterialization.approvalRequiredBeforeProduction=true", + "typedDeadCodeMaterialization.sideEffectFreedomProven=" + sideEffectFreedomProven, + "typedDeadCodeMaterialization.status=" + status, + "typedDeadCodeMaterialization.firstBlocker=" + firstBlocker, + "" + ); + } } diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java index d468ba43..b6d8289e 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java @@ -1472,13 +1472,24 @@ void validationReporterWritesKernelLaunchAdvisoryReportAndWorkflowFragment() thr assertTrue(reportMarkdown.contains("- Constant folding preview passes: `1`")); assertTrue(reportMarkdown.contains("- Constant folding preview candidates: `2`")); assertTrue(reportMarkdown.contains("- Constant folding preview skipped blockers: `2`")); + assertTrue(reportMarkdown.contains("- Constant folding materialization status: `not-recorded`")); + assertTrue(reportMarkdown.contains("- Constant folding materialized nodes: `0`")); + assertTrue(reportMarkdown.contains("- Constant folding materialization literal rewrites: `0`")); + assertTrue(reportMarkdown.contains("- Constant folding materialization identity rewrites: `0`")); + assertTrue(reportMarkdown.contains("- Constant folding materialization first blocker: `not-recorded`")); assertTrue(reportMarkdown.contains("- Safe local CSE preview passes: `1`")); assertTrue(reportMarkdown.contains("- Safe local CSE preview candidate expressions: `3`")); assertTrue(reportMarkdown.contains("- Safe local CSE preview duplicate expressions: `1`")); assertTrue(reportMarkdown.contains("- Safe local CSE preview blockers: `2`")); + assertTrue(reportMarkdown.contains("- Safe local CSE materialization status: `not-recorded`")); + assertTrue(reportMarkdown.contains("- Safe local CSE materialized nodes: `0`")); + assertTrue(reportMarkdown.contains("- Safe local CSE materialization first blocker: `not-recorded`")); assertTrue(reportMarkdown.contains("- Typed dead-code preview passes: `1`")); assertTrue(reportMarkdown.contains("- Typed dead-code preview unreachable nodes: `2`")); assertTrue(reportMarkdown.contains("- Typed dead-code preview blockers: `2`")); + assertTrue(reportMarkdown.contains("- Typed dead-code materialization status: `not-recorded`")); + assertTrue(reportMarkdown.contains("- Typed dead-code materialization removed nodes: `0`")); + assertTrue(reportMarkdown.contains("- Typed dead-code materialization first blocker: `not-recorded`")); assertTrue(reportMarkdown.contains("- Preview readiness status: `blocked-by-proof`")); assertTrue(reportMarkdown.contains("- Preview readiness families: `constant-folding=blocked-by-proof, safe-local-cse=blocked-by-proof, typed-dead-code=blocked-by-proof`")); assertTrue(reportMarkdown.contains("- Preview readiness recorded families: `3`")); @@ -1496,7 +1507,7 @@ void validationReporterWritesKernelLaunchAdvisoryReportAndWorkflowFragment() thr assertTrue(reportMarkdown.contains("- Review package manual review only: `true`")); assertTrue(reportMarkdown.contains("- Review package production mutation: `disabled`")); assertTrue(reportMarkdown.contains("- Review package selected IR replacement: `disabled`")); - assertTrue(reportMarkdown.contains("| `workload/perlin.cl` | `recorded` | `1` | `1` | `0` | `0` | `0` | `0` | `not-recorded` | `no-candidates` | `no-candidates` | `2` | `2` | `3` | `1` | `2` | `2` | `2` | `not-recorded` | `none` | `ir-optimizer:text-canonicalization:1=1` |")); + assertTrue(reportMarkdown.contains("| `workload/perlin.cl` | `recorded` | `1` | `1` | `0` | `0` | `0` | `0` | `not-recorded` | `no-candidates` | `no-candidates` | `2` | `2` | `0` | `not-recorded` | `3` | `1` | `2` | `0` | `not-recorded` | `2` | `2` | `0` | `not-recorded` | `not-recorded` | `none` | `ir-optimizer:text-canonicalization:1=1` |")); assertTrue(reportMarkdown.contains("ir-optimizer:text-canonicalization:1=1")); assertTrue(summaryMarkdown.contains("## Kernel Launch Advisories")); assertTrue(summaryMarkdown.contains("- Blocking: `0`")); From 58eab1f23c45fc350b5304b51ea0e2679c0c5526 Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Tue, 14 Jul 2026 23:17:27 +0300 Subject: [PATCH 27/32] feat: Add Clamp, mix, step optimization & local constant folding --- ROADMAP.md | 14 +- docs/API-Overview.md | 6 +- docs/Getting-Started.md | 2 +- docs/IR-Optimizer.md | 3 + docs/Public-API-And-Extension-Contract.md | 11 +- ...rClampMaterializationProposalProvider.java | 109 ++------ ...MadFmaMaterializationProposalProvider.java | 109 ++------ ...uIrMixMaterializationProposalProvider.java | 109 ++------ .../GpuIrOptimizationProposalProvider.java | 18 ++ .../GpuIrProposalRuntimeBridgePass.java | 86 ++++++ ...IrStepMaterializationProposalProvider.java | 122 +++------ .../iroptimizer/GpuIrTypedBodyGraphPatch.java | 241 +++++++++++++++++ .../GpuIrProposalRuntimeBridgePassTest.java | 245 ++++++++++++++++++ .../GpuIrTypedBodyGraphPatchTest.java | 184 +++++++++++++ .../api/annotations/GPUOptimize.java | 52 +++- .../frontend/GpuFrontendService.java | 57 +++- .../ir/artifact/IrGpuArtifactParser.java | 10 + .../ir/artifact/IrGpuArtifactSerializer.java | 10 + .../IrGpuOptimizerPolicyMetadata.java | 125 ++++++++- .../GpuRuntimeCompileArtifactDumper.java | 91 +++++++ .../GpuRuntimeIrOptimizationRequest.java | 12 + .../runtime/GpuRuntimeIrPeepholePass.java | 10 + ...enClRuntimeIrOptimizerEvidenceSummary.java | 100 +++++++ .../processors/GpuCompilerProcessorTest.java | 131 ++++++++++ .../GpuRuntimeIrOptimizerRegistryTest.java | 2 + ...RuntimeIrOptimizerEvidenceSummaryTest.java | 34 +++ 26 files changed, 1540 insertions(+), 353 deletions(-) create mode 100644 ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedBodyGraphPatch.java create mode 100644 ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedBodyGraphPatchTest.java diff --git a/ROADMAP.md b/ROADMAP.md index f56fb75e..60e8681a 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -452,11 +452,11 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i `GpuRuntimeIrPeepholeArtifactProofBindingPreflight` now adds the proof-binding boundary above that envelope. It records proof/review blockers, runtime-equivalence payload completeness, rollback evidence, approval state, ready/blocked counts, and disabled proof / rollback / approval binding plus optimized-artifact / transformed-IR / mutation / selected-IR flags, so the future artifact handoff cannot be mistaken for accepted proof. `GpuRuntimeIrPeepholeOptimizedArtifactSelectionPreflight` now adds the final read-only selection boundary above proof binding. It records proof-binding readiness, production-gate requirement/acceptance, mutation-policy allowance, ready/blocked counts, first blocker, and disabled selection / optimized-artifact-selected / mutation / selected-IR flags so the current selected runtime IR cannot change accidentally. - [x] Add Java-source fast-math policy metadata for runtime optimizer decisions. - `@GPUOptimize(fastMath = false)` is now the production-safe default policy surface. Java-source frontend artifacts persist the explicit annotation source or `default-strict`, runtime optimization requests expose the resolved policy, and optimizer hooks can declare `requiresFastMath()` so the registry skips them without invocation unless the method explicitly opts in. This does not enable production mutation by itself. + `@GPUOptimize(fastMath = false)` is now the production-safe default policy surface. Java-source frontend artifacts persist the explicit annotation source or `default-strict`, runtime optimization requests expose the resolved policy, and optimizer hooks can declare `requiresFastMath()` so the registry skips them without invocation unless the method explicitly opts in. The policy metadata now also carries enablement/profile hints, family allow/deny lists, journal/dump hints, production intent, vendor adaptation, vectorization preference, and resource-shaping intent. This does not enable production mutation by itself. - [x] Preserve fast-math policy through the ASM frontend. - Structured ASM ingestion now writes the same `IrGpuOptimizerPolicyMetadata` contract as Java source. Missing ASM policy resolves to `default-strict`, the serialized `kernel.irgpu.properties` manifest carries `optimizerPolicy.fastMath=false` and `optimizerPolicy.source=default-strict`, and parser round-trip coverage pins the storage/runtime boundary. Future ASM compile options may opt in only through the same shared metadata and evidence gates. + Structured ASM ingestion now writes the same `IrGpuOptimizerPolicyMetadata` contract as Java source. Missing ASM policy resolves to `default-strict`, the serialized `kernel.irgpu.properties` manifest carries strict/default optimizer policy fields, and parser round-trip coverage pins the storage/runtime boundary. Future ASM compile options may opt in only through the same shared metadata and evidence gates. - [x] Persist fast-math and optimizer-policy metadata in `IrGpu` storage. - `kernel.irgpu.properties` now carries `optimizerPolicy.fastMath` and `optimizerPolicy.source`, with backward-compatible strict defaults in the parser. Runtime optimizer requests read the loaded `IrGpu` policy, and passes marked `requiresFastMath()` are fail-closed/skipped when fast math is false or absent. Future rule-family toggles remain open. + `kernel.irgpu.properties` now carries the expanded `optimizerPolicy.*` metadata set, including `fastMath`, `enabled`, `profile`, family allow/deny lists, journal/dump hints, production intent, vendor adaptation, vectorization preference, resource shaping, and `source`, with backward-compatible strict defaults in the parser. Runtime optimizer requests read the loaded `IrGpu` policy, and passes marked `requiresFastMath()` are fail-closed/skipped when fast math is false or absent. Applying family toggles to provider execution remains a separate gating task. - [x] Add read-only proof, runtime-equivalence, and rollback gates for peephole rewrites. `GpuRuntimeIrPeepholeRewriteProofReadiness` now exposes the read-only proof preflight above selection readiness: proof required/accepted state, runtime-equivalence payload presence/completeness, rollback evidence, approval acceptance, original IR identity, absent transformed IR identity, and disabled mutation / selected-IR replacement. `GpuRuntimeIrPeepholeRewriteReviewPackage` now sits above that as the package boundary: it records required/complete status, first blocker, conflict count, proof acceptance, runtime-equivalence payload, rollback, approval, manual-review-only state, and disabled mutation / selection / selected-IR replacement. The pass now emits these gates both at aggregate level and per rule through `rule.N.rewriteSelection.*`, `rule.N.rewriteProof.*`, and `rule.N.rewriteReviewPackage.*`, so drift artifacts can distinguish a rule blocked by structural validation from one blocked by missing runtime-equivalence payload, rollback evidence, approval, or incomplete review packaging. Real rewrites still must emit accepted proof artifacts with optimizer family (`peephole`, `instcombine`, or per-rule families), rule counters, fast-math policy, original/transformed IR identities, and compact/durable runtime-equivalence payload evidence before this item can close. Failed proofs, incomplete pattern rewrites, runtime-equivalence failures, rollback diagnostics, or incomplete review packages must keep the original IR selected. - [ ] Bind accepted proof, runtime-equivalence, and rollback evidence to materialized peephole rewrites. @@ -728,6 +728,8 @@ Current rule: `ir-validation` remains strictly read-only. Optimizer modules may `GpuIrStepMaterializationProposalProvider` now rewrites typed `1.0F` / `0.0F` ternary masks over `<`, `<=`, `>`, and `>=` comparisons into OpenCL `step` review candidates. Inclusive comparisons use direct `step(edge, x)` forms; strict `>` / `<` comparisons use `1.0F - step(...)` to preserve equality and NaN behavior. The provider updates typed body and `ir-text-v1` together, emits runtime-equivalence payload fields, and keeps selected runtime IR unchanged. - [x] Materialize interpolation trees as review-only `mix` optimized artifacts. `GpuIrMixMaterializationProposalProvider` now rewrites typed canonical interpolation trees to OpenCL `mix(a, b, t)` review candidates. It also recognizes expanded weighted and generated `mad(b, t, (1.0F - t) * a)` forms when fast-math is allowed, including the optimizer-journal density shape, and records whether algebraic reassociation was required. Typed body and `ir-text-v1` are updated together while selected runtime IR remains unchanged. +- [x] Seed the shared typed-body graph-patch scaffold for peephole materializers. + `GpuIrTypedBodyGraphPatch` now centralizes deterministic typed-node lookup, reachable-node scanning, next-node allocation, root replacement, append-only auxiliary node insertion, intrinsic-call node construction, a fail-closed `Plan.apply(...)` path that validates typed-root and text-pattern availability before applying text + typed-body patches together, and shared patch-blocker taxonomy for provider counters. The review-only `clamp`, `step`, `mix`, and `mad/fma` materializers now use this scaffold for transactional patch mechanics while preserving their matcher logic, runtime-equivalence evidence, rollback behavior, and original selected IR. - [ ] Materialize typed peephole rewrites into optimized review artifacts. Add the shared graph rewrite engine for the remaining rule families and broader generalized materialization: allocate deterministic node ids, build intrinsic replacement nodes, apply graph patches, rebuild a distinct optimized `IrGpu` artifact, regenerate review backend source, and preserve rollback to original IR on any validation or evidence failure. `mad/fma`, `clamp`, `step`, `mix`, safe-local-CSE, constant folding, typed-DCE, and the first loop-vectorization slice currently use dedicated conservative materializers rather than a shared graph-patch engine. - [ ] Broaden peephole rules from canonical shapes to real generated forms. @@ -736,8 +738,10 @@ Current rule: `ir-validation` remains strictly read-only. Optimizer modules may Provide an opt-in journal that records the path from program launch through descriptor loading, `IrGpu` loading, validation, optimizer-provider discovery, proposal execution, runtime-equivalence/review-package state, backend source selection, backend compilation, fallback/rollback, invocation, and shutdown. The journal should be useful for debugging without changing default runtime behavior. - [ ] Promote optional `ir-optimizer` participation into the full runtime path when present. Keep no-module and `optimizerProfile=off` behavior unchanged, but make the module-present diagnostic/review path explicit and documented: when enabled, the runtime should run optimizer proposals, dump original/optimized/selected artifacts, validate evidence, and still select original IR unless future production gates accept mutation. -- [ ] Expand `@GPUOptimize` into a richer method-level optimizer policy. - The annotation currently carries `fastMath` only. Future controls should cover optimizer enablement/profile, per-family toggles (`mad/fma`, `clamp`, `step`, `mix`, `dot`, vectorization, CSE, constant folding, private-array scalarization), review-only versus production intent, vectorization preference, vendor adaptation, register-pressure/resource shaping, and optional journal/dump hints while staying fail-closed by default. +- [x] Expand `@GPUOptimize` into a richer method-level optimizer policy metadata surface. + `@GPUOptimize` now records method-level optimizer enablement, profile hints, `fastMath`, enabled/disabled optimizer families, optional journal and artifact-dump hints, production-selection intent, vendor adaptation, vectorization preference, and resource/register-pressure shaping intent. The Java-source frontend persists the expanded `IrGpuOptimizerPolicyMetadata` into `kernel.irgpu.properties`, the parser keeps backward-compatible strict defaults for older manifests, runtime optimization requests expose the resolved policy, and proposal bridge context carries the new metadata as evidence/context. This remains fail-closed metadata only; it does not enable production mutation, selected IR replacement, or optimized backend source selection. +- [x] Wire rich `@GPUOptimize` family policy into optimizer-provider gating. + The proposal runtime bridge now evaluates persisted `enabledFamilies` / `disabledFamilies` before invoking a provider. Explicit `@GPUOptimize(enabled = false)` skips provider execution, deny-list entries take precedence, allow-lists restrict provider families only when non-empty, and every policy skip emits `ir-optimizer.policy-gate` proof fields with provider family, source policy, skip reason, disabled mutation, disabled selection, and original IR identity. Runtime dump evidence now aggregates those skips into `runtime-ir-optimizer-evidence.properties` / markdown summary fields such as `policyGate.skipped.count`, blocker counts, provider-invocation count, first blocker, and family summary. Missing/default-strict policy keeps the existing review pipeline behavior unchanged, and selected runtime IR remains original unless future production gates explicitly allow otherwise. - [ ] Evaluate the pre-existing private-array scalarizer prototype as an optimizer family. The attached prototype code is a useful starting point for a future `private-array-scalarization` provider, but it needs to be ported into the current immutable proposal + typed-body contract, use conservative side-effect/index proofs, emit before/after review artifacts, and remain blocked from production selection until runtime-equivalence and rollback evidence exist. diff --git a/docs/API-Overview.md b/docs/API-Overview.md index d5a6f6f4..fac0af90 100644 --- a/docs/API-Overview.md +++ b/docs/API-Overview.md @@ -18,7 +18,7 @@ Use these in source code that should compile to GPU code. - `@GPUWorkGroupSizeHint` declares a portable preferred work-group size hint for backends that support it. - `@GPUVectorTypeHint` declares a portable preferred vector type hint for backend lowerers that use it. - `@GPUPacked`, `@GPUAligned`, and `@GPUAlwaysInline` cover common layout and helper emission metadata without raw backend strings. -- `@GPUOptimize` records method-level optimizer policy such as `fastMath`; the default remains strict. +- `@GPUOptimize` records method-level optimizer policy such as enablement/profile hints, `fastMath`, family toggles, journal/dump hints, production intent, vendor adaptation, vectorization preference, and resource-shaping intent; the default remains strict and fail-closed. - `@GPUDeviceConstraint` restricts a method to supported backends, vendors, device classes, and required runtime features. - `@GPUFallbackVariant` groups ABI-compatible implementations that runtime may choose for different devices. - `@GPUStruct` marks a Java class as a value type that can be marshalled to OpenCL struct layout. @@ -34,12 +34,14 @@ Prefer portable annotations first. Use raw attributes only for backend-specific @GPU @GPUWorkGroupSize(x = 8, y = 8, z = 1) @GPUWorkGroupSizeHint(x = 8, y = 8, z = 1) -@GPUOptimize(fastMath = false) +@GPUOptimize(fastMath = false, enabledFamilies = {"clamp", "step", "mix"}) static void kernel(@GPUGlobal float[] output) { output[GPU.get_global_id(0)] = 1.0f; } ``` +`@GPUOptimize` records intent in the generated `IrGpu` manifest. It does not by itself enable production mutation, selected IR replacement, or optimized backend source selection. + Use a device constraint when a method requires specific hardware capabilities: ```java diff --git a/docs/Getting-Started.md b/docs/Getting-Started.md index 6cd5f6f3..8a306a69 100644 --- a/docs/Getting-Started.md +++ b/docs/Getting-Started.md @@ -50,7 +50,7 @@ Keep first kernels simple: - Use portable annotations such as `@GPUWorkGroupSize`, `@GPUWorkGroupSizeHint`, `@GPUVectorTypeHint`, `@GPUPacked`, `@GPUAligned`, `@GPUAlwaysInline`, and `@GPUOptimize` before raw backend attributes. - Avoid object allocation, exceptions, recursion, virtual dispatch, and heap-heavy Java patterns inside kernels. -`@GPUOptimize(fastMath = false)` is the safe default. Use `fastMath = true` only when you explicitly allow future proof-backed optimizer passes to use non-strict floating-point rewrites. +`@GPUOptimize(fastMath = false)` is the safe default. Use `fastMath = true` only when you explicitly allow proof-backed optimizer passes to use non-strict floating-point rewrites. The annotation can also record optional optimizer intent such as `profile`, `enabledFamilies`, `disabledFamilies`, `journal`, `dumpArtifacts`, `vendorAdaptation`, `vectorization`, and `resourceShaping`; family toggles gate optional optimizer-provider participation, while mutation and optimized-source selection remain fail-closed unless runtime evidence and selection gates allow more. ## 3. Run The Kernel diff --git a/docs/IR-Optimizer.md b/docs/IR-Optimizer.md index 59efe6de..812224b5 100644 --- a/docs/IR-Optimizer.md +++ b/docs/IR-Optimizer.md @@ -8,6 +8,8 @@ The current module is intentionally proposal-first. It contributes ServiceLoader The first alpha is an evidence and contract boundary, not a production rewrite release. It includes the optional `ir-optimizer` / `ir-vendor-optimizer` module split, immutable proposal contracts, the validation sandwich, preview-only optimizer families, backend-neutral source materialization, constrained review-only constant-folding materialization, review-only safe-local-CSE materialization that reuses an existing local value, review-only fast-math `mad/fma` materialization, review-only `clamp` materialization for generated `min(max(x, lo), hi)` shapes, review-only `step` materialization for ternary masks, review-only `mix` materialization for interpolation shapes, review-only fixed-width loop-vectorization materialization, typed dead-code materialization for unreachable pure typed nodes, approval-template metadata, optimized-artifact candidate envelopes, and OpenCL evidence/report/validator guardrails. +The first shared typed-body patch scaffold is `GpuIrTypedBodyGraphPatch`. It owns deterministic typed-node lookup, reachable-node scanning, next-node allocation, root replacement, append-only auxiliary node insertion, intrinsic-call node construction, fail-closed `Plan.apply(...)` validation for text + typed-body patches, and shared patch-blocker taxonomy for provider counters. `clamp`, `step`, `mix`, and `mad/fma` use it for transactional patch mechanics while their matcher logic, runtime-equivalence evidence, and fail-closed selected-IR behavior remain provider-owned. + It intentionally excludes runtime IR selection, selected-IR replacement, production mutation, real vendor rewrites, and any automatic choice of optimized artifacts. Those remain future gates above runtime-equivalence, rollback, approval, and production-promotion evidence. ## Contract @@ -18,6 +20,7 @@ It intentionally excludes runtime IR selection, selected-IR replacement, product - Optimizer passes must not mutate the input artifact in place. - Real rewrites must return a distinct optimized artifact, diagnostics, proof links, and rollback evidence. - Missing optimizer modules, missing approval, failed proof, or disabled optimization must leave the original IR selected. +- `@GPUOptimize` policy metadata is available to proposal providers through the runtime bridge context, including profile hints, fast-math permission, enabled/disabled family lists, journal/dump hints, production intent, vendor adaptation, vectorization preference, and resource-shaping intent. The runtime bridge now uses `enabledFamilies` / `disabledFamilies` and explicit `enabled = false` to gate provider invocation. Policy skips emit `ir-optimizer.policy-gate` proof fields, are aggregated into `runtime-ir-optimizer-evidence.properties` / markdown `policyGate.*` counters, and keep mutation, optimized-artifact selection, and selected-IR replacement disabled. ## Current Skeleton diff --git a/docs/Public-API-And-Extension-Contract.md b/docs/Public-API-And-Extension-Contract.md index 56c46e42..21cf02a6 100644 --- a/docs/Public-API-And-Extension-Contract.md +++ b/docs/Public-API-And-Extension-Contract.md @@ -183,14 +183,19 @@ Method-level optimizer intent should be declared with `@GPUOptimize`. The defaul ```java @GPU -@GPUOptimize(fastMath = false) +@GPUOptimize(fastMath = false, enabledFamilies = {"clamp", "step", "mix"}) static void strictKernel(@GPUGlobal float[] output) { output[GPU.get_global_id(0)] = 1.0f; } ``` -`fastMath = true` only records permission for future proof-backed rewrites. It does not bypass runtime-equivalence, -rollback, or production-promotion gates. +`@GPUOptimize` persists method-level optimizer metadata into the generated `IrGpu` manifest: enablement/profile hints, +`fastMath`, enabled/disabled optimizer families, optional journal and artifact-dump hints, production intent, vendor +adaptation, vectorization preference, and resource/register-pressure shaping intent. The optional optimizer bridge uses +`enabledFamilies` / `disabledFamilies` and explicit `enabled = false` to decide provider participation, with policy-skip +evidence recorded in optimizer reports and aggregated under `policyGate.*` in runtime optimizer evidence summaries. +`fastMath = true` only records permission for proof-backed non-strict rewrites, and `productionIntent = true` only records intent. Neither bypasses runtime-equivalence, approval, rollback, or +production-promotion gates. Any mutating runtime optimizer, peephole pass, vendor rewrite, or third-party optimization hook must produce evidence before it can affect production code: diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrClampMaterializationProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrClampMaterializationProposalProvider.java index fa4fdffd..a323a146 100644 --- a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrClampMaterializationProposalProvider.java +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrClampMaterializationProposalProvider.java @@ -334,10 +334,9 @@ private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody) { break; } ClampCandidate value = candidate.orElseThrow(); - Replacement replacement = replaceFirst(currentBody, value.expressionText(), value.replacementText()); - if (!replacement.replaced()) { - stats.skippedBodyTextPatternMissingCount++; - stats.setFirstBlocker("body-text-pattern-missing"); + GpuIrTypedBodyGraphPatch.Applied patch = graphPatch(value).apply(currentTypedBody, currentBody); + if (!patch.applied()) { + stats.recordPatchBlocker(patch.blocker()); break; } changed = true; @@ -350,8 +349,8 @@ private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody) { stats.firstExpression = value.expressionText(); stats.firstReplacement = value.replacementText(); } - currentBody = replacement.body(); - currentTypedBody = rewriteTypedBody(currentTypedBody, value); + currentBody = patch.body(); + currentTypedBody = patch.typedBody(); } if (!changed) { @@ -370,8 +369,8 @@ private static Optional firstMaterializableCandidate( String body, RewriteStats stats ) { - Map nodesById = nodesById(typedBody); - Set reachableNodeIds = reachableNodeIds(typedBody, nodesById); + Map nodesById = GpuIrTypedBodyGraphPatch.nodesById(typedBody); + Set reachableNodeIds = GpuIrTypedBodyGraphPatch.reachableNodeIds(typedBody, nodesById); for (IrGpuTypedNode node : typedBody.nodes()) { if (!reachableNodeIds.contains(node.id()) || !isCall(node, "min")) { continue; @@ -545,74 +544,20 @@ private static Optional childSourceText( return sourceText(nodesById.get(childId), nodesById); } - private record Replacement(String body, boolean replaced) { - } - - private static Replacement replaceFirst(String body, String expressionText, String replacementText) { - int index = body.indexOf(expressionText); - if (index < 0) { - return new Replacement(body, false); - } - return new Replacement( - body.substring(0, index) + replacementText + body.substring(index + expressionText.length()), - true + private static GpuIrTypedBodyGraphPatch.Plan graphPatch(ClampCandidate candidate) { + return GpuIrTypedBodyGraphPatch.plan( + candidate.expressionText(), + candidate.replacementText(), + GpuIrTypedBodyGraphPatch.intrinsicCall( + candidate.rootNodeId(), + TARGET_INTRINSIC, + "clamp-review", + candidate.argumentNodeIds(), + "clamp-review" + ) ); } - private static IrGpuTypedBody rewriteTypedBody(IrGpuTypedBody typedBody, ClampCandidate candidate) { - ArrayList nodes = new ArrayList<>(); - for (IrGpuTypedNode node : typedBody.nodes()) { - if (node.id() == candidate.rootNodeId()) { - nodes.add(replacementNode(node.id(), candidate)); - continue; - } - nodes.add(node); - } - return new IrGpuTypedBody(typedBody.format(), typedBody.rootNodeIds(), nodes); - } - - private static IrGpuTypedNode replacementNode(int nodeId, ClampCandidate candidate) { - LinkedHashMap attributes = new LinkedHashMap<>(); - attributes.put("name", TARGET_INTRINSIC); - attributes.put("backendName", TARGET_INTRINSIC); - attributes.put("codeTemplate", ""); - attributes.put("receiver.null", "true"); - attributes.put("resultType", "clamp-review"); - attributes.put("argumentTypes.count", "3"); - attributes.put("argumentTypes.0", "clamp-review"); - attributes.put("argumentTypes.1", "clamp-review"); - attributes.put("argumentTypes.2", "clamp-review"); - return new IrGpuTypedNode( - nodeId, - "GpuIrIntrinsicCall", - attributes, - Map.of("arguments", candidate.argumentNodeIds()) - ); - } - - private static Set reachableNodeIds(IrGpuTypedBody typedBody, Map nodesById) { - LinkedHashSet reachable = new LinkedHashSet<>(); - ArrayList pending = new ArrayList<>(typedBody.rootNodeIds()); - while (!pending.isEmpty()) { - int nodeId = pending.remove(pending.size() - 1); - if (!reachable.add(nodeId)) { - continue; - } - IrGpuTypedNode node = nodesById.get(nodeId); - if (node == null) { - continue; - } - for (List childIds : node.children().values()) { - for (Integer childId : childIds) { - if (childId != null && nodesById.containsKey(childId) && !reachable.contains(childId)) { - pending.add(childId); - } - } - } - } - return Set.copyOf(reachable); - } - private static Integer singleChild(IrGpuTypedNode node, String name) { if (node == null) { return null; @@ -648,14 +593,6 @@ private static String firstAttribute(IrGpuTypedNode node, String... names) { return ""; } - private static Map nodesById(IrGpuTypedBody typedBody) { - LinkedHashMap nodes = new LinkedHashMap<>(); - for (IrGpuTypedNode node : typedBody.nodes()) { - nodes.put(node.id(), node); - } - return Map.copyOf(nodes); - } - private static boolean isOpenClReview(GpuIrOptimizationProposalRequest request) { return "OPENCL".equals(request.contextFields().getOrDefault("backendTarget", "")); } @@ -743,6 +680,16 @@ private void recordCandidate(ClampCandidate candidate) { candidateKeys.add(candidate.methodName() + "#" + candidate.rootNodeId()); } + private void recordPatchBlocker(String blocker) { + switch (GpuIrTypedBodyGraphPatch.blockerKind(blocker)) { + case BODY_TEXT_PATTERN_MISSING -> skippedBodyTextPatternMissingCount++; + case TYPED_BODY_MISSING -> skippedTypedBodyMissingCount++; + case TYPED_GRAPH_MISSING -> skippedMissingChildReferenceCount++; + case OTHER -> skippedUnsupportedShapeCount++; + } + setFirstBlocker(blocker); + } + private void add(RewriteStats other) { typedBodyCount += other.typedBodyCount; candidateKeys.addAll(other.candidateKeys); diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMadFmaMaterializationProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMadFmaMaterializationProposalProvider.java index 4e453b4f..b03d2c81 100644 --- a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMadFmaMaterializationProposalProvider.java +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMadFmaMaterializationProposalProvider.java @@ -372,10 +372,9 @@ private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody, boole break; } MadCandidate value = candidate.orElseThrow(); - Replacement replacement = replaceFirst(currentBody, value.expressionText(), value.replacementText()); - if (!replacement.replaced()) { - stats.skippedBodyTextPatternMissingCount++; - stats.setFirstBlocker("body-text-pattern-missing"); + GpuIrTypedBodyGraphPatch.Applied patch = graphPatch(value).apply(currentTypedBody, currentBody); + if (!patch.applied()) { + stats.recordPatchBlocker(patch.blocker()); break; } changed = true; @@ -388,8 +387,8 @@ private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody, boole stats.firstExpression = value.expressionText(); stats.firstReplacement = value.replacementText(); } - currentBody = replacement.body(); - currentTypedBody = rewriteTypedBody(currentTypedBody, value); + currentBody = patch.body(); + currentTypedBody = patch.typedBody(); } if (!changed) { @@ -409,8 +408,8 @@ private static Optional firstMaterializableCandidate( boolean fastMathAllowed, RewriteStats stats ) { - Map nodesById = nodesById(typedBody); - Set reachableNodeIds = reachableNodeIds(typedBody, nodesById); + Map nodesById = GpuIrTypedBodyGraphPatch.nodesById(typedBody); + Set reachableNodeIds = GpuIrTypedBodyGraphPatch.reachableNodeIds(typedBody, nodesById); for (IrGpuTypedNode node : typedBody.nodes()) { if (!reachableNodeIds.contains(node.id()) || !isBinary(node, "+")) { continue; @@ -583,74 +582,20 @@ private static Optional childSourceText( return sourceText(nodesById.get(childId), nodesById); } - private record Replacement(String body, boolean replaced) { - } - - private static Replacement replaceFirst(String body, String expressionText, String replacementText) { - int index = body.indexOf(expressionText); - if (index < 0) { - return new Replacement(body, false); - } - return new Replacement( - body.substring(0, index) + replacementText + body.substring(index + expressionText.length()), - true + private static GpuIrTypedBodyGraphPatch.Plan graphPatch(MadCandidate candidate) { + return GpuIrTypedBodyGraphPatch.plan( + candidate.expressionText(), + candidate.replacementText(), + GpuIrTypedBodyGraphPatch.intrinsicCall( + candidate.rootNodeId(), + TARGET_INTRINSIC, + "fast-math-review", + candidate.argumentNodeIds(), + "fast-math-review" + ) ); } - private static IrGpuTypedBody rewriteTypedBody(IrGpuTypedBody typedBody, MadCandidate candidate) { - ArrayList nodes = new ArrayList<>(); - for (IrGpuTypedNode node : typedBody.nodes()) { - if (node.id() == candidate.rootNodeId()) { - nodes.add(replacementNode(node.id(), candidate)); - continue; - } - nodes.add(node); - } - return new IrGpuTypedBody(typedBody.format(), typedBody.rootNodeIds(), nodes); - } - - private static IrGpuTypedNode replacementNode(int nodeId, MadCandidate candidate) { - LinkedHashMap attributes = new LinkedHashMap<>(); - attributes.put("name", TARGET_INTRINSIC); - attributes.put("backendName", TARGET_INTRINSIC); - attributes.put("codeTemplate", ""); - attributes.put("receiver.null", "true"); - attributes.put("resultType", "fast-math-review"); - attributes.put("argumentTypes.count", "3"); - attributes.put("argumentTypes.0", "fast-math-review"); - attributes.put("argumentTypes.1", "fast-math-review"); - attributes.put("argumentTypes.2", "fast-math-review"); - return new IrGpuTypedNode( - nodeId, - "GpuIrIntrinsicCall", - attributes, - Map.of("arguments", candidate.argumentNodeIds()) - ); - } - - private static Set reachableNodeIds(IrGpuTypedBody typedBody, Map nodesById) { - LinkedHashSet reachable = new LinkedHashSet<>(); - ArrayList pending = new ArrayList<>(typedBody.rootNodeIds()); - while (!pending.isEmpty()) { - int nodeId = pending.remove(pending.size() - 1); - if (!reachable.add(nodeId)) { - continue; - } - IrGpuTypedNode node = nodesById.get(nodeId); - if (node == null) { - continue; - } - for (List childIds : node.children().values()) { - for (Integer childId : childIds) { - if (childId != null && nodesById.containsKey(childId) && !reachable.contains(childId)) { - pending.add(childId); - } - } - } - } - return Set.copyOf(reachable); - } - private static Integer singleChild(IrGpuTypedNode node, String name) { if (node == null) { return null; @@ -683,14 +628,6 @@ private static String firstAttribute(IrGpuTypedNode node, String... names) { return ""; } - private static Map nodesById(IrGpuTypedBody typedBody) { - LinkedHashMap nodes = new LinkedHashMap<>(); - for (IrGpuTypedNode node : typedBody.nodes()) { - nodes.put(node.id(), node); - } - return Map.copyOf(nodes); - } - private static boolean isOpenClReview(GpuIrOptimizationProposalRequest request) { return "OPENCL".equals(request.contextFields().getOrDefault("backendTarget", "")); } @@ -779,6 +716,16 @@ private void recordCandidate(MadCandidate candidate) { candidateKeys.add(candidate.methodName() + "#" + candidate.rootNodeId()); } + private void recordPatchBlocker(String blocker) { + switch (GpuIrTypedBodyGraphPatch.blockerKind(blocker)) { + case BODY_TEXT_PATTERN_MISSING -> skippedBodyTextPatternMissingCount++; + case TYPED_BODY_MISSING -> skippedTypedBodyMissingCount++; + case TYPED_GRAPH_MISSING -> skippedMissingChildReferenceCount++; + case OTHER -> skippedImpureOperandCount++; + } + setFirstBlocker(blocker); + } + private void add(RewriteStats other) { typedBodyCount += other.typedBodyCount; candidateKeys.addAll(other.candidateKeys); diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMixMaterializationProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMixMaterializationProposalProvider.java index a5770df6..4108f9f9 100644 --- a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMixMaterializationProposalProvider.java +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMixMaterializationProposalProvider.java @@ -386,10 +386,9 @@ private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody, boole break; } MixCandidate value = candidate.orElseThrow(); - Replacement replacement = replaceFirst(currentBody, value.expressionText(), value.replacementText()); - if (!replacement.replaced()) { - stats.skippedBodyTextPatternMissingCount++; - stats.setFirstBlocker("body-text-pattern-missing"); + GpuIrTypedBodyGraphPatch.Applied patch = graphPatch(value).apply(currentTypedBody, currentBody); + if (!patch.applied()) { + stats.recordPatchBlocker(patch.blocker()); break; } changed = true; @@ -409,8 +408,8 @@ private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody, boole stats.firstExpression = value.expressionText(); stats.firstReplacement = value.replacementText(); } - currentBody = replacement.body(); - currentTypedBody = rewriteTypedBody(currentTypedBody, value); + currentBody = patch.body(); + currentTypedBody = patch.typedBody(); } if (!changed) { @@ -430,8 +429,8 @@ private static Optional firstMaterializableCandidate( boolean fastMathAllowed, RewriteStats stats ) { - Map nodesById = nodesById(typedBody); - Set reachableNodeIds = reachableNodeIds(typedBody, nodesById); + Map nodesById = GpuIrTypedBodyGraphPatch.nodesById(typedBody); + Set reachableNodeIds = GpuIrTypedBodyGraphPatch.reachableNodeIds(typedBody, nodesById); for (IrGpuTypedNode node : typedBody.nodes()) { if (!reachableNodeIds.contains(node.id())) { continue; @@ -840,74 +839,20 @@ private static Optional childSourceText( return sourceText(nodesById.get(childId), nodesById); } - private record Replacement(String body, boolean replaced) { - } - - private static Replacement replaceFirst(String body, String expressionText, String replacementText) { - int index = body.indexOf(expressionText); - if (index < 0) { - return new Replacement(body, false); - } - return new Replacement( - body.substring(0, index) + replacementText + body.substring(index + expressionText.length()), - true + private static GpuIrTypedBodyGraphPatch.Plan graphPatch(MixCandidate candidate) { + return GpuIrTypedBodyGraphPatch.plan( + candidate.expressionText(), + candidate.replacementText(), + GpuIrTypedBodyGraphPatch.intrinsicCall( + candidate.rootNodeId(), + TARGET_INTRINSIC, + "mix-review", + candidate.argumentNodeIds(), + "mix-review" + ) ); } - private static IrGpuTypedBody rewriteTypedBody(IrGpuTypedBody typedBody, MixCandidate candidate) { - ArrayList nodes = new ArrayList<>(); - for (IrGpuTypedNode node : typedBody.nodes()) { - if (node.id() == candidate.rootNodeId()) { - nodes.add(replacementNode(node.id(), candidate)); - continue; - } - nodes.add(node); - } - return new IrGpuTypedBody(typedBody.format(), typedBody.rootNodeIds(), nodes); - } - - private static IrGpuTypedNode replacementNode(int nodeId, MixCandidate candidate) { - LinkedHashMap attributes = new LinkedHashMap<>(); - attributes.put("name", TARGET_INTRINSIC); - attributes.put("backendName", TARGET_INTRINSIC); - attributes.put("codeTemplate", ""); - attributes.put("receiver.null", "true"); - attributes.put("resultType", "mix-review"); - attributes.put("argumentTypes.count", "3"); - attributes.put("argumentTypes.0", "mix-review"); - attributes.put("argumentTypes.1", "mix-review"); - attributes.put("argumentTypes.2", "mix-review"); - return new IrGpuTypedNode( - nodeId, - "GpuIrIntrinsicCall", - attributes, - Map.of("arguments", candidate.argumentNodeIds()) - ); - } - - private static Set reachableNodeIds(IrGpuTypedBody typedBody, Map nodesById) { - LinkedHashSet reachable = new LinkedHashSet<>(); - ArrayList pending = new ArrayList<>(typedBody.rootNodeIds()); - while (!pending.isEmpty()) { - int nodeId = pending.remove(pending.size() - 1); - if (!reachable.add(nodeId)) { - continue; - } - IrGpuTypedNode node = nodesById.get(nodeId); - if (node == null) { - continue; - } - for (List childIds : node.children().values()) { - for (Integer childId : childIds) { - if (childId != null && nodesById.containsKey(childId) && !reachable.contains(childId)) { - pending.add(childId); - } - } - } - } - return Set.copyOf(reachable); - } - private static Integer singleChild(IrGpuTypedNode node, String name) { if (node == null) { return null; @@ -996,14 +941,6 @@ private static String firstAttribute(IrGpuTypedNode node, String... names) { return ""; } - private static Map nodesById(IrGpuTypedBody typedBody) { - LinkedHashMap nodes = new LinkedHashMap<>(); - for (IrGpuTypedNode node : typedBody.nodes()) { - nodes.put(node.id(), node); - } - return Map.copyOf(nodes); - } - private static boolean isOpenClReview(GpuIrOptimizationProposalRequest request) { return "OPENCL".equals(request.contextFields().getOrDefault("backendTarget", "")); } @@ -1095,6 +1032,16 @@ private void recordCandidate(MixCandidate candidate) { candidateKeys.add(candidate.methodName() + "#" + candidate.rootNodeId()); } + private void recordPatchBlocker(String blocker) { + switch (GpuIrTypedBodyGraphPatch.blockerKind(blocker)) { + case BODY_TEXT_PATTERN_MISSING -> skippedBodyTextPatternMissingCount++; + case TYPED_BODY_MISSING -> skippedTypedBodyMissingCount++; + case TYPED_GRAPH_MISSING -> skippedMissingChildReferenceCount++; + case OTHER -> skippedUnsupportedShapeCount++; + } + setFirstBlocker(blocker); + } + private void add(RewriteStats other) { typedBodyCount += other.typedBodyCount; candidateKeys.addAll(other.candidateKeys); diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalProvider.java index 79e6567b..9f176138 100644 --- a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalProvider.java +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrOptimizationProposalProvider.java @@ -6,6 +6,7 @@ import net.sixik.ga_utils.javatogpu.extension.GpuExtensionPhase; import java.util.Set; +import java.util.Locale; /** * Optional backend-neutral provider that proposes a new IR artifact without mutating the original. @@ -15,6 +16,23 @@ public interface GpuIrOptimizationProposalProvider extends GpuExtension { GpuIrOptimizationProposal propose(GpuIrOptimizationProposalRequest request); + /** + * Stable optimizer-family id used by method-level policy gates such as {@code @GPUOptimize(enabledFamilies = ...)}. + */ + default String optimizerFamily() { + String family = extensionId() == null ? "unknown" : extensionId().trim().toLowerCase(Locale.ROOT); + String prefix = GpuIrOptimizerModule.MODULE_ID + "."; + if (family.startsWith(prefix)) { + family = family.substring(prefix.length()); + } + for (String suffix : java.util.List.of("-materialization", "-preview")) { + if (family.endsWith(suffix)) { + family = family.substring(0, family.length() - suffix.length()); + } + } + return family.isBlank() ? "unknown" : family; + } + @Override default Set extensionCapabilities() { return Set.of(GpuExtensionCapability.IR_OPTIMIZATION_PROPOSAL); diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePass.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePass.java index 6599b699..74f0fcca 100644 --- a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePass.java +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePass.java @@ -67,6 +67,16 @@ public GpuRuntimeIrOptimizationReport run(GpuRuntimeIrOptimizationRequest reques Optional candidateArtifact = Optional.empty(); ArrayList passReports = new ArrayList<>(); for (GpuIrOptimizationProposalProvider provider : providers) { + ProviderPolicyGate policyGate = providerPolicyGate(request, provider); + if (!policyGate.allowed()) { + passReports.add(policySkippedReport( + provider, + reviewWorkingArtifact, + request, + policyGate + ).withStage(stage())); + continue; + } GpuIrOptimizationProposalRequest proposalRequest = new GpuIrOptimizationProposalRequest( reviewWorkingArtifact, request.compileRequest().options().optimizationProfile(), @@ -92,6 +102,65 @@ public GpuRuntimeIrOptimizationReport run(GpuRuntimeIrOptimizationRequest reques return new GpuRuntimeIrOptimizationReport(Optional.of(selected), candidateArtifact, passReports); } + private static ProviderPolicyGate providerPolicyGate( + GpuRuntimeIrOptimizationRequest request, + GpuIrOptimizationProposalProvider provider + ) { + net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuOptimizerPolicyMetadata policy = + request.optimizerPolicy(); + String family = provider.optimizerFamily(); + if ("GPUOptimize".equals(policy.source()) && !policy.enabled()) { + return new ProviderPolicyGate(false, family, "optimizer-policy-disabled"); + } + if (policy.disabledFamilies().contains(family)) { + return new ProviderPolicyGate(false, family, "optimizer-family-disabled"); + } + if (!policy.enabledFamilies().isEmpty() && !policy.enabledFamilies().contains(family)) { + return new ProviderPolicyGate(false, family, "optimizer-family-not-enabled"); + } + return new ProviderPolicyGate(true, family, "none"); + } + + private static GpuRuntimeIrOptimizationPassReport policySkippedReport( + GpuIrOptimizationProposalProvider provider, + IrGpuArtifact currentArtifact, + GpuRuntimeIrOptimizationRequest request, + ProviderPolicyGate policyGate + ) { + String identity = IrGpuArtifactIdentity.stableIdentity(currentArtifact); + LinkedHashMap fields = new LinkedHashMap<>(contextFields(request, false)); + fields.put("optimizerFamily", policyGate.family()); + fields.put("provider.extensionId", provider.extensionId()); + fields.put("provider.extensionVersion", provider.extensionVersion()); + fields.put("policyGate.status", "skipped"); + fields.put("policyGate.allowed", "false"); + fields.put("policyGate.reason", policyGate.reason()); + fields.put("policyGate.family", policyGate.family()); + fields.put("policyGate.providerInvoked", "false"); + fields.put("analysisOnly", "true"); + fields.put("mutationAllowed", "false"); + fields.put("selectionApplied", "false"); + fields.put("optimizedArtifactSelected", "false"); + fields.put("selectedIrReplacement", "false"); + GpuRuntimeIrOptimizationProofArtifact proofArtifact = GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.policy-gate", + policyGate.reason(), + fields + ); + return new GpuRuntimeIrOptimizationPassReport( + GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, + provider.extensionVersion(), + GpuRuntimeIrOptimizationOutcome.SKIPPED, + identity, + identity, + policyGate.reason(), + "", + proofArtifact, + List.of("optimizer provider family '" + policyGate.family() + + "' skipped by @GPUOptimize policy: " + policyGate.reason()) + ); + } + @Override public GpuRuntimeIrOptimizationStage stage() { return GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY; @@ -264,7 +333,21 @@ private static Map contextFields(GpuRuntimeIrOptimizationRequest fields.put("deviceProfile.deviceClass", request.compileRequest().deviceProfile().deviceClass().name()); fields.put("fastMathAllowed", Boolean.toString(request.fastMathEnabled())); fields.put(GpuIrOptimizationPolicy.FAST_MATH_ALLOWED_FIELD, Boolean.toString(request.fastMathEnabled())); + fields.put( + GpuIrOptimizationPolicy.VENDOR_ADAPTATION_ALLOWED_FIELD, + Boolean.toString(request.optimizerPolicy().vendorAdaptation()) + ); fields.put("optimizerPolicy.fastMath", Boolean.toString(request.fastMathEnabled())); + fields.put("optimizerPolicy.enabled", Boolean.toString(request.optimizerPolicyEnabled())); + fields.put("optimizerPolicy.profile", request.optimizerPolicy().profile()); + fields.put("optimizerPolicy.enabledFamilies", String.join(",", request.optimizerPolicy().enabledFamilies())); + fields.put("optimizerPolicy.disabledFamilies", String.join(",", request.optimizerPolicy().disabledFamilies())); + fields.put("optimizerPolicy.journal", Boolean.toString(request.optimizerJournalRequested())); + fields.put("optimizerPolicy.dumpArtifacts", Boolean.toString(request.optimizerArtifactDumpRequested())); + fields.put("optimizerPolicy.productionIntent", Boolean.toString(request.optimizerPolicy().productionIntent())); + fields.put("optimizerPolicy.vendorAdaptation", Boolean.toString(request.optimizerPolicy().vendorAdaptation())); + fields.put("optimizerPolicy.vectorization", request.optimizerPolicy().vectorization()); + fields.put("optimizerPolicy.resourceShaping", Boolean.toString(request.optimizerPolicy().resourceShaping())); fields.put("optimizerPolicy.source", request.optimizerPolicy().source()); fields.put("mutationAllowed", Boolean.toString(mutationAllowed)); return Map.copyOf(fields); @@ -273,4 +356,7 @@ private static Map contextFields(GpuRuntimeIrOptimizationRequest private static List loadProviders() { return GpuIrOptimizationProposalRegistry.loadFromServiceLoader().providers(); } + + private record ProviderPolicyGate(boolean allowed, String family, String reason) { + } } diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrStepMaterializationProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrStepMaterializationProposalProvider.java index 0e7f91f6..0207967f 100644 --- a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrStepMaterializationProposalProvider.java +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrStepMaterializationProposalProvider.java @@ -343,10 +343,9 @@ private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody) { break; } StepCandidate value = candidate.orElseThrow(); - Replacement replacement = replaceFirst(currentBody, value.expressionText(), value.replacementText()); - if (!replacement.replaced()) { - stats.skippedBodyTextPatternMissingCount++; - stats.setFirstBlocker("body-text-pattern-missing"); + GpuIrTypedBodyGraphPatch.Applied patch = graphPatch(value).apply(currentTypedBody, currentBody); + if (!patch.applied()) { + stats.recordPatchBlocker(patch.blocker()); break; } changed = true; @@ -364,8 +363,8 @@ private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody) { stats.firstExpression = value.expressionText(); stats.firstReplacement = value.replacementText(); } - currentBody = replacement.body(); - currentTypedBody = rewriteTypedBody(currentTypedBody, value); + currentBody = patch.body(); + currentTypedBody = patch.typedBody(); } if (!changed) { @@ -384,9 +383,9 @@ private static Optional firstMaterializableCandidate( String body, RewriteStats stats ) { - Map nodesById = nodesById(typedBody); - Set reachableNodeIds = reachableNodeIds(typedBody, nodesById); - int nextNodeId = nodesById.keySet().stream().mapToInt(Integer::intValue).max().orElse(0) + 1; + Map nodesById = GpuIrTypedBodyGraphPatch.nodesById(typedBody); + Set reachableNodeIds = GpuIrTypedBodyGraphPatch.reachableNodeIds(typedBody, nodesById); + int nextNodeId = GpuIrTypedBodyGraphPatch.nextNodeId(typedBody); for (IrGpuTypedNode node : typedBody.nodes()) { if (!reachableNodeIds.contains(node.id()) || !isConditional(node)) { continue; @@ -627,40 +626,18 @@ private static Optional childSourceText( return sourceText(nodesById.get(childId), nodesById); } - private record Replacement(String body, boolean replaced) { - } - - private static Replacement replaceFirst(String body, String expressionText, String replacementText) { - int index = body.indexOf(expressionText); - if (index < 0) { - return new Replacement(body, false); - } - return new Replacement( - body.substring(0, index) + replacementText + body.substring(index + expressionText.length()), - true - ); - } - - private static IrGpuTypedBody rewriteTypedBody(IrGpuTypedBody typedBody, StepCandidate candidate) { - ArrayList nodes = new ArrayList<>(); - boolean addedStepNode = false; - for (IrGpuTypedNode node : typedBody.nodes()) { - if (node.id() == candidate.rootNodeId()) { - nodes.add(candidate.inverted() - ? invertedRootNode(node.id(), candidate) - : stepNode(node.id(), candidate)); - continue; - } - nodes.add(node); - } - if (candidate.inverted()) { - nodes.add(stepNode(candidate.stepNodeId(), candidate)); - addedStepNode = true; - } - if (!candidate.inverted() || addedStepNode) { - return new IrGpuTypedBody(typedBody.format(), typedBody.rootNodeIds(), nodes); - } - return typedBody; + private static GpuIrTypedBodyGraphPatch.Plan graphPatch(StepCandidate candidate) { + IrGpuTypedNode replacement = candidate.inverted() + ? invertedRootNode(candidate.rootNodeId(), candidate) + : stepNode(candidate.rootNodeId(), candidate); + return candidate.inverted() + ? GpuIrTypedBodyGraphPatch.plan( + candidate.expressionText(), + candidate.replacementText(), + replacement, + List.of(stepNode(candidate.stepNodeId(), candidate)) + ) + : GpuIrTypedBodyGraphPatch.plan(candidate.expressionText(), candidate.replacementText(), replacement); } private static IrGpuTypedNode invertedRootNode(int nodeId, StepCandidate candidate) { @@ -676,46 +653,15 @@ private static IrGpuTypedNode invertedRootNode(int nodeId, StepCandidate candida } private static IrGpuTypedNode stepNode(int nodeId, StepCandidate candidate) { - LinkedHashMap attributes = new LinkedHashMap<>(); - attributes.put("name", TARGET_INTRINSIC); - attributes.put("backendName", TARGET_INTRINSIC); - attributes.put("codeTemplate", ""); - attributes.put("receiver.null", "true"); - attributes.put("resultType", "step-review"); - attributes.put("argumentTypes.count", "2"); - attributes.put("argumentTypes.0", "step-review"); - attributes.put("argumentTypes.1", "step-review"); - return new IrGpuTypedNode( + return GpuIrTypedBodyGraphPatch.intrinsicCall( nodeId, - "GpuIrIntrinsicCall", - attributes, - Map.of("arguments", candidate.argumentNodeIds()) + TARGET_INTRINSIC, + "step-review", + candidate.argumentNodeIds(), + "step-review" ); } - private static Set reachableNodeIds(IrGpuTypedBody typedBody, Map nodesById) { - LinkedHashSet reachable = new LinkedHashSet<>(); - ArrayList pending = new ArrayList<>(typedBody.rootNodeIds()); - while (!pending.isEmpty()) { - int nodeId = pending.remove(pending.size() - 1); - if (!reachable.add(nodeId)) { - continue; - } - IrGpuTypedNode node = nodesById.get(nodeId); - if (node == null) { - continue; - } - for (List childIds : node.children().values()) { - for (Integer childId : childIds) { - if (childId != null && nodesById.containsKey(childId) && !reachable.contains(childId)) { - pending.add(childId); - } - } - } - } - return Set.copyOf(reachable); - } - private static Integer singleChild(IrGpuTypedNode node, String... names) { if (node == null || names == null) { return null; @@ -771,14 +717,6 @@ private static String firstAttribute(IrGpuTypedNode node, String... names) { return ""; } - private static Map nodesById(IrGpuTypedBody typedBody) { - LinkedHashMap nodes = new LinkedHashMap<>(); - for (IrGpuTypedNode node : typedBody.nodes()) { - nodes.put(node.id(), node); - } - return Map.copyOf(nodes); - } - private static boolean isOpenClReview(GpuIrOptimizationProposalRequest request) { return "OPENCL".equals(request.contextFields().getOrDefault("backendTarget", "")); } @@ -868,6 +806,16 @@ private void recordCandidate(StepCandidate candidate) { candidateKeys.add(candidate.methodName() + "#" + candidate.rootNodeId()); } + private void recordPatchBlocker(String blocker) { + switch (GpuIrTypedBodyGraphPatch.blockerKind(blocker)) { + case BODY_TEXT_PATTERN_MISSING -> skippedBodyTextPatternMissingCount++; + case TYPED_BODY_MISSING -> skippedTypedBodyMissingCount++; + case TYPED_GRAPH_MISSING -> skippedMissingChildReferenceCount++; + case OTHER -> skippedUnsupportedShapeCount++; + } + setFirstBlocker(blocker); + } + private void add(RewriteStats other) { typedBodyCount += other.typedBodyCount; candidateKeys.addAll(other.candidateKeys); diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedBodyGraphPatch.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedBodyGraphPatch.java new file mode 100644 index 00000000..0d2d3834 --- /dev/null +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedBodyGraphPatch.java @@ -0,0 +1,241 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +final class GpuIrTypedBodyGraphPatch { + + private static final String BODY_TEXT_PATTERN_MISSING = "body-text-pattern-missing"; + private static final String TYPED_BODY_MISSING = "typed-body-missing"; + private static final String TYPED_REPLACEMENT_MISSING = "typed-replacement-missing"; + private static final String TYPED_ROOT_MISSING = "typed-root-missing"; + + private GpuIrTypedBodyGraphPatch() { + } + + static Map nodesById(IrGpuTypedBody typedBody) { + LinkedHashMap nodes = new LinkedHashMap<>(); + if (typedBody == null) { + return Map.of(); + } + for (IrGpuTypedNode node : typedBody.nodes()) { + nodes.put(node.id(), node); + } + return Map.copyOf(nodes); + } + + static Set reachableNodeIds(IrGpuTypedBody typedBody, Map nodesById) { + LinkedHashSet reachable = new LinkedHashSet<>(); + if (typedBody == null || nodesById == null || nodesById.isEmpty()) { + return Set.of(); + } + ArrayList pending = new ArrayList<>(typedBody.rootNodeIds()); + while (!pending.isEmpty()) { + int nodeId = pending.remove(pending.size() - 1); + if (!reachable.add(nodeId)) { + continue; + } + IrGpuTypedNode node = nodesById.get(nodeId); + if (node == null) { + continue; + } + for (List childIds : node.children().values()) { + for (Integer childId : childIds) { + if (childId != null && nodesById.containsKey(childId) && !reachable.contains(childId)) { + pending.add(childId); + } + } + } + } + return Set.copyOf(reachable); + } + + static int nextNodeId(IrGpuTypedBody typedBody) { + if (typedBody == null || typedBody.nodes().isEmpty()) { + return 0; + } + return typedBody.nodes().stream().mapToInt(IrGpuTypedNode::id).max().orElse(-1) + 1; + } + + static IrGpuTypedBody replaceNode(IrGpuTypedBody typedBody, IrGpuTypedNode replacementNode) { + return replaceAndAppend(typedBody, replacementNode, List.of()); + } + + static IrGpuTypedBody appendNode(IrGpuTypedBody typedBody, IrGpuTypedNode node) { + if (typedBody == null) { + return IrGpuTypedBody.none(); + } + ArrayList nodes = new ArrayList<>(typedBody.nodes()); + nodes.add(node); + return new IrGpuTypedBody(typedBody.format(), typedBody.rootNodeIds(), nodes); + } + + static IrGpuTypedBody replaceAndAppend( + IrGpuTypedBody typedBody, + IrGpuTypedNode replacementNode, + List appendedNodes + ) { + if (typedBody == null) { + return IrGpuTypedBody.none(); + } + ArrayList nodes = new ArrayList<>(); + boolean replaced = false; + for (IrGpuTypedNode node : typedBody.nodes()) { + if (node.id() == replacementNode.id()) { + nodes.add(replacementNode); + replaced = true; + } else { + nodes.add(node); + } + } + if (!replaced) { + return typedBody; + } + if (appendedNodes != null) { + nodes.addAll(appendedNodes); + } + return new IrGpuTypedBody(typedBody.format(), typedBody.rootNodeIds(), nodes); + } + + static Plan plan( + String sourceText, + String replacementText, + IrGpuTypedNode replacementNode + ) { + return new Plan(sourceText, replacementText, replacementNode, List.of()); + } + + static Plan plan( + String sourceText, + String replacementText, + IrGpuTypedNode replacementNode, + List appendedNodes + ) { + return new Plan(sourceText, replacementText, replacementNode, appendedNodes); + } + + static PatchBlockerKind blockerKind(String blocker) { + return switch (blocker == null ? "" : blocker) { + case BODY_TEXT_PATTERN_MISSING -> PatchBlockerKind.BODY_TEXT_PATTERN_MISSING; + case TYPED_BODY_MISSING -> PatchBlockerKind.TYPED_BODY_MISSING; + case TYPED_ROOT_MISSING, TYPED_REPLACEMENT_MISSING -> PatchBlockerKind.TYPED_GRAPH_MISSING; + default -> PatchBlockerKind.OTHER; + }; + } + + enum PatchBlockerKind { + BODY_TEXT_PATTERN_MISSING, + TYPED_BODY_MISSING, + TYPED_GRAPH_MISSING, + OTHER + } + + record Plan( + String sourceText, + String replacementText, + IrGpuTypedNode replacementNode, + List appendedNodes + ) { + + Plan { + sourceText = sourceText == null ? "" : sourceText; + replacementText = replacementText == null ? "" : replacementText; + appendedNodes = appendedNodes == null ? List.of() : List.copyOf(appendedNodes); + } + + Applied apply(IrGpuTypedBody typedBody, String body) { + if (typedBody == null || !typedBody.available()) { + return Applied.blocked(typedBody, body, TYPED_BODY_MISSING); + } + if (replacementNode == null) { + return Applied.blocked(typedBody, body, TYPED_REPLACEMENT_MISSING); + } + if (!nodesById(typedBody).containsKey(replacementNode.id())) { + return Applied.blocked(typedBody, body, TYPED_ROOT_MISSING); + } + if (body == null || sourceText.isBlank()) { + return Applied.blocked(typedBody, body, BODY_TEXT_PATTERN_MISSING); + } + int index = body.indexOf(sourceText); + if (index < 0) { + return Applied.blocked(typedBody, body, BODY_TEXT_PATTERN_MISSING); + } + String patchedBody = body.substring(0, index) + + replacementText + + body.substring(index + sourceText.length()); + IrGpuTypedBody patchedTypedBody = replaceAndAppend(typedBody, replacementNode, appendedNodes); + return new Applied(patchedTypedBody, patchedBody, true, "none"); + } + } + + record Applied( + IrGpuTypedBody typedBody, + String body, + boolean applied, + String blocker + ) { + + private static Applied blocked(IrGpuTypedBody typedBody, String body, String blocker) { + return new Applied(typedBody, body, false, blocker); + } + } + + static IrGpuTypedNode intrinsicCall( + int nodeId, + String intrinsicName, + String resultType, + List argumentNodeIds, + String argumentType + ) { + ArrayList argumentTypes = new ArrayList<>(); + for (int index = 0; index < safeArguments(argumentNodeIds).size(); index++) { + argumentTypes.add(argumentType); + } + return intrinsicCall(nodeId, intrinsicName, resultType, argumentNodeIds, argumentTypes); + } + + static IrGpuTypedNode intrinsicCall( + int nodeId, + String intrinsicName, + String resultType, + List argumentNodeIds, + List argumentTypes + ) { + List arguments = safeArguments(argumentNodeIds); + LinkedHashMap attributes = new LinkedHashMap<>(); + attributes.put("name", intrinsicName); + attributes.put("backendName", intrinsicName); + attributes.put("codeTemplate", ""); + attributes.put("receiver.null", "true"); + attributes.put("resultType", resultType); + attributes.put("argumentTypes.count", Integer.toString(arguments.size())); + for (int index = 0; index < arguments.size(); index++) { + attributes.put("argumentTypes." + index, argumentTypeAt(argumentTypes, index)); + } + return new IrGpuTypedNode( + nodeId, + "GpuIrIntrinsicCall", + attributes, + Map.of("arguments", arguments) + ); + } + + private static List safeArguments(List argumentNodeIds) { + return argumentNodeIds == null ? List.of() : List.copyOf(argumentNodeIds); + } + + private static String argumentTypeAt(List argumentTypes, int index) { + if (argumentTypes == null || index >= argumentTypes.size()) { + return "unknown-review"; + } + String argumentType = argumentTypes.get(index); + return argumentType == null || argumentType.isBlank() ? "unknown-review" : argumentType; + } +} diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePassTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePassTest.java index b6f99c52..187b4d67 100644 --- a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePassTest.java +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePassTest.java @@ -46,6 +46,7 @@ import java.util.Map; import java.util.Optional; import java.util.ServiceLoader; +import java.util.concurrent.atomic.AtomicBoolean; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; @@ -154,6 +155,172 @@ void bridgeKeepsCanonicalizationProposalOnlyByDefault() { )); } + @Test + void bridgeSkipsProviderWhenFamilyIsDisabledByMethodPolicy() { + AtomicBoolean invoked = new AtomicBoolean(false); + IrGpuArtifact original = artifactWithPolicy( + "body\n return original\n", + IrGpuOptimizerPolicyMetadata.fromGpuOptimize( + false, + true, + "review", + List.of(), + List.of("mix"), + false, + false, + false, + false, + "auto", + false + ) + ); + GpuIrProposalRuntimeBridgePass bridge = new GpuIrProposalRuntimeBridgePass( + List.of(new CountingProposalProvider("mix", invoked)), + GpuIrOptimizationSandwichRunner.alwaysValid(), + false + ); + + GpuRuntimeIrOptimizationReport report = bridge + .run(new GpuRuntimeIrOptimizationRequest(request(original), Optional.of(original))); + + assertFalse(invoked.get()); + assertSame(original, report.artifact().orElseThrow()); + assertTrue(report.candidateArtifact().isEmpty()); + assertEquals(1, report.passReports().size()); + assertEquals(GpuRuntimeIrOptimizationOutcome.SKIPPED, report.passReports().get(0).outcome()); + assertEquals("optimizer-family-disabled", report.passReports().get(0).proofStatus()); + Map fields = report.passReports().get(0).proofArtifact().fields(); + assertEquals("mix", fields.get("optimizerFamily")); + assertEquals("skipped", fields.get("policyGate.status")); + assertEquals("false", fields.get("policyGate.allowed")); + assertEquals("optimizer-family-disabled", fields.get("policyGate.reason")); + assertEquals("false", fields.get("policyGate.providerInvoked")); + assertEquals("false", fields.get("selectionApplied")); + assertEquals("false", fields.get("optimizedArtifactSelected")); + assertEquals("false", fields.get("selectedIrReplacement")); + + String evidence = runtimeOptimizerEvidence(original, report); + assertTrue(evidence.contains("policyGate.skipped.count=1")); + assertTrue(evidence.contains("policyGate.optimizerPolicyDisabled.count=0")); + assertTrue(evidence.contains("policyGate.familyDisabled.count=1")); + assertTrue(evidence.contains("policyGate.familyNotEnabled.count=0")); + assertTrue(evidence.contains("policyGate.providerInvoked.count=0")); + assertTrue(evidence.contains("policyGate.firstBlocker=optimizer-family-disabled")); + assertTrue(evidence.contains("policyGate.family.summary=mix=1")); + assertTrue(evidence.contains("policyGate.selectionApplied=false")); + assertTrue(evidence.contains("policyGate.optimizedArtifactSelected=false")); + assertTrue(evidence.contains("policyGate.selectedIrReplacement=false")); + } + + @Test + void bridgeSkipsProviderWhenFamilyIsNotInMethodAllowList() { + AtomicBoolean invoked = new AtomicBoolean(false); + IrGpuArtifact original = artifactWithPolicy( + "body\n return original\n", + IrGpuOptimizerPolicyMetadata.fromGpuOptimize( + false, + true, + "review", + List.of("clamp"), + List.of(), + false, + false, + false, + false, + "auto", + false + ) + ); + GpuIrProposalRuntimeBridgePass bridge = new GpuIrProposalRuntimeBridgePass( + List.of(new CountingProposalProvider("mix", invoked)), + GpuIrOptimizationSandwichRunner.alwaysValid(), + false + ); + + GpuRuntimeIrOptimizationReport report = bridge + .run(new GpuRuntimeIrOptimizationRequest(request(original), Optional.of(original))); + + assertFalse(invoked.get()); + assertSame(original, report.artifact().orElseThrow()); + assertEquals("optimizer-family-not-enabled", report.passReports().get(0).proofStatus()); + Map fields = report.passReports().get(0).proofArtifact().fields(); + assertEquals("clamp", fields.get("optimizerPolicy.enabledFamilies")); + assertEquals("optimizer-family-not-enabled", fields.get("policyGate.reason")); + assertEquals("mix", fields.get("policyGate.family")); + } + + @Test + void bridgeRunsProviderWhenFamilyIsAllowedByMethodAllowList() { + AtomicBoolean invoked = new AtomicBoolean(false); + IrGpuArtifact original = artifactWithPolicy( + "body\n return original\n", + IrGpuOptimizerPolicyMetadata.fromGpuOptimize( + false, + true, + "review", + List.of("mix-materialization"), + List.of(), + false, + false, + false, + false, + "auto", + false + ) + ); + GpuIrProposalRuntimeBridgePass bridge = new GpuIrProposalRuntimeBridgePass( + List.of(new CountingProposalProvider("mix", invoked)), + GpuIrOptimizationSandwichRunner.alwaysValid(), + false + ); + + GpuRuntimeIrOptimizationReport report = bridge + .run(new GpuRuntimeIrOptimizationRequest(request(original), Optional.of(original))); + + assertTrue(invoked.get()); + assertSame(original, report.artifact().orElseThrow()); + assertEquals(1, report.passReports().size()); + assertEquals(GpuRuntimeIrOptimizationOutcome.SKIPPED, report.passReports().get(0).outcome()); + assertEquals("not-mutating", report.passReports().get(0).proofStatus()); + } + + @Test + void bridgeSkipsAllProvidersWhenExplicitMethodPolicyDisablesOptimizer() { + AtomicBoolean invoked = new AtomicBoolean(false); + IrGpuArtifact original = artifactWithPolicy( + "body\n return original\n", + IrGpuOptimizerPolicyMetadata.fromGpuOptimize( + false, + false, + "review", + List.of(), + List.of(), + false, + false, + false, + false, + "auto", + false + ) + ); + GpuIrProposalRuntimeBridgePass bridge = new GpuIrProposalRuntimeBridgePass( + List.of(new CountingProposalProvider("clamp", invoked)), + GpuIrOptimizationSandwichRunner.alwaysValid(), + false + ); + + GpuRuntimeIrOptimizationReport report = bridge + .run(new GpuRuntimeIrOptimizationRequest(request(original), Optional.of(original))); + + assertFalse(invoked.get()); + assertSame(original, report.artifact().orElseThrow()); + assertEquals("optimizer-policy-disabled", report.passReports().get(0).proofStatus()); + assertEquals( + "optimizer-policy-disabled", + report.passReports().get(0).proofArtifact().fields().get("policyGate.reason") + ); + } + @Test void bridgeMaterializesConstantFoldingCandidateButKeepsOriginalSelectedByDefault() { IrGpuArtifact original = constantFoldingArtifact(); @@ -501,6 +668,29 @@ private static GpuRuntimeCompileRequest request(IrGpuArtifact artifact) { ); } + private static String runtimeOptimizerEvidence( + IrGpuArtifact original, + GpuRuntimeIrOptimizationReport optimizationReport + ) { + GpuRuntimeCompileRequest compileRequest = request(original); + GpuBackendModuleArtifact backendArtifact = GpuBackendModuleArtifact.openClSource( + "__kernel void run(__global int* out) { out[0] = 1; }", + "runtime/lowered/run.cl", + "test-lowerer-v1" + ); + GpuRuntimeCompileArtifactDump dump = GpuRuntimeCompileArtifactDumper.dump( + GpuRuntimeCompileArtifactSnapshot.from( + compileRequest, + compileRequest, + backendArtifact, + GpuRuntimeCompileInvalidationStamp.from(compileRequest, backendArtifact, "policy-gate"), + GpuRuntimeCompileProvenance.from(compileRequest), + optimizationReport + ) + ); + return dump.artifact(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + } + private static GpuIrOptimizationProposalRequest proposalRequest( GpuRuntimeCompileRequest compileRequest, IrGpuArtifact artifact, @@ -564,6 +754,61 @@ private static IrGpuArtifact artifact(String body) { ); } + private static IrGpuArtifact artifactWithPolicy(String body, IrGpuOptimizerPolicyMetadata policy) { + IrGpuArtifact artifact = artifact(body); + return new IrGpuArtifact( + artifact.header(), + artifact.module(), + artifact.entryParameters(), + artifact.launchMetadata(), + artifact.validationMetadata(), + artifact.featureMetadata(), + policy, + artifact.regenerationMetadata(), + artifact.structMetadata(), + artifact.constants(), + artifact.constantData(), + artifact.backendOutputs(), + artifact.runtimeDefaultBackend(), + artifact.runtimeOptimizationProfile(), + artifact.methodDeviceConstraints(), + artifact.methodFallbackVariants(), + artifact.extensionParticipationMetadata() + ); + } + + private record CountingProposalProvider( + String optimizerFamily, + AtomicBoolean invoked + ) implements GpuIrOptimizationProposalProvider { + + @Override + public GpuIrOptimizationProposal propose(GpuIrOptimizationProposalRequest request) { + invoked.set(true); + return GpuIrOptimizationProposal.noChange( + extensionId(), + extensionVersion(), + request.originalArtifact(), + "counting provider invoked" + ); + } + + @Override + public String optimizerFamily() { + return optimizerFamily; + } + + @Override + public String extensionId() { + return "test." + optimizerFamily; + } + + @Override + public String extensionVersion() { + return extensionId() + ":1"; + } + } + private static IrGpuArtifact constantFoldingArtifact() { IrGpuTypedBody typedBody = new IrGpuTypedBody( IrGpuTypedBody.FORMAT, diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedBodyGraphPatchTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedBodyGraphPatchTest.java new file mode 100644 index 00000000..685aee49 --- /dev/null +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedBodyGraphPatchTest.java @@ -0,0 +1,184 @@ +package net.sixik.ga_utils.javatogpu.iroptimizer; + +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedBody; +import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuIrTypedBodyGraphPatchTest { + + @Test + void mapsReachableNodesAndAllocatesDeterministicNextId() { + IrGpuTypedBody body = typedBody(); + + Map nodesById = GpuIrTypedBodyGraphPatch.nodesById(body); + + assertEquals("GpuIrAssignment", nodesById.get(0).kind()); + assertEquals("GpuIrLiteral", nodesById.get(99).kind()); + assertEquals(100, GpuIrTypedBodyGraphPatch.nextNodeId(body)); + assertEquals(List.of(0, 1, 2, 3, 99), body.nodes().stream().map(IrGpuTypedNode::id).toList()); + assertEquals( + List.of(0, 1, 2, 3), + body.nodes().stream() + .map(IrGpuTypedNode::id) + .filter(id -> GpuIrTypedBodyGraphPatch.reachableNodeIds(body, nodesById).contains(id)) + .toList() + ); + } + + @Test + void replacesRootNodeAndAppendsAuxiliaryNodeWithoutChangingRoots() { + IrGpuTypedBody body = typedBody(); + IrGpuTypedNode replacement = GpuIrTypedBodyGraphPatch.intrinsicCall( + 2, + "clamp", + "clamp-review", + List.of(1, 3, 99), + "clamp-review" + ); + IrGpuTypedNode auxiliary = new IrGpuTypedNode(100, "GpuIrLiteral", Map.of("sourceText", "1.0F"), Map.of()); + + IrGpuTypedBody patched = GpuIrTypedBodyGraphPatch.replaceAndAppend(body, replacement, List.of(auxiliary)); + + assertEquals(List.of(0), patched.rootNodeIds()); + assertEquals(List.of(0, 1, 2, 3, 99, 100), patched.nodes().stream().map(IrGpuTypedNode::id).toList()); + IrGpuTypedNode replacedNode = GpuIrTypedBodyGraphPatch.nodesById(patched).get(2); + assertEquals("GpuIrIntrinsicCall", replacedNode.kind()); + assertEquals("clamp", replacedNode.attributes().get("name")); + assertEquals("clamp-review", replacedNode.attributes().get("argumentTypes.2")); + assertEquals(List.of(1, 3, 99), replacedNode.children().get("arguments")); + assertEquals("1.0F", GpuIrTypedBodyGraphPatch.nodesById(patched).get(100).attributes().get("sourceText")); + } + + @Test + void keepsOriginalBodyWhenReplacementRootWasNotPresent() { + IrGpuTypedBody body = typedBody(); + IrGpuTypedNode replacement = GpuIrTypedBodyGraphPatch.intrinsicCall( + 101, + "mix", + "mix-review", + List.of(1, 2, 3), + List.of("base-review", "target-review", "amount-review") + ); + + IrGpuTypedBody patched = GpuIrTypedBodyGraphPatch.replaceNode(body, replacement); + + assertEquals(body.nodes(), patched.nodes()); + assertEquals(body.rootNodeIds(), patched.rootNodeIds()); + assertTrue(GpuIrTypedBodyGraphPatch.reachableNodeIds(patched, GpuIrTypedBodyGraphPatch.nodesById(patched)).contains(2)); + } + + @Test + void appliesTextAndTypedBodyPatchAsSinglePlan() { + IrGpuTypedBody body = typedBody(); + IrGpuTypedNode replacement = GpuIrTypedBodyGraphPatch.intrinsicCall( + 2, + "mad", + "fast-math-review", + List.of(1, 3, 99), + "fast-math-review" + ); + + GpuIrTypedBodyGraphPatch.Applied applied = GpuIrTypedBodyGraphPatch + .plan("(out[0] + 2)", "intrinsic(mad template=\"\" args=[out[0], 2, unreachable])", replacement) + .apply(body, "body\n set tmp = (out[0] + 2)\n"); + + assertTrue(applied.applied()); + assertEquals("none", applied.blocker()); + assertEquals("body\n set tmp = intrinsic(mad template=\"\" args=[out[0], 2, unreachable])\n", applied.body()); + assertEquals("GpuIrIntrinsicCall", GpuIrTypedBodyGraphPatch.nodesById(applied.typedBody()).get(2).kind()); + assertEquals("mad", GpuIrTypedBodyGraphPatch.nodesById(applied.typedBody()).get(2).attributes().get("name")); + } + + @Test + void blocksPlanWhenTextPatternIsMissingWithoutChangingTypedBody() { + IrGpuTypedBody body = typedBody(); + IrGpuTypedNode replacement = GpuIrTypedBodyGraphPatch.intrinsicCall( + 2, + "mix", + "mix-review", + List.of(1, 2, 3), + "mix-review" + ); + + GpuIrTypedBodyGraphPatch.Applied applied = GpuIrTypedBodyGraphPatch + .plan("missing", "replacement", replacement) + .apply(body, "body\n set tmp = (out[0] + 2)\n"); + + assertFalse(applied.applied()); + assertEquals("body-text-pattern-missing", applied.blocker()); + assertEquals(body.nodes(), applied.typedBody().nodes()); + assertEquals("body\n set tmp = (out[0] + 2)\n", applied.body()); + } + + @Test + void blocksPlanWhenTypedRootIsMissingWithoutChangingTextBody() { + IrGpuTypedBody body = typedBody(); + IrGpuTypedNode replacement = GpuIrTypedBodyGraphPatch.intrinsicCall( + 101, + "step", + "step-review", + List.of(1, 3), + "step-review" + ); + + GpuIrTypedBodyGraphPatch.Applied applied = GpuIrTypedBodyGraphPatch + .plan("(out[0] + 2)", "intrinsic(step template=\"\" args=[out[0], 2])", replacement) + .apply(body, "body\n set tmp = (out[0] + 2)\n"); + + assertFalse(applied.applied()); + assertEquals("typed-root-missing", applied.blocker()); + assertEquals(body.nodes(), applied.typedBody().nodes()); + assertEquals("body\n set tmp = (out[0] + 2)\n", applied.body()); + } + + @Test + void classifiesPatchBlockersForProviderCounters() { + assertEquals( + GpuIrTypedBodyGraphPatch.PatchBlockerKind.BODY_TEXT_PATTERN_MISSING, + GpuIrTypedBodyGraphPatch.blockerKind("body-text-pattern-missing") + ); + assertEquals( + GpuIrTypedBodyGraphPatch.PatchBlockerKind.TYPED_BODY_MISSING, + GpuIrTypedBodyGraphPatch.blockerKind("typed-body-missing") + ); + assertEquals( + GpuIrTypedBodyGraphPatch.PatchBlockerKind.TYPED_GRAPH_MISSING, + GpuIrTypedBodyGraphPatch.blockerKind("typed-root-missing") + ); + assertEquals( + GpuIrTypedBodyGraphPatch.PatchBlockerKind.TYPED_GRAPH_MISSING, + GpuIrTypedBodyGraphPatch.blockerKind("typed-replacement-missing") + ); + assertEquals( + GpuIrTypedBodyGraphPatch.PatchBlockerKind.OTHER, + GpuIrTypedBodyGraphPatch.blockerKind("custom-blocker") + ); + } + + private static IrGpuTypedBody typedBody() { + return new IrGpuTypedBody( + IrGpuTypedBody.FORMAT, + List.of(0), + List.of( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of( + "target", List.of(1), + "value", List.of(2) + )), + new IrGpuTypedNode(1, "GpuIrVariableRef", Map.of("name", "out[0]"), Map.of()), + new IrGpuTypedNode(2, "GpuIrBinary", Map.of("operator", "+"), Map.of( + "left", List.of(1), + "right", List.of(3) + )), + new IrGpuTypedNode(3, "GpuIrLiteral", Map.of("sourceText", "2"), Map.of()), + new IrGpuTypedNode(99, "GpuIrLiteral", Map.of("sourceText", "unreachable"), Map.of()) + ) + ); + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/GPUOptimize.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/GPUOptimize.java index 3d7a0fb0..dad9b2d5 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/GPUOptimize.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/api/annotations/GPUOptimize.java @@ -6,7 +6,7 @@ import java.lang.annotation.Target; /** - * Declares method-level optimization policy for future runtime IR optimizer passes. + * Declares method-level optimization policy for runtime IR optimizer passes. * *

The default is strict floating-point behavior. Enabling fast math allows future proof-backed optimizers to consider * transformations such as fused multiply-add, reassociation, interpolation folding, and other rewrites that may change @@ -17,8 +17,58 @@ @Target(ElementType.METHOD) public @interface GPUOptimize { + /** + * Enables method-level optimizer participation metadata for this method. + */ + boolean enabled() default true; + + /** + * Optional profile hint, for example {@code review}, {@code balanced}, or a project-defined profile name. + */ + String profile() default ""; + /** * Allows future fast-math IR rewrites when the active optimizer profile and evidence gates also permit them. */ boolean fastMath() default false; + + /** + * Optional allow-list of optimizer families such as {@code clamp}, {@code step}, {@code mix}, or {@code mad-fma}. + */ + String[] enabledFamilies() default {}; + + /** + * Optional deny-list of optimizer families. Deny-list entries take precedence over enabled families. + */ + String[] disabledFamilies() default {}; + + /** + * Requests a runtime optimizer journal when the host runtime exposes a journaling path. + */ + boolean journal() default false; + + /** + * Requests before/after optimizer artifact dumps when supported by the host runtime. + */ + boolean dumpArtifacts() default false; + + /** + * Records production-selection intent only; it does not bypass proof, approval, rollback, or runtime gates. + */ + boolean productionIntent() default false; + + /** + * Allows future vendor-specific optimizer proposals when device evidence and runtime gates also allow them. + */ + boolean vendorAdaptation() default false; + + /** + * Optional vectorization preference, for example {@code auto}, {@code prefer}, or {@code avoid}. + */ + String vectorization() default "auto"; + + /** + * Allows future resource/register-pressure shaping proposals when evidence gates also permit them. + */ + boolean resourceShaping() default false; } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendService.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendService.java index a26ffdfc..ac0ac151 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendService.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/GpuFrontendService.java @@ -397,13 +397,21 @@ private static > List parseEnumAnnotationValues( private static List parseStringAnnotationValues( AnnotationExpr annotation, String propertyName + ) { + return parseStringAnnotationValues(annotation, propertyName, "GPUDeviceConstraint"); + } + + private static List parseStringAnnotationValues( + AnnotationExpr annotation, + String propertyName, + String annotationName ) { return annotationValue(annotation, propertyName).stream() .flatMap(GpuFrontendService::annotationValues) .map(expression -> { if (!expression.isStringLiteralExpr()) { throw new IllegalArgumentException( - "GPUDeviceConstraint." + propertyName + " must contain string literals: " + expression + annotationName + "." + propertyName + " must contain string literals: " + expression ); } return expression.asStringLiteralExpr().asString(); @@ -460,6 +468,26 @@ private static int parseIntAnnotationValue( .orElse(defaultValue); } + private static boolean parseBooleanAnnotationValue( + AnnotationExpr annotation, + String propertyName, + boolean defaultValue, + String annotationName + ) { + return annotationValue(annotation, propertyName) + .map(expression -> { + if (expression.isBooleanLiteralExpr()) { + return expression.asBooleanLiteralExpr().getValue(); + } + String value = expression.toString().trim(); + if ("true".equals(value) || "false".equals(value)) { + return Boolean.parseBoolean(value); + } + return defaultValue; + }) + .orElse(defaultValue); + } + private static java.util.Optional annotationValue( AnnotationExpr annotation, String propertyName @@ -543,18 +571,27 @@ private static IrGpuOptimizerPolicyMetadata buildOptimizerPolicyMetadata(GpuIrCo } return declaration.getAnnotationByName("GPUOptimize") - .map(annotation -> { - boolean fastMath = annotation.isNormalAnnotationExpr() - && annotation.asNormalAnnotationExpr().getPairs().stream() - .filter(pair -> pair.getNameAsString().equals("fastMath")) - .findFirst() - .map(pair -> Boolean.parseBoolean(pair.getValue().toString())) - .orElse(false); - return IrGpuOptimizerPolicyMetadata.fromGpuOptimize(fastMath); - }) + .map(GpuFrontendService::parseGpuOptimizePolicy) .orElseGet(IrGpuOptimizerPolicyMetadata::defaultStrict); } + private static IrGpuOptimizerPolicyMetadata parseGpuOptimizePolicy(AnnotationExpr annotation) { + String profile = parseOptionalStringAnnotationValue(annotation, "profile", "GPUOptimize"); + return IrGpuOptimizerPolicyMetadata.fromGpuOptimize( + parseBooleanAnnotationValue(annotation, "fastMath", false, "GPUOptimize"), + parseBooleanAnnotationValue(annotation, "enabled", true, "GPUOptimize"), + profile.isBlank() ? "default" : profile, + parseStringAnnotationValues(annotation, "enabledFamilies", "GPUOptimize"), + parseStringAnnotationValues(annotation, "disabledFamilies", "GPUOptimize"), + parseBooleanAnnotationValue(annotation, "journal", false, "GPUOptimize"), + parseBooleanAnnotationValue(annotation, "dumpArtifacts", false, "GPUOptimize"), + parseBooleanAnnotationValue(annotation, "productionIntent", false, "GPUOptimize"), + parseBooleanAnnotationValue(annotation, "vendorAdaptation", false, "GPUOptimize"), + parseOptionalStringAnnotationValue(annotation, "vectorization", "GPUOptimize"), + parseBooleanAnnotationValue(annotation, "resourceShaping", false, "GPUOptimize") + ); + } + private static List buildStructMetadata(List structs) { return structs.stream() .map(struct -> new IrGpuStructMetadata( diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifactParser.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifactParser.java index 613abc8e..bc40cd1c 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifactParser.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifactParser.java @@ -332,6 +332,16 @@ private static IrGpuFeatureMetadata parseFeatureMetadata(Properties properties) private static IrGpuOptimizerPolicyMetadata parseOptimizerPolicyMetadata(Properties properties) { return new IrGpuOptimizerPolicyMetadata( Boolean.parseBoolean(properties.getProperty("optimizerPolicy.fastMath", "false")), + Boolean.parseBoolean(properties.getProperty("optimizerPolicy.enabled", "false")), + properties.getProperty("optimizerPolicy.profile", "off"), + parseIndexedValues(properties, "optimizerPolicy.enabledFamily"), + parseIndexedValues(properties, "optimizerPolicy.disabledFamily"), + Boolean.parseBoolean(properties.getProperty("optimizerPolicy.journal", "false")), + Boolean.parseBoolean(properties.getProperty("optimizerPolicy.dumpArtifacts", "false")), + Boolean.parseBoolean(properties.getProperty("optimizerPolicy.productionIntent", "false")), + Boolean.parseBoolean(properties.getProperty("optimizerPolicy.vendorAdaptation", "false")), + properties.getProperty("optimizerPolicy.vectorization", "auto"), + Boolean.parseBoolean(properties.getProperty("optimizerPolicy.resourceShaping", "false")), properties.getProperty("optimizerPolicy.source", "default-strict") ); } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifactSerializer.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifactSerializer.java index 80fc7227..f81d393a 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifactSerializer.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuArtifactSerializer.java @@ -343,6 +343,16 @@ private static void writeOptimizerPolicyMetadata( ? IrGpuOptimizerPolicyMetadata.defaultStrict() : optimizerPolicyMetadata; properties.put("optimizerPolicy.fastMath", Boolean.toString(metadata.fastMath())); + properties.put("optimizerPolicy.enabled", Boolean.toString(metadata.enabled())); + properties.put("optimizerPolicy.profile", metadata.profile()); + writeStringList(properties, "optimizerPolicy.enabledFamily", metadata.enabledFamilies()); + writeStringList(properties, "optimizerPolicy.disabledFamily", metadata.disabledFamilies()); + properties.put("optimizerPolicy.journal", Boolean.toString(metadata.journal())); + properties.put("optimizerPolicy.dumpArtifacts", Boolean.toString(metadata.dumpArtifacts())); + properties.put("optimizerPolicy.productionIntent", Boolean.toString(metadata.productionIntent())); + properties.put("optimizerPolicy.vendorAdaptation", Boolean.toString(metadata.vendorAdaptation())); + properties.put("optimizerPolicy.vectorization", metadata.vectorization()); + properties.put("optimizerPolicy.resourceShaping", Boolean.toString(metadata.resourceShaping())); properties.put("optimizerPolicy.source", metadata.source()); } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuOptimizerPolicyMetadata.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuOptimizerPolicyMetadata.java index 21a07dca..a0f54da6 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuOptimizerPolicyMetadata.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/frontend/ir/artifact/IrGpuOptimizerPolicyMetadata.java @@ -1,22 +1,145 @@ package net.sixik.ga_utils.javatogpu.frontend.ir.artifact; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; + /** * Method-level optimizer policy stored in an IrGpu artifact. */ public record IrGpuOptimizerPolicyMetadata( boolean fastMath, + boolean enabled, + String profile, + List enabledFamilies, + List disabledFamilies, + boolean journal, + boolean dumpArtifacts, + boolean productionIntent, + boolean vendorAdaptation, + String vectorization, + boolean resourceShaping, String source ) { public IrGpuOptimizerPolicyMetadata { + profile = normalizeText(profile, "off"); + enabledFamilies = normalizeFamilies(enabledFamilies); + disabledFamilies = normalizeFamilies(disabledFamilies); + vectorization = normalizeText(vectorization, "auto"); source = source == null || source.isBlank() ? "default-strict" : source; } + public IrGpuOptimizerPolicyMetadata(boolean fastMath, String source) { + this( + fastMath, + false, + "off", + List.of(), + List.of(), + false, + false, + false, + false, + "auto", + false, + source + ); + } + public static IrGpuOptimizerPolicyMetadata defaultStrict() { return new IrGpuOptimizerPolicyMetadata(false, "default-strict"); } public static IrGpuOptimizerPolicyMetadata fromGpuOptimize(boolean fastMath) { - return new IrGpuOptimizerPolicyMetadata(fastMath, "GPUOptimize"); + return fromGpuOptimize( + fastMath, + true, + "default", + List.of(), + List.of(), + false, + false, + false, + false, + "auto", + false + ); + } + + public static IrGpuOptimizerPolicyMetadata fromGpuOptimize( + boolean fastMath, + boolean enabled, + String profile, + List enabledFamilies, + List disabledFamilies, + boolean journal, + boolean dumpArtifacts, + boolean productionIntent, + boolean vendorAdaptation, + String vectorization, + boolean resourceShaping + ) { + return new IrGpuOptimizerPolicyMetadata( + fastMath, + enabled, + profile, + enabledFamilies, + disabledFamilies, + journal, + dumpArtifacts, + productionIntent, + vendorAdaptation, + vectorization, + resourceShaping, + "GPUOptimize" + ); + } + + public boolean hasFamilyFilter() { + return !enabledFamilies.isEmpty() || !disabledFamilies.isEmpty(); + } + + public boolean allowsFamily(String family) { + String normalized = normalizeFamily(family); + if (normalized.isBlank() || !enabled || disabledFamilies.contains(normalized)) { + return false; + } + return enabledFamilies.isEmpty() || enabledFamilies.contains(normalized); + } + + private static List normalizeFamilies(List values) { + if (values == null || values.isEmpty()) { + return List.of(); + } + LinkedHashSet normalized = new LinkedHashSet<>(); + for (String value : values) { + String family = normalizeFamily(value); + if (!family.isBlank()) { + normalized.add(family); + } + } + return List.copyOf(normalized); + } + + private static String normalizeFamily(String value) { + String family = value == null ? "" : value.trim().toLowerCase(Locale.ROOT); + for (String suffix : List.of("-materialization", "-preview")) { + if (family.endsWith(suffix)) { + family = family.substring(0, family.length() - suffix.length()); + } + } + return switch (family) { + case "mad", "fma", "madfma", "mad/fma" -> "mad-fma"; + case "cse", "safe-cse" -> "safe-local-cse"; + case "const-folding", "constantfolding" -> "constant-folding"; + case "dce", "typed-dce", "dead-code" -> "typed-dead-code"; + case "vectorization" -> "loop-vectorization"; + default -> family; + }; + } + + private static String normalizeText(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value.trim(); } } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java index 22c422bf..e8ce2cf2 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java @@ -759,6 +759,24 @@ private static String formatRuntimeIrOptimizerEvidence(GpuRuntimeIrOptimizationR .append(approvalTemplatePayload.passedCount()).append('\n'); builder.append("approvalTemplate.runtimeEquivalencePayloadComplete.count=") .append(approvalTemplatePayload.completeCount()).append('\n'); + PolicyGateEvidence policyGate = policyGateEvidence(irOptimizerReports); + builder.append("policyGate.skipped.count=").append(policyGate.skippedCount()).append('\n'); + builder.append("policyGate.optimizerPolicyDisabled.count=") + .append(policyGate.optimizerPolicyDisabledCount()).append('\n'); + builder.append("policyGate.familyDisabled.count=") + .append(policyGate.familyDisabledCount()).append('\n'); + builder.append("policyGate.familyNotEnabled.count=") + .append(policyGate.familyNotEnabledCount()).append('\n'); + builder.append("policyGate.providerInvoked.count=") + .append(policyGate.providerInvokedCount()).append('\n'); + builder.append("policyGate.firstBlocker=") + .append(safePropertyValue(policyGate.firstBlocker())).append('\n'); + builder.append("policyGate.family.summary=") + .append(safePropertyValue(policyGate.familySummary())).append('\n'); + builder.append("policyGate.mutationAllowed=false\n"); + builder.append("policyGate.selectionApplied=false\n"); + builder.append("policyGate.optimizedArtifactSelected=false\n"); + builder.append("policyGate.selectedIrReplacement=false\n"); OptimizedArtifactCandidateEvidence optimizedArtifactCandidate = optimizedArtifactCandidateEvidence(irOptimizerReports); builder.append("optimizedArtifactCandidate.status=").append(optimizedArtifactCandidate.status()).append('\n'); builder.append("optimizedArtifactCandidate.count=").append(optimizedArtifactCandidate.count()).append('\n'); @@ -1299,6 +1317,68 @@ private record ApprovalTemplatePayloadEvidence( ) { } + private static PolicyGateEvidence policyGateEvidence( + List passReports + ) { + int skippedCount = 0; + int optimizerPolicyDisabledCount = 0; + int familyDisabledCount = 0; + int familyNotEnabledCount = 0; + int providerInvokedCount = 0; + String firstBlocker = "none"; + LinkedHashMap familyCounts = new LinkedHashMap<>(); + for (GpuRuntimeIrOptimizationPassReport passReport : passReports) { + if (passReport == null || passReport.proofArtifact() == null) { + continue; + } + Map fields = passReport.proofArtifact().fields(); + if (!"skipped".equals(fields.get("policyGate.status"))) { + continue; + } + skippedCount++; + String reason = fields.getOrDefault("policyGate.reason", "unknown"); + String family = fields.getOrDefault("policyGate.family", fields.getOrDefault("optimizerFamily", "unknown")); + if (!family.isBlank()) { + familyCounts.merge(family, 1, Integer::sum); + } + if (parseBoolean(fields.get("policyGate.providerInvoked"))) { + providerInvokedCount++; + } + switch (reason) { + case "optimizer-policy-disabled" -> optimizerPolicyDisabledCount++; + case "optimizer-family-disabled" -> familyDisabledCount++; + case "optimizer-family-not-enabled" -> familyNotEnabledCount++; + default -> { } + } + if ("none".equals(firstBlocker) && !reason.isBlank() && !"none".equals(reason)) { + firstBlocker = reason; + } + } + return new PolicyGateEvidence( + skippedCount, + optimizerPolicyDisabledCount, + familyDisabledCount, + familyNotEnabledCount, + providerInvokedCount, + skippedCount == 0 ? "none" : firstBlocker, + formatCountSummary(familyCounts) + ); + } + + private static String formatCountSummary(Map counts) { + if (counts == null || counts.isEmpty()) { + return "none"; + } + StringBuilder summary = new StringBuilder(); + counts.forEach((key, count) -> { + if (!summary.isEmpty()) { + summary.append(", "); + } + summary.append(key).append('=').append(Math.max(0, count)); + }); + return summary.toString(); + } + private static OptimizedArtifactCandidateEvidence optimizedArtifactCandidateEvidence( List passReports ) { @@ -3234,6 +3314,17 @@ private record ApprovalManifestPackageEvidence( ) { } + private record PolicyGateEvidence( + int skippedCount, + int optimizerPolicyDisabledCount, + int familyDisabledCount, + int familyNotEnabledCount, + int providerInvokedCount, + String firstBlocker, + String familySummary + ) { + } + private record OptimizedArtifactCandidateEvidence( String status, int count, diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizationRequest.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizationRequest.java index 85d8d273..19fbc48b 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizationRequest.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizationRequest.java @@ -40,4 +40,16 @@ public IrGpuOptimizerPolicyMetadata optimizerPolicy() { public boolean fastMathEnabled() { return optimizerPolicy().fastMath(); } + + public boolean optimizerPolicyEnabled() { + return optimizerPolicy().enabled(); + } + + public boolean optimizerJournalRequested() { + return optimizerPolicy().journal(); + } + + public boolean optimizerArtifactDumpRequested() { + return optimizerPolicy().dumpArtifacts(); + } } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholePass.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholePass.java index 67592220..2f7c60da 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholePass.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrPeepholePass.java @@ -75,6 +75,16 @@ private Map diagnosticFields( fields.put("mode", "diagnostic-only"); fields.put("mutationEnabled", "false"); fields.put("fastMath", Boolean.toString(request.fastMathEnabled())); + fields.put("policyEnabled", Boolean.toString(request.optimizerPolicyEnabled())); + fields.put("policyProfile", request.optimizerPolicy().profile()); + fields.put("policyEnabledFamilies", String.join(",", request.optimizerPolicy().enabledFamilies())); + fields.put("policyDisabledFamilies", String.join(",", request.optimizerPolicy().disabledFamilies())); + fields.put("policyJournal", Boolean.toString(request.optimizerJournalRequested())); + fields.put("policyDumpArtifacts", Boolean.toString(request.optimizerArtifactDumpRequested())); + fields.put("policyProductionIntent", Boolean.toString(request.optimizerPolicy().productionIntent())); + fields.put("policyVendorAdaptation", Boolean.toString(request.optimizerPolicy().vendorAdaptation())); + fields.put("policyVectorization", request.optimizerPolicy().vectorization()); + fields.put("policyResourceShaping", Boolean.toString(request.optimizerPolicy().resourceShaping())); fields.put("policySource", request.optimizerPolicy().source()); int methodBodyCount = artifact.map(value -> value.module().methodBodies().size()).orElse(0); long typedBodyCount = artifact.stream() diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java index 38de30a1..09c8a495 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java @@ -120,6 +120,44 @@ int totalApprovalTemplateRuntimeEquivalencePayloadCompleteCount() { .sum(); } + int totalPolicyGateSkippedCount() { + return entries.stream().mapToInt(Entry::policyGateSkippedCount).sum(); + } + + int totalPolicyGateOptimizerPolicyDisabledCount() { + return entries.stream().mapToInt(Entry::policyGateOptimizerPolicyDisabledCount).sum(); + } + + int totalPolicyGateFamilyDisabledCount() { + return entries.stream().mapToInt(Entry::policyGateFamilyDisabledCount).sum(); + } + + int totalPolicyGateFamilyNotEnabledCount() { + return entries.stream().mapToInt(Entry::policyGateFamilyNotEnabledCount).sum(); + } + + int totalPolicyGateProviderInvokedCount() { + return entries.stream().mapToInt(Entry::policyGateProviderInvokedCount).sum(); + } + + String policyGateFirstBlocker() { + for (Entry entry : entries) { + if (entry.policyGateSkippedCount() > 0 && !"none".equals(entry.policyGateFirstBlocker())) { + return entry.policyGateFirstBlocker(); + } + } + return totalPolicyGateSkippedCount() > 0 ? "unknown" : "none"; + } + + String policyGateFamilySummary() { + LinkedHashMap counts = new LinkedHashMap<>(); + for (Entry entry : entries) { + parseCountSummary(entry.policyGateFamilySummary()).forEach((family, count) -> + counts.merge(family, count, Integer::sum)); + } + return formatProviderCounts(counts); + } + int totalOptimizedArtifactCandidateCount() { return entries.stream().mapToInt(Entry::optimizedArtifactCandidateCount).sum(); } @@ -1144,6 +1182,20 @@ String toMarkdown() { .append(totalApprovalTemplateRuntimeEquivalencePayloadPassedCount()).append("`\n"); markdown.append("- Approval templates runtime-equivalence payload complete: `") .append(totalApprovalTemplateRuntimeEquivalencePayloadCompleteCount()).append("`\n"); + markdown.append("- Policy-gated optimizer skips: `") + .append(totalPolicyGateSkippedCount()).append("`\n"); + markdown.append("- Policy-gated optimizer disabled skips: `") + .append(totalPolicyGateOptimizerPolicyDisabledCount()).append("`\n"); + markdown.append("- Policy-gated family disabled skips: `") + .append(totalPolicyGateFamilyDisabledCount()).append("`\n"); + markdown.append("- Policy-gated family not-enabled skips: `") + .append(totalPolicyGateFamilyNotEnabledCount()).append("`\n"); + markdown.append("- Policy-gated provider invoked count: `") + .append(totalPolicyGateProviderInvokedCount()).append("`\n"); + markdown.append("- Policy-gate first blocker: `") + .append(inline(policyGateFirstBlocker())).append("`\n"); + markdown.append("- Policy-gate family summary: `") + .append(inline(policyGateFamilySummary())).append("`\n"); markdown.append("- Optimized artifact candidate status: `") .append(optimizedArtifactCandidateStatus()).append("`\n"); markdown.append("- Optimized artifact candidates: `") @@ -1530,6 +1582,26 @@ private static String formatProviderCounts(Map counts) { return summary.toString(); } + private static Map parseCountSummary(String summary) { + if (summary == null || summary.isBlank() || "none".equals(summary)) { + return Map.of(); + } + LinkedHashMap counts = new LinkedHashMap<>(); + for (String part : summary.split(",")) { + String trimmed = part.trim(); + int separator = trimmed.lastIndexOf('='); + if (separator <= 0 || separator >= trimmed.length() - 1) { + continue; + } + String key = trimmed.substring(0, separator).trim(); + int count = parseInt(trimmed.substring(separator + 1).trim(), 0); + if (!key.isBlank() && count > 0) { + counts.merge(key, count, Integer::sum); + } + } + return Map.copyOf(counts); + } + private record PreviewFamilyReadiness(String family, String status, int candidateCount, int blockerCount) { } @@ -2061,6 +2133,34 @@ int typedDeadCodeMaterializationBlockedCount() { + typedDeadCodeMaterializationBlockedSideEffectingUnreachableNodeCount; } + int policyGateSkippedCount() { + return parseInt(evidenceProperties.get("policyGate.skipped.count"), 0); + } + + int policyGateOptimizerPolicyDisabledCount() { + return parseInt(evidenceProperties.get("policyGate.optimizerPolicyDisabled.count"), 0); + } + + int policyGateFamilyDisabledCount() { + return parseInt(evidenceProperties.get("policyGate.familyDisabled.count"), 0); + } + + int policyGateFamilyNotEnabledCount() { + return parseInt(evidenceProperties.get("policyGate.familyNotEnabled.count"), 0); + } + + int policyGateProviderInvokedCount() { + return parseInt(evidenceProperties.get("policyGate.providerInvoked.count"), 0); + } + + String policyGateFirstBlocker() { + return evidenceProperties.getOrDefault("policyGate.firstBlocker", "none"); + } + + String policyGateFamilySummary() { + return evidenceProperties.getOrDefault("policyGate.family.summary", "none"); + } + int constantFoldingPreviewProofBlockerCount() { if (constantFoldingPreviewCandidateCount <= 0) { return 0; diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/processors/GpuCompilerProcessorTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/processors/GpuCompilerProcessorTest.java index 8169d17c..487b0c5b 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/processors/GpuCompilerProcessorTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/processors/GpuCompilerProcessorTest.java @@ -127,6 +127,16 @@ __kernel void jtg_kernel(__global const float* input, __global float* output) { assertTrue(irGpuManifest.contains("feature.optional.count=1")); assertTrue(irGpuManifest.contains("feature.optional.0=opencl-source-compat")); assertTrue(irGpuManifest.contains("optimizerPolicy.fastMath=false")); + assertTrue(irGpuManifest.contains("optimizerPolicy.enabled=false")); + assertTrue(irGpuManifest.contains("optimizerPolicy.profile=off")); + assertTrue(irGpuManifest.contains("optimizerPolicy.enabledFamily.count=0")); + assertTrue(irGpuManifest.contains("optimizerPolicy.disabledFamily.count=0")); + assertTrue(irGpuManifest.contains("optimizerPolicy.journal=false")); + assertTrue(irGpuManifest.contains("optimizerPolicy.dumpArtifacts=false")); + assertTrue(irGpuManifest.contains("optimizerPolicy.productionIntent=false")); + assertTrue(irGpuManifest.contains("optimizerPolicy.vendorAdaptation=false")); + assertTrue(irGpuManifest.contains("optimizerPolicy.vectorization=auto")); + assertTrue(irGpuManifest.contains("optimizerPolicy.resourceShaping=false")); assertTrue(irGpuManifest.contains("optimizerPolicy.source=default-strict")); assertTrue(irGpuManifest.contains("regeneration.backendNeutralSourceReady=false")); assertTrue(irGpuManifest.contains("regeneration.payloadFormat=ir-text-v1")); @@ -175,7 +185,35 @@ __kernel void jtg_kernel(__global const float* input, __global float* output) { assertTrue(irGpuArtifact.featureMetadata().requiredFeatures().isEmpty()); assertEquals(List.of("opencl-source-compat"), irGpuArtifact.featureMetadata().optionalFeatures()); assertFalse(irGpuArtifact.optimizerPolicyMetadata().fastMath()); + assertFalse(irGpuArtifact.optimizerPolicyMetadata().enabled()); + assertEquals("off", irGpuArtifact.optimizerPolicyMetadata().profile()); + assertTrue(irGpuArtifact.optimizerPolicyMetadata().enabledFamilies().isEmpty()); + assertTrue(irGpuArtifact.optimizerPolicyMetadata().disabledFamilies().isEmpty()); + assertFalse(irGpuArtifact.optimizerPolicyMetadata().journal()); + assertFalse(irGpuArtifact.optimizerPolicyMetadata().dumpArtifacts()); + assertFalse(irGpuArtifact.optimizerPolicyMetadata().productionIntent()); + assertFalse(irGpuArtifact.optimizerPolicyMetadata().vendorAdaptation()); + assertEquals("auto", irGpuArtifact.optimizerPolicyMetadata().vectorization()); + assertFalse(irGpuArtifact.optimizerPolicyMetadata().resourceShaping()); assertEquals("default-strict", irGpuArtifact.optimizerPolicyMetadata().source()); + + String legacyOptimizerPolicyManifest = String.join("\n", irGpuManifest.lines() + .filter(line -> !line.startsWith("optimizerPolicy.enabled=")) + .filter(line -> !line.startsWith("optimizerPolicy.profile=")) + .filter(line -> !line.startsWith("optimizerPolicy.enabledFamily")) + .filter(line -> !line.startsWith("optimizerPolicy.disabledFamily")) + .filter(line -> !line.startsWith("optimizerPolicy.journal=")) + .filter(line -> !line.startsWith("optimizerPolicy.dumpArtifacts=")) + .filter(line -> !line.startsWith("optimizerPolicy.productionIntent=")) + .filter(line -> !line.startsWith("optimizerPolicy.vendorAdaptation=")) + .filter(line -> !line.startsWith("optimizerPolicy.vectorization=")) + .filter(line -> !line.startsWith("optimizerPolicy.resourceShaping=")) + .toList()); + var legacyOptimizerPolicy = IrGpuArtifactParser.parse(legacyOptimizerPolicyManifest).optimizerPolicyMetadata(); + assertFalse(legacyOptimizerPolicy.enabled()); + assertEquals("off", legacyOptimizerPolicy.profile()); + assertTrue(legacyOptimizerPolicy.enabledFamilies().isEmpty()); + assertTrue(legacyOptimizerPolicy.disabledFamilies().isEmpty()); assertFalse(irGpuArtifact.regenerationMetadata().backendNeutralSourceReady()); assertEquals("ir-text-v1", irGpuArtifact.regenerationMetadata().payloadFormat()); assertEquals("derived-opencl-source", irGpuArtifact.regenerationMetadata().fallbackSource()); @@ -638,13 +676,106 @@ void kernel(@GPUGlobal float[] input, @GPUGlobal float[] output) { assertTrue(Files.exists(irGpuPath)); String irGpuManifest = Files.readString(irGpuPath); assertTrue(irGpuManifest.contains("optimizerPolicy.fastMath=true")); + assertTrue(irGpuManifest.contains("optimizerPolicy.enabled=true")); + assertTrue(irGpuManifest.contains("optimizerPolicy.profile=default")); assertTrue(irGpuManifest.contains("optimizerPolicy.source=GPUOptimize")); IrGpuArtifact irGpuArtifact = IrGpuArtifactParser.parse(irGpuManifest); assertTrue(irGpuArtifact.optimizerPolicyMetadata().fastMath()); + assertTrue(irGpuArtifact.optimizerPolicyMetadata().enabled()); + assertEquals("default", irGpuArtifact.optimizerPolicyMetadata().profile()); assertEquals("GPUOptimize", irGpuArtifact.optimizerPolicyMetadata().source()); } + @Test + void generatedIrGpuManifestPreservesRichOptimizerPolicy() throws IOException { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + Path classOutputDir = Files.createTempDirectory("javatogpu-rich-optimizer-policy-classes"); + Path generatedOutputDir = Files.createTempDirectory("javatogpu-rich-optimizer-policy-generated"); + + String source = """ + package sample; + + import net.sixik.ga_utils.javatogpu.api.GPU; + import net.sixik.ga_utils.javatogpu.api.annotations.GPUGlobal; + import net.sixik.ga_utils.javatogpu.api.annotations.GPUOptimize; + + public class Demo { + @GPUOptimize( + profile = "review", + fastMath = true, + enabledFamilies = {"mix", "CLAMP", "step"}, + disabledFamilies = "dot", + journal = true, + dumpArtifacts = true, + productionIntent = true, + vendorAdaptation = true, + vectorization = "prefer", + resourceShaping = true + ) + @net.sixik.ga_utils.javatogpu.api.annotations.GPU + void kernel(@GPUGlobal float[] input, @GPUGlobal float[] output) { + int id = GPU.get_global_id(0); + output[id] = input[id] * 2.0f + 1.0f; + } + } + """; + + try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null)) { + List options = List.of( + "-classpath", System.getProperty("java.class.path"), + "-d", classOutputDir.toString(), + "-s", generatedOutputDir.toString() + ); + JavaFileObject sourceFile = new StringJavaFileObject("sample.Demo", source); + JavaCompiler.CompilationTask task = compiler.getTask( + null, + fileManager, + null, + options, + null, + List.of(sourceFile) + ); + task.setProcessors(List.of(new GpuCompilerProcessor())); + + assertTrue(task.call()); + } + + Path irGpuPath = generatedOutputDir.resolve("javatogpu/sample/Demo/kernel.irgpu.properties"); + assertTrue(Files.exists(irGpuPath)); + String irGpuManifest = Files.readString(irGpuPath); + assertTrue(irGpuManifest.contains("optimizerPolicy.profile=review")); + assertTrue(irGpuManifest.contains("optimizerPolicy.enabledFamily.count=3")); + assertTrue(irGpuManifest.contains("optimizerPolicy.enabledFamily.0=mix")); + assertTrue(irGpuManifest.contains("optimizerPolicy.enabledFamily.1=clamp")); + assertTrue(irGpuManifest.contains("optimizerPolicy.enabledFamily.2=step")); + assertTrue(irGpuManifest.contains("optimizerPolicy.disabledFamily.0=dot")); + assertTrue(irGpuManifest.contains("optimizerPolicy.journal=true")); + assertTrue(irGpuManifest.contains("optimizerPolicy.dumpArtifacts=true")); + assertTrue(irGpuManifest.contains("optimizerPolicy.productionIntent=true")); + assertTrue(irGpuManifest.contains("optimizerPolicy.vendorAdaptation=true")); + assertTrue(irGpuManifest.contains("optimizerPolicy.vectorization=prefer")); + assertTrue(irGpuManifest.contains("optimizerPolicy.resourceShaping=true")); + + IrGpuArtifact irGpuArtifact = IrGpuArtifactParser.parse(irGpuManifest); + var policy = irGpuArtifact.optimizerPolicyMetadata(); + assertTrue(policy.fastMath()); + assertTrue(policy.enabled()); + assertEquals("review", policy.profile()); + assertEquals(List.of("mix", "clamp", "step"), policy.enabledFamilies()); + assertEquals(List.of("dot"), policy.disabledFamilies()); + assertTrue(policy.journal()); + assertTrue(policy.dumpArtifacts()); + assertTrue(policy.productionIntent()); + assertTrue(policy.vendorAdaptation()); + assertEquals("prefer", policy.vectorization()); + assertTrue(policy.resourceShaping()); + assertTrue(policy.allowsFamily("mix")); + assertFalse(policy.allowsFamily("dot")); + assertFalse(policy.allowsFamily("mad-fma")); + assertEquals("GPUOptimize", policy.source()); + } + @Test void generatesOpenClKernelFileDuringCompilationWithCanonicalAnnotations() throws IOException { JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistryTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistryTest.java index 12029fae..506f580e 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistryTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeIrOptimizerRegistryTest.java @@ -1482,8 +1482,10 @@ void optimizationRequestExposesFastMathPolicyFromIrGpuArtifact() { ); assertTrue(request.fastMathEnabled()); + assertTrue(request.optimizerPolicyEnabled()); assertEquals("GPUOptimize", request.optimizerPolicy().source()); assertFalse(missingArtifactRequest.fastMathEnabled()); + assertFalse(missingArtifactRequest.optimizerPolicyEnabled()); assertEquals("default-strict", missingArtifactRequest.optimizerPolicy().source()); } diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java index b7ff743b..8c0dae76 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummaryTest.java @@ -46,6 +46,17 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { approvalTemplate.runtimeEquivalencePayloadPresent.count=0 approvalTemplate.runtimeEquivalencePayloadPassed.count=0 approvalTemplate.runtimeEquivalencePayloadComplete.count=0 + policyGate.skipped.count=2 + policyGate.optimizerPolicyDisabled.count=0 + policyGate.familyDisabled.count=1 + policyGate.familyNotEnabled.count=1 + policyGate.providerInvoked.count=0 + policyGate.firstBlocker=optimizer-family-disabled + policyGate.family.summary=mix=1, clamp=1 + policyGate.mutationAllowed=false + policyGate.selectionApplied=false + policyGate.optimizedArtifactSelected=false + policyGate.selectedIrReplacement=false optimizedArtifactCandidate.status=candidate-ready optimizedArtifactCandidate.count=1 optimizedArtifactCandidate.ready.count=1 @@ -241,6 +252,13 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { assertEquals(0, summary.entries().get(0).approvalTemplateRuntimeEquivalencePayloadPresentCount()); assertEquals(0, summary.entries().get(0).approvalTemplateRuntimeEquivalencePayloadPassedCount()); assertEquals(0, summary.entries().get(0).approvalTemplateRuntimeEquivalencePayloadCompleteCount()); + assertEquals(2, summary.entries().get(0).policyGateSkippedCount()); + assertEquals(0, summary.entries().get(0).policyGateOptimizerPolicyDisabledCount()); + assertEquals(1, summary.entries().get(0).policyGateFamilyDisabledCount()); + assertEquals(1, summary.entries().get(0).policyGateFamilyNotEnabledCount()); + assertEquals(0, summary.entries().get(0).policyGateProviderInvokedCount()); + assertEquals("optimizer-family-disabled", summary.entries().get(0).policyGateFirstBlocker()); + assertEquals("mix=1, clamp=1", summary.entries().get(0).policyGateFamilySummary()); assertEquals("candidate-ready", summary.entries().get(0).optimizedArtifactCandidateStatus()); assertEquals(1, summary.entries().get(0).optimizedArtifactCandidateCount()); assertEquals(1, summary.entries().get(0).optimizedArtifactCandidateReadyCount()); @@ -352,6 +370,15 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { assertEquals(0, summary.totalApprovalTemplateRuntimeEquivalencePayloadPresentCount()); assertEquals(0, summary.totalApprovalTemplateRuntimeEquivalencePayloadPassedCount()); assertEquals(0, summary.totalApprovalTemplateRuntimeEquivalencePayloadCompleteCount()); + assertEquals(2, summary.totalPolicyGateSkippedCount()); + assertEquals(0, summary.totalPolicyGateOptimizerPolicyDisabledCount()); + assertEquals(1, summary.totalPolicyGateFamilyDisabledCount()); + assertEquals(1, summary.totalPolicyGateFamilyNotEnabledCount()); + assertEquals(0, summary.totalPolicyGateProviderInvokedCount()); + assertEquals("optimizer-family-disabled", summary.policyGateFirstBlocker()); + String policyGateFamilySummary = summary.policyGateFamilySummary(); + assertTrue(policyGateFamilySummary.contains("mix=1")); + assertTrue(policyGateFamilySummary.contains("clamp=1")); assertEquals("none", summary.optimizedArtifactCandidateFirstBlocker()); assertEquals("mutation-disabled", summary.optimizedArtifactCandidateSelectionFirstBlocker()); assertEquals(1, summary.totalBackendNeutralSourceMaterializationPassCount()); @@ -431,6 +458,13 @@ void readsRuntimeIrOptimizerEvidenceArtifacts() throws Exception { assertTrue(summary.toMarkdown().contains( "- Approval templates runtime-equivalence payload complete: `0`" )); + assertTrue(summary.toMarkdown().contains("- Policy-gated optimizer skips: `2`")); + assertTrue(summary.toMarkdown().contains("- Policy-gated optimizer disabled skips: `0`")); + assertTrue(summary.toMarkdown().contains("- Policy-gated family disabled skips: `1`")); + assertTrue(summary.toMarkdown().contains("- Policy-gated family not-enabled skips: `1`")); + assertTrue(summary.toMarkdown().contains("- Policy-gated provider invoked count: `0`")); + assertTrue(summary.toMarkdown().contains("- Policy-gate first blocker: `optimizer-family-disabled`")); + assertTrue(summary.toMarkdown().contains("- Policy-gate family summary: `" + policyGateFamilySummary + "`")); assertTrue(summary.toMarkdown().contains("- Optimized artifact candidate status: `candidate-ready`")); assertTrue(summary.toMarkdown().contains("- Optimized artifact candidates: `1`")); assertTrue(summary.toMarkdown().contains("- Optimized artifact candidates ready: `1`")); From 42aab269fcef98b1f0abe5d223e72a96f66ecc1b Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Wed, 15 Jul 2026 02:22:51 +0300 Subject: [PATCH 28/32] feat: IR-Optimizer v1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Based on my current understanding, without algorithmic graph analysis (like the egg library in Rust provides), continuing to support math optimizations will turn into an absolute maintenance hell. Therefore, I’m putting this on hold for now until better times. --- ROADMAP.md | 54 +- docs/IR-Optimizer.md | 312 +++++++++- docs/Runtime-Guide.md | 12 +- docs/Validation-and-Operations.md | 2 +- examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md | 16 +- ...oldingMaterializationProposalProvider.java | 95 +-- ...zationMaterializationProposalProvider.java | 147 ++++- ...MadFmaMaterializationProposalProvider.java | 83 ++- .../GpuIrProposalRuntimeBridgePass.java | 62 +- ...calCseMaterializationProposalProvider.java | 588 +++++++++++++++--- .../iroptimizer/GpuIrTypedBodyGraphPatch.java | 62 +- ...adCodeMaterializationProposalProvider.java | 55 +- ...rTypedDeadCodePreviewProposalProvider.java | 54 +- ...onMaterializationProposalProviderTest.java | 42 ++ ...maMaterializationProposalProviderTest.java | 104 +++- .../GpuIrProposalRuntimeBridgePassTest.java | 279 ++++++++- ...seMaterializationProposalProviderTest.java | 120 +++- .../GpuIrTypedBodyGraphPatchTest.java | 37 ++ .../runtime/GpuBackendCompileOptions.java | 26 + .../GpuRuntimeCompileArtifactDumper.java | 137 ++++ .../runtime/GpuRuntimeCompileOptions.java | 27 + .../opencl/OpenClGpuRuntimeBackend.java | 30 +- ...enClRuntimeIrOptimizerEvidenceSummary.java | 119 +++- ...untimeIrOptimizerEvidenceValidatorCli.java | 212 ++++++- .../GpuRuntimeCompileArtifactDumperTest.java | 109 ++++ .../opencl/OpenClGpuRuntimeBackendTest.java | 175 ++++++ ...meIrOptimizerEvidenceValidatorCliTest.java | 146 +++++ 27 files changed, 2720 insertions(+), 385 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 60e8681a..bc707cd4 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -693,21 +693,22 @@ Current rule: `ir-validation` remains strictly read-only. Optimizer modules may OpenCL runtime validation now aggregates preview evidence into `constantFoldingPreview.*` fields, source-ready materialization evidence into `backendNeutralSourceMaterialization.*`, and materialized review-candidate evidence into `constantFoldingMaterialization.*` fields in `runtime-ir-optimizer-evidence.properties`. The report surfaces pass, candidate, source-ready, skipped-blocker, materialized-node, literal-rewrite, identity-rewrite, runtime-equivalence payload, status, and first-blocker counts; backend-neutral source materialization can become approval-template pending without requiring runtime-equivalence review, materialized constant folding can reach runtime-equivalence `review-ready`, and `reviewPackage.*` remains pending manual approval while optimized IR selection stays disabled. `GpuIrSafeLocalCsePreviewProposalProvider` now adds the matching safe-local-CSE proof surface by scanning typed IR for repeated local expressions and recording expression, candidate-expression, duplicate-expression, equivalence-class, blocker, policy, proof, and safety evidence without proposing a rewrite. It explicitly records unsupported-operator, impure-operand, and control-flow-boundary blockers plus required runtime-equivalence/approval gates and unresolved dominance / side-effect-freedom proof flags, so CSE readiness can be measured without enabling optimized IR selection. OpenCL runtime validation now aggregates that preview evidence into `safeLocalCsePreview.*` fields in `runtime-ir-optimizer-evidence.properties` and surfaces pass, candidate-expression, duplicate-expression, and blocker counts in the `Runtime IR Optimizer Evidence` report section. - `GpuIrSafeLocalCseMaterializationProposalProvider` now adds the first review-only safe-local-CSE transform. It reuses an already materialized local binding in straight-line `ir-text-v1` bodies, rewrites repeated pure binary expressions to existing local references in the optimized review artifact, runs to a fixed point over reachable roots, updates typed nodes and body text together, emits compact runtime-equivalence payload cases with `RewriteKind=safe-local-cse-reuse-existing-local`, and blocks on control-flow boundaries, missing text patterns, missing existing bindings, unsupported/impure operands, or OpenCL review-source emission failures. It does not introduce new temporaries, mutate the original artifact, select optimized IR, or enable production mutation. - `GpuIrMadFmaMaterializationProposalProvider` now adds the first review-only typed peephole materializer. It requires fast-math permission, rewrites reachable `a * b + c` / `c + a * b` typed roots and matching `ir-text-v1` text to `intrinsic(mad template="" args=[a, b, c])`, emits per-case `RewriteKind=mad-fma-to-opencl-mad` runtime-equivalence payload fields, verifies OpenCL review-source emission, and stays proposal-only through the bridge so `optimized.backend.opencl-c` can show `mad(...)` while `backend.opencl-c` remains selected from the original runtime IR. Runtime evidence now lifts this into `madFmaMaterialization.*` with pass, candidate, transformed-node, body-text-replacement, fixed-point-pass, skipped fast-math/body-text-pattern, fast-math policy, payload, status, and first-blocker fields. + `GpuIrSafeLocalCseMaterializationProposalProvider` now adds the first review-only safe-local-CSE transform. It reuses an already materialized local binding in straight-line `ir-text-v1` bodies, rewrites repeated pure binary expressions to existing local references in the optimized review artifact, runs to a fixed point over reachable roots, and applies body-text plus typed-node updates through the shared transactional `GpuIrTypedBodyGraphPatch.Plan` path, including binding-aware text replacement after the original local declaration. It can also introduce a conservative local temporary for repeated pure binary expressions with no existing binding, such as `(value * 1.414F)`, when all text occurrences match typed occurrences exactly and no input variable is reassigned between the first and last use. The introduced-temp path inserts a deterministic `jtg_cseN` declaration, clones the initializer typed subtree, replaces all repeated typed occurrences with variable refs, validates typed reachability, emits `introducedTemporary.count`, `safety.newTemporaryIntroduced=true`, and per-case `RewriteKind=safe-local-cse-introduce-local`, and still keeps the original runtime IR selected. It blocks on control-flow boundaries, missing text patterns, unsafe local invalidation, unsupported/impure operands, or OpenCL review-source emission failures, and it does not mutate the original artifact, select optimized IR, or enable production mutation. + `GpuIrMadFmaMaterializationProposalProvider` now adds the first review-only typed peephole materializer. It requires fast-math permission, rewrites reachable `a * b + c` / `c + a * b` typed roots and matching `ir-text-v1` text to `intrinsic(mad template="" args=[a, b, c])`, and rewrites reachable `a * b - c` roots to `intrinsic(mad template="" args=[a, b, (-c)])` by appending a typed unary-negation argument node in the optimized review artifact. It emits per-case `RewriteKind=mad-fma-to-opencl-mad` / `RewriteKind=multiply-subtract-to-opencl-mad` runtime-equivalence payload fields, verifies OpenCL review-source emission, and stays proposal-only through the bridge so `optimized.backend.opencl-c` can show `mad(...)` while `backend.opencl-c` remains selected from the original runtime IR. Runtime evidence now lifts this into `madFmaMaterialization.*` with pass, candidate, transformed-node, body-text-replacement, fixed-point-pass, skipped fast-math/body-text-pattern, fast-math policy, payload, status, and first-blocker fields. `GpuIrClampMaterializationProposalProvider` now adds the first review-only non-fast-math typed peephole materializer for generated clamp trees. It recognizes conservative reachable `min(max(x, lo), hi)` typed intrinsic-call shapes, rewrites matching `ir-text-v1` text to `intrinsic(clamp template="" args=[x, lo, hi])`, replaces the typed root with a `GpuIrIntrinsicCall`, emits per-case `RewriteKind=min-max-to-opencl-clamp` runtime-equivalence payload fields, verifies OpenCL review-source emission, and stays proposal-only through the bridge so `optimized.backend.opencl-c` can show `clamp(...)` while `backend.opencl-c` remains selected from the original runtime IR. Reordered min/max forms and broader algebraic variants remain open until edge-case semantics are proven. `GpuIrStepMaterializationProposalProvider` now adds review-only non-fast-math materialization for typed ternary masks. Inclusive comparisons such as `a >= b ? 1.0F : 0.0F` become direct `step(b, a)` review intrinsics, while strict comparisons such as `a > b ? 1.0F : 0.0F` become `(1.0F - step(a, b))` so equality and NaN behavior stay equivalent to the original ternary. The proof records `directStep.count`, `invertedStep.count`, `RewriteKind=ternary-mask-to-opencl-step` / `ternary-mask-to-inverted-opencl-step`, strict-comparison/equality/NaN preservation, OpenCL review-source readiness, and keeps the selected runtime IR unchanged. `GpuIrMixMaterializationProposalProvider` now adds review-only materialization for typed interpolation trees. Canonical `a + t * (b - a)` / `t * (b - a) + a` forms become `mix(a, b, t)` without fast-math permission, while expanded weighted forms and generated `mad(b, t, (1.0F - t) * a)` forms require fast-math permission before review materialization. The optimizer-journal shape `mad(blend, isSolid, ((1.0F - isSolid) * (clampedWave * 0.1F)))` can now become `mix(clampedWave * 0.1F, blend, isSolid)` in `optimized.backend.opencl-c` while the selected runtime IR remains unchanged. - `GpuIrLoopVectorizationMaterializationProposalProvider` now adds the first review-only loop vectorization materializer. It recognizes strict fixed-width contiguous float reductions such as `sum += input[id * 4 + i]`, equivalent `mad(id, 4, i)` / `intrinsic(mad ...)` index shapes, straight-line precomputed base locals such as `var int base = id * 4; sum += input[base + i]`, reversed `i + base` forms, and constant-offset bases such as `sum += input[((id * 4) + 8) + i]`. Local base aliases are deliberately conservative: only preceding straight-line `int` declarations are considered, assignments invalidate aliases, and control-flow boundaries clear the alias set. The pass rewrites the optimized review body to `vload4` plus ordered scalar lane reduction through `Float4`, records per-case `RewriteKind=fixed-width-contiguous-vload4-ordered-reduction`, intentionally avoids `dot` / reassociation, requires no fast-math permission, reconstructs a matching review typed body for the supported `ir-text-v1` subset, falls back to typed-body invalidation only when surrounding body text cannot be reconstructed, and leaves the original runtime IR selected. + `GpuIrLoopVectorizationMaterializationProposalProvider` now adds the first review-only loop vectorization materializer. It recognizes strict fixed-width contiguous float reductions such as `sum += input[id * 4 + i]`, equivalent `mad(id, 4, i)` / `intrinsic(mad ...)` index shapes, straight-line precomputed base locals such as `var int base = id * 4; sum += input[base + i]`, reversed `i + base` forms, and constant-offset bases such as `sum += input[((id * 4) + 8) + i]`. Local base aliases are deliberately conservative: only preceding straight-line `int` declarations are considered, assignments invalidate aliases, and control-flow boundaries clear the alias set. The pass rewrites the optimized review body to `vload4` plus ordered scalar lane reduction through `Float4`, records per-case `RewriteKind=fixed-width-contiguous-vload4-ordered-reduction`, intentionally avoids `dot` / reassociation, requires no fast-math permission, reconstructs a matching review typed body for the supported `ir-text-v1` subset, validates that reconstructed graph through shared `GpuIrTypedBodyGraphPatch` reachability before counting it as materialized, falls back to typed-body invalidation when surrounding body text cannot be reconstructed or the reconstructed graph is incomplete, and leaves the original runtime IR selected. Regression coverage now verifies both sides of that boundary: a validated typed-body rebuild travels provider -> bridge -> dumper -> `validateOpenClRuntimeIrOptimizerEvidence` as `review-ready` while `backend.opencl-c` remains original, and the fail-closed case stays blocked when source vectorization succeeds but typed-body rebuild is rejected. `GpuIrTypedDeadCodePreviewProposalProvider` now adds the first typed dead-code proof surface by walking typed-body roots and recording node, reachable-node, unreachable-node, unreachable-kind, blocker, policy, proof, and safety evidence without proposing a rewrite. It explicitly records missing-root, missing-child-reference, and side-effecting-unreachable-node blockers plus required runtime-equivalence/approval gates and unresolved side-effect-freedom proof flags, so dead-code cleanup readiness can be measured without enabling optimized IR selection. OpenCL runtime validation now aggregates that preview evidence into `typedDeadCodePreview.*` fields in `runtime-ir-optimizer-evidence.properties` and surfaces pass, unreachable-node, and blocker counts in the `Runtime IR Optimizer Evidence` report section. `GpuIrTypedDeadCodeMaterializationProposalProvider` now adds the first non-constant review-only materialized transform. It removes only unreachable pure typed nodes from typed bodies, leaves method text bodies unchanged, blocks on missing roots, missing child references, or side-effecting unreachable nodes, and records per-case method/node/rewrite-kind runtime-equivalence payload evidence with `runtimeEquivalencePayload.CaseIdentity=method-name-and-node-id`. The default bridge still keeps the original runtime IR selected and treats the optimized artifact as a review candidate only. - OpenCL runtime validation now aggregates safe-local-CSE materialization into `safeLocalCseMaterialization.*` fields in `runtime-ir-optimizer-evidence.properties` and surfaces pass, local-binding, transformed-node, body-text-replacement, payload, status, and first-blocker counts in the `Runtime IR Optimizer Evidence` report section. It also aggregates mad/FMA materialization into `madFmaMaterialization.*` fields and surfaces pass, candidate, transformed-node, body-text-replacement, fixed-point-pass, skipped-blocker, fast-math policy, payload, status, and first-blocker counts. Clamp, step, and mix materialization are aggregated into `clampMaterialization.*`, `stepMaterialization.*`, and `mixMaterialization.*` fields and surface candidate/transformed-node/body-text-replacement counts, direct versus inverted step accounting, canonical/expanded/MAD-expanded mix accounting, fast-math or strict-float safety evidence, payload, status, and first-blocker counts. Loop-vectorization materialization is aggregated into `loopVectorizationMaterialization.*` fields and surfaces pass, candidate, transformed-loop, changed-method-body, body-text-replacement, typed-body-materialization/invalidation, skipped-blocker, payload, status, and first-blocker counts. Typed dead-code materialization is aggregated into `typedDeadCodeMaterialization.*` fields and surfaces pass, typed-node, unreachable-node, removed-node, payload, status, first-blocker, and side-effect-freedom counts. `runtimeEquivalenceReview.*` now includes `constant-folding-materialization`, `safe-local-cse-materialization`, `mad-fma-materialization`, `clamp-materialization`, `step-materialization`, `mix-materialization`, `loop-vectorization-materialization`, and `typed-dead-code-materialization` readiness in the family summary before review-package approval can progress. + OpenCL runtime validation now aggregates safe-local-CSE materialization into `safeLocalCseMaterialization.*` fields in `runtime-ir-optimizer-evidence.properties` and surfaces pass, local-binding, introduced-temporary, transformed-node, body-text-replacement, payload, status, and first-blocker counts in the `Runtime IR Optimizer Evidence` report section. It also aggregates mad/FMA materialization into `madFmaMaterialization.*` fields and surfaces pass, candidate, transformed-node, body-text-replacement, fixed-point-pass, skipped-blocker, fast-math policy, payload, status, and first-blocker counts. Clamp, step, and mix materialization are aggregated into `clampMaterialization.*`, `stepMaterialization.*`, and `mixMaterialization.*` fields and surface candidate/transformed-node/body-text-replacement counts, direct versus inverted step accounting, canonical/expanded/MAD-expanded mix accounting, fast-math or strict-float safety evidence, payload, status, and first-blocker counts. Loop-vectorization materialization is aggregated into `loopVectorizationMaterialization.*` fields and surfaces pass, candidate, transformed-loop, changed-method-body, body-text-replacement, typed-body-materialization/invalidation, typed-body rebuild attempted/parsed/built/graph-validated/rejected counts, rebuild status/first-blocker, skipped-blocker, payload, status, and first-blocker counts. Typed dead-code materialization is aggregated into `typedDeadCodeMaterialization.*` fields and surfaces pass, typed-node, unreachable-node, removed-node, payload, status, first-blocker, and side-effect-freedom counts. `runtimeEquivalenceReview.*` now includes `constant-folding-materialization`, `safe-local-cse-materialization`, `mad-fma-materialization`, `clamp-materialization`, `step-materialization`, `mix-materialization`, `loop-vectorization-materialization`, and `typed-dead-code-materialization` readiness in the family summary before review-package approval can progress. The same runtime evidence now includes `previewReadiness.*` aggregate fields over constant folding, safe-local CSE, and typed dead-code cleanup. The aggregate records overall status, recorded/candidate/blocked family counts, and a per-family status summary (`not-recorded`, `no-candidates`, `candidates-recorded`, `blocked-by-proof`, or `ready-for-runtime-equivalence-review`) so CI can track optimizer-family readiness without enabling production mutation or selected-IR replacement. `runtimeEquivalenceReview.*` now sits above `previewReadiness.*` as a fail-closed review gate. It records whether preview and materialized families are eligible for runtime-equivalence review, whether review evidence is required, the first blocker, the family summary, and explicitly disabled production mutation / selected-IR replacement so review readiness cannot be confused with production optimization enablement. `reviewPackage.*` now sits above runtime-equivalence review as the manual-review bundle boundary. It records package status, requirement/completeness, first blocker, proposal-pass count, pending approval count, original/optimized IR and proof-summary requirements, and repeats manual-review-only plus disabled production mutation / selected-IR replacement guardrails so review packaging remains evidence-only. `GpuIrProposalRuntimeBridgePass` now composes proposal-only materialized candidates through a separate review-working artifact: later providers can see earlier review transforms such as constant folding before safe-local-CSE and `mad/fma` materialization run, while the runtime-selected `artifact` remains the original unless mutation is explicitly allowed. This makes `optimized.irgpu.properties` / `optimized.backend.opencl-c` useful as an accumulated before/after review file without changing `backend.opencl-c`. - `validateOpenClRuntimeIrOptimizerEvidence` now validates those raw runtime evidence files in the OpenCL operational/NVIDIA routines, failing CI if optimized-artifact candidate or review-package guardrails disappear, if production mutation or selected-IR replacement becomes enabled, if candidate selection is applied, or if a required package is marked complete/missing a blocker before an activation design exists. It now also validates present materialization-family evidence: `safeLocalCseMaterialization.*` cannot reach review-ready without existing-local reuse, dominance/side-effect proof, matching text replacement, runtime-equivalence/approval requirements, and passed payloads; `madFmaMaterialization.*` cannot lose matching text replacements, fast-math policy evidence, runtime-equivalence/approval requirements, passed payloads, or a consistent first blocker; `clampMaterialization.*` cannot lose matching text replacement, strict-float/no-fast-math safety, runtime-equivalence/approval requirements, passed payloads, or a consistent first blocker; `stepMaterialization.*` cannot lose direct/inverted step accounting, strict comparison/equality/NaN safety, matching text replacement, runtime-equivalence/approval requirements, passed payloads, or a consistent first blocker; `mixMaterialization.*` cannot lose canonical/expanded/MAD-expanded accounting, required fast-math/reassociation evidence, matching text replacement, runtime-equivalence/approval requirements, passed payloads, or a consistent first blocker; `loopVectorizationMaterialization.*` cannot reach review-ready without matching text replacements, fixed trip-count proof, contiguous-load proof, ordered-reduction preservation, typed-body materialization/invalidation accounting, runtime-equivalence/approval requirements, and passed payloads; and `typedDeadCodeMaterialization.*` cannot reach review-ready without consistent node counts, side-effect proof, runtime-equivalence/approval requirements, and passed payloads. + `GpuRuntimeCompileOptions.openClIrOptimizerExperimentalApply(...)` now exposes an explicit local-test apply mode backed by `runtime.irOptimizerSelection=experimental-apply`. The default bridge remains review-only, but the compile option lets the bridge pass `mutationAllowed=true`, select a validated optimized artifact, lower the optimized backend source for the actual OpenCL compile path, and record `experimentalApply.*` plus selected-candidate evidence. Production-like profiles still pass through the existing production IR acceptance gate, and normal CI validation continues to reject applied optimized selection unless `validateOpenClRuntimeIrOptimizerEvidence --allow-experimental-apply` is used for local apply-mode artifacts. + `validateOpenClRuntimeIrOptimizerEvidence` now validates those raw runtime evidence files in the OpenCL operational/NVIDIA routines, failing CI if optimized-artifact candidate or review-package guardrails disappear, if production mutation or selected-IR replacement becomes enabled, if candidate selection is applied, or if a required package is marked complete/missing a blocker before an activation design exists. It now also validates present materialization-family evidence: `safeLocalCseMaterialization.*` cannot reach review-ready without existing-local reuse or introduced-temp evidence, dominance/side-effect proof, matching text replacement, runtime-equivalence/approval requirements, and passed payloads; `madFmaMaterialization.*` cannot lose matching text replacements, fast-math policy evidence, runtime-equivalence/approval requirements, passed payloads, or a consistent first blocker; `clampMaterialization.*` cannot lose matching text replacement, strict-float/no-fast-math safety, runtime-equivalence/approval requirements, passed payloads, or a consistent first blocker; `stepMaterialization.*` cannot lose direct/inverted step accounting, strict comparison/equality/NaN safety, matching text replacement, runtime-equivalence/approval requirements, passed payloads, or a consistent first blocker; `mixMaterialization.*` cannot lose canonical/expanded/MAD-expanded accounting, required fast-math/reassociation evidence, matching text replacement, runtime-equivalence/approval requirements, passed payloads, or a consistent first blocker; `loopVectorizationMaterialization.*` cannot reach review-ready without matching text replacements, fixed trip-count proof, contiguous-load proof, ordered-reduction preservation, typed-body materialization/invalidation accounting, runtime-equivalence/approval requirements, and passed payloads; and `typedDeadCodeMaterialization.*` cannot reach review-ready without consistent node counts, side-effect proof, runtime-equivalence/approval requirements, and passed payloads. - [x] Keep vendor optimization as a separate optional provider layer. `ir-vendor-optimizer` now exists as a separate optional module skeleton. It contributes vendor proposals only through `GpuIrVendorOptimizationProposalProvider` and `GpuIrVendorOptimizationProposalRegistry`, while the default runtime bridge keeps loading only backend-neutral proposal providers. Future vendor modules may consume target device profile, backend compiler feedback, resource metrics, driver quirks, launch advisories, and promotion evidence, but missing modules, missing matching device evidence, or disabled vendor adaptation must leave backend-neutral IR selection unchanged. - [x] Gate production optimizer selection through approval-bound artifacts. @@ -729,11 +730,11 @@ Current rule: `ir-validation` remains strictly read-only. Optimizer modules may - [x] Materialize interpolation trees as review-only `mix` optimized artifacts. `GpuIrMixMaterializationProposalProvider` now rewrites typed canonical interpolation trees to OpenCL `mix(a, b, t)` review candidates. It also recognizes expanded weighted and generated `mad(b, t, (1.0F - t) * a)` forms when fast-math is allowed, including the optimizer-journal density shape, and records whether algebraic reassociation was required. Typed body and `ir-text-v1` are updated together while selected runtime IR remains unchanged. - [x] Seed the shared typed-body graph-patch scaffold for peephole materializers. - `GpuIrTypedBodyGraphPatch` now centralizes deterministic typed-node lookup, reachable-node scanning, next-node allocation, root replacement, append-only auxiliary node insertion, intrinsic-call node construction, a fail-closed `Plan.apply(...)` path that validates typed-root and text-pattern availability before applying text + typed-body patches together, and shared patch-blocker taxonomy for provider counters. The review-only `clamp`, `step`, `mix`, and `mad/fma` materializers now use this scaffold for transactional patch mechanics while preserving their matcher logic, runtime-equivalence evidence, rollback behavior, and original selected IR. + `GpuIrTypedBodyGraphPatch` now centralizes deterministic typed-node lookup, reachable-node scanning with missing-root / missing-child diagnostics, next-node allocation, root replacement, append-only auxiliary node insertion, intrinsic-call node construction, a fail-closed `Plan.apply(...)` path that validates typed-root and text-pattern availability before applying text + typed-body patches together, binding-aware text replacement offsets, and shared patch-blocker taxonomy for provider counters. The review-only `clamp`, `step`, `mix`, `mad/fma`, constant-folding, and safe-local-CSE materializers now use this scaffold for transactional patch mechanics; safe-local-CSE plus typed-dead-code preview/materialization also use the shared reachability report while preserving their matcher logic, runtime-equivalence evidence, rollback behavior, and original selected IR. - [ ] Materialize typed peephole rewrites into optimized review artifacts. Add the shared graph rewrite engine for the remaining rule families and broader generalized materialization: allocate deterministic node ids, build intrinsic replacement nodes, apply graph patches, rebuild a distinct optimized `IrGpu` artifact, regenerate review backend source, and preserve rollback to original IR on any validation or evidence failure. `mad/fma`, `clamp`, `step`, `mix`, safe-local-CSE, constant folding, typed-DCE, and the first loop-vectorization slice currently use dedicated conservative materializers rather than a shared graph-patch engine. - [ ] Broaden peephole rules from canonical shapes to real generated forms. - Add regression fixtures for additional real generated clamp/step/mix variants: reordered clamp argument trees, inverted or swapped ternary mask branches, literal spelling variants, structurally duplicated but text-equivalent interpolation operands, helper-call operands, and non-`mad` expanded interpolation forms. Equality semantics and floating-point reassociation must be explicit blockers unless proven safe. + Add regression fixtures for additional real generated clamp/step/mix variants: reordered clamp argument trees, inverted or swapped ternary mask branches, literal spelling variants, structurally duplicated but text-equivalent interpolation operands, helper-call operands, and non-`mad` expanded interpolation forms. Treat this as a short-term alpha hardening path only: broad combinatorial algebra should move to the future e-graph / equality-saturation core instead of growing endless hand-written tree-shape conditions. Equality semantics and floating-point reassociation must be explicit blockers unless proven safe. - [ ] Add an optional compile lifecycle event journal. Provide an opt-in journal that records the path from program launch through descriptor loading, `IrGpu` loading, validation, optimizer-provider discovery, proposal execution, runtime-equivalence/review-package state, backend source selection, backend compilation, fallback/rollback, invocation, and shutdown. The journal should be useful for debugging without changing default runtime behavior. - [ ] Promote optional `ir-optimizer` participation into the full runtime path when present. @@ -745,6 +746,33 @@ Current rule: `ir-validation` remains strictly read-only. Optimizer modules may - [ ] Evaluate the pre-existing private-array scalarizer prototype as an optimizer family. The attached prototype code is a useful starting point for a future `private-array-scalarization` provider, but it needs to be ported into the current immutable proposal + typed-body contract, use conservative side-effect/index proofs, emit before/after review artifacts, and remain blocked from production selection until runtime-equivalence and rollback evidence exist. +### I4.2 IR-Optimizer v2 core: e-graphs and equality saturation + +Goal: move the long-term optimizer core from isolated hand-written peephole conditions to an algebraic optimizer that can reason over many equivalent expression forms at once, then choose the best safe form for the selected backend and policy. + +Current rule: keep the existing alpha materializers useful and conservative. The v2 core must plug into the same optional proposal/review/apply pipeline, produce `optimized.*` review artifacts first, and stay fail-closed behind validation, runtime-equivalence, rollback, approval, and production-promotion gates. + +- [ ] Define the v2 optimizer boundary as a backend-neutral algebraic expression optimizer. + The first v2 scope should cover pure expression regions only: local initializers, assignment RHS expressions, return expressions, ternary mask expressions, and intrinsic/math trees with no memory writes, atomics, barriers, volatile effects, or control-flow rewrites. Loops and memory-sensitive transformations should stay outside the first equality-saturation core unless represented as explicitly proven regions. +- [ ] Add typed-IR region extraction for e-graph input. + Build a deterministic extractor from reachable `ir-tree-v1` nodes into small pure expression regions, carrying node ids, source method/body identity, type information, numeric kind, purity/effect classification, fast-math policy, device/backend context, and original text anchors. Unsupported nodes should become explicit blockers rather than partial rewrites. +- [ ] Introduce an optimizer expression language and rewrite-rule catalog. + Model literals, variables, arithmetic, comparisons, select/ternary masks, OpenCL-style intrinsics, vector constructors, and safe casts in a compact internal expression form. Seed the catalog with the existing alpha families: constant identities, constant folding candidates, `mad/fma`, `clamp`, `step`, `mix`, safe CSE shapes, and later dot/vector patterns. Every rule must declare semantic guards such as strict-float, fast-math-required, integer-overflow behavior, NaN/equality behavior, and backend availability. +- [ ] Implement the first bounded e-graph saturation engine. + Add deterministic equality-saturation passes with hard budgets for nodes, e-classes, iterations, time, and per-method work. Saturation must report why it stopped (`fixed-point`, `budget`, `unsupported-node`, `policy-gate`, `proof-blocker`) and must never mutate the original `IrGpu` while exploring alternatives. +- [ ] Add a backend-aware cost model and extractor. + Choose the best expression from an e-class using a cost model that can prefer `clamp` over `min(max(...))`, `mad` over `mul+add` only when policy permits, `mix` over expanded interpolation when safe, and scalar/vector forms based on backend/device hints. The cost model should be explainable and conservative: if two choices have unclear semantics or proof, keep the original shape. +- [ ] Emit an equality-saturation journal and review evidence. + Extend optimizer evidence with e-graph-specific fields: region count, e-class/e-node counts, applied rule counts, saturation stop reason, selected extraction cost, rejected lower-cost blockers, semantic guard decisions, and before/after expression summaries. This should feed the future compile lifecycle journal without changing default runtime behavior. +- [ ] Lower selected e-graph expressions back through the immutable proposal pipeline. + Convert the chosen expression back into typed IR and `ir-text-v1` using the shared graph-patch/materialization boundary, allocate deterministic node ids, regenerate review backend source, validate the transformed artifact, and preserve rollback to the original artifact on any mismatch. The selected runtime IR must remain original unless experimental apply or future production gates explicitly allow mutation. +- [ ] Migrate existing peephole families onto the v2 core gradually. + Keep alpha materializers as regression or fallback implementations while moving `mad/fma`, `clamp`, `step`, `mix`, constant folding, and safe local CSE into the e-graph rule catalog. Close each migration only when the v2 output matches or improves the current review artifacts and emits equal or stronger evidence. +- [ ] Keep vendor optimization as an overlay, not a hidden mutator. + Vendor/device-specific optimizer modules should be able to contribute extra rewrite rules, backend availability metadata, and cost-model weights, but they must still use the same e-graph proof/evidence path, proposal contract, approval manifests, and fail-closed runtime selection gates. +- [ ] Add v2 regression fixtures from real generated kernels. + Build fixtures that prove equality saturation handles equivalent generated forms without ordering-sensitive pass bugs: expanded `mix`, `mad`-lowered interpolation, strict/inclusive `step`, nested clamp trees, repeated common subexpressions, constant/identity chains, and future vector/load patterns. Include negative fixtures for unsafe float reassociation, side effects, unsupported memory access, and budget exhaustion. + ## Recommended Execution Order If the goal is to move forward pragmatically from the current state, the best order is now: @@ -757,13 +785,15 @@ If the goal is to move forward pragmatically from the current state, the best or Start with the non-mutating foundation: canonical `IrGpu` artifact, dual output beside current OpenCL source, OpenCL-from-`IrGpu` parity, runtime compile request/options, backend-lowering boundary, and source/ASM frontends feeding the same IR storage path. Keep runtime optimization profile `off` by default. 4. `I4 Optional IR optimizer module split` Before implementing real optimizer transforms, freeze the module boundary: validator stays read-only, backend-neutral optimizer contracts live behind optional dependencies, vendor optimizers are separate providers, and all mutation remains proposal/opt-in/fail-closed until proof and rollback are present. -5. `A1 Intel runner bring-up when hardware exists` +5. `I4.2 IR-Optimizer v2 e-graph core` + After the current alpha optimizer remains useful in review/apply mode, start the v2 algebraic core on pure expression regions only: typed-IR extraction, guarded rewrite catalog, bounded equality saturation, backend-aware cost model, review evidence, and immutable proposal lowering. Keep existing peephole materializers as fallback/regression fixtures until each family migrates safely. +6. `A1 Intel runner bring-up when hardware exists` The remaining cross-vendor production gate stays open until an Intel OpenCL stack can run the same bucket set. -6. `Improved ASM parser/frontend` +7. `Improved ASM parser/frontend` Broader ASM ingestion should lower into `IrGpu` first, then reuse the same runtime compile request, validation, backend lowering, and future optimizer path instead of growing a separate pipeline. The first broader-ASM diagnostics layer is already in place through `asmFailure.*` metadata and public preflight APIs. -7. `I3/I4 prototype runtime optimization` +8. `I3/I4 prototype runtime optimization` After `IrGpu` storage and OpenCL parity are stable, add opt-in prototype optimization with rollback, pre/post runtime-equivalence artifacts, and NVIDIA/AMD evidence first. Keep production mutation disabled until A1/A2 confidence and the future Intel gate are satisfied. -8. `H Lower-priority backend/optimization work` +9. `H Lower-priority backend/optimization work` ## Current Working Conclusion @@ -771,7 +801,7 @@ JavaToGpu is already beyond the "toy compiler" stage. The broad repetitive intrinsic-family generation detour is now closed for current priorities, so the active focus returns to production-core/runtime validation rather than expanding optional API symmetry. -After the first serious dogfooding pass, the main repo-local language/runtime gaps for the selected workload classes are no longer the active blocker. Section C and F1 are closed for current practical workload coverage: 3D launch config, union-style packed views, root-blob ergonomics, launch-sensitive attributes, and the focused packed/root-blob dogfooding slice are all covered. Operational confidence now includes NVIDIA and AMD lanes; Intel remains the remaining hardware validation gap. The next major architecture frontier is the I3 Production IR pipeline plus the I4 optional optimizer split: `IrGpu` should become the stored backend-neutral artifact, OpenCL/CUDA/Vulkan/Metal should become backend lowerers selected at runtime from compile options and device profile, `ir-validation` must remain read-only, and future `ir-optimizer` / `ir-vendor-optimizer` modules should plug in as optional proposal/opt-in/fail-closed providers. Broader ASM ingestion should feed that same `IrGpu` path instead of creating a second compiler pipeline. +After the first serious dogfooding pass, the main repo-local language/runtime gaps for the selected workload classes are no longer the active blocker. Section C and F1 are closed for current practical workload coverage: 3D launch config, union-style packed views, root-blob ergonomics, launch-sensitive attributes, and the focused packed/root-blob dogfooding slice are all covered. Operational confidence now includes NVIDIA and AMD lanes; Intel remains the remaining hardware validation gap. The next major architecture frontier is the I3 Production IR pipeline plus the I4 optional optimizer split: `IrGpu` should become the stored backend-neutral artifact, OpenCL/CUDA/Vulkan/Metal should become backend lowerers selected at runtime from compile options and device profile, `ir-validation` must remain read-only, and future `ir-optimizer` / `ir-vendor-optimizer` modules should plug in as optional proposal/opt-in/fail-closed providers. The current rule-based IR-Optimizer alpha is a useful evidence/materialization layer, but the long-term v2 core should become an e-graph / equality-saturation optimizer for pure algebraic expression regions so broad generated-form optimization does not devolve into ordering-sensitive hand-written tree conditions. Broader ASM ingestion should feed that same `IrGpu` path instead of creating a second compiler pipeline. Latest I2/L2 reconciliation: the current CI contract layer is closed for present priorities. Direct contract coverage now pins current readiness CI summaries, optimizer-gate consistency/acceptance, optimizer CI gate-index consistency/acceptance, regression/baseline CI summaries, nested artifact-field composition, and opt-in real-example dogfooding reports for `examples-app` / `test-app`. The dogfooding contract keeps safety fail-fast while treating optimizer readiness as a read-only stability artifact: production mutation must remain disabled, rewrite applicability must stay false, known CSE policy blockers are counted, and no-candidate CSE literal-promotion / auto-vectorization blockers are accepted as the current baseline. The remaining I2 items are intentionally broader frontier work: deeper transformation-safety proofs, stronger canonicalization/common-computation detection, runtime-equivalence evidence for selected optimizer families, and eventual production rewrite promotion after A1/A2 runtime confidence is stable. These should stay open until backed by runtime evidence rather than being marked complete from read-only artifact coverage alone. diff --git a/docs/IR-Optimizer.md b/docs/IR-Optimizer.md index 812224b5..efd9453b 100644 --- a/docs/IR-Optimizer.md +++ b/docs/IR-Optimizer.md @@ -1,18 +1,288 @@ # IR Optimizer -The optional IR optimizer module is the future backend-neutral transform layer for `IrGpu` artifacts. +IR Optimizer is an optional optimization layer for generated GPU IR and backend +code. Think of it as a safe "try to make this kernel cleaner" step: it can look +at the generated IR, propose improvements, dump before/after files, and explain +why each optimization was or was not applied. -The current module is intentionally proposal-first. It contributes ServiceLoader-discovered providers for no-op participation, conservative backend-neutral proposals, preview-only proof surfaces, and narrowly scoped review-only materialized candidates without changing the selected IR by default. This keeps the default runtime behavior safe while establishing the module, packaging, and report boundary for future optimization families. +The most important rule is simple: -## First Alpha Scope +> By default, the optimizer is review-only. It can generate an optimized +> candidate, but the runtime still compiles the normal selected backend code. -The first alpha is an evidence and contract boundary, not a production rewrite release. It includes the optional `ir-optimizer` / `ir-vendor-optimizer` module split, immutable proposal contracts, the validation sandwich, preview-only optimizer families, backend-neutral source materialization, constrained review-only constant-folding materialization, review-only safe-local-CSE materialization that reuses an existing local value, review-only fast-math `mad/fma` materialization, review-only `clamp` materialization for generated `min(max(x, lo), hi)` shapes, review-only `step` materialization for ternary masks, review-only `mix` materialization for interpolation shapes, review-only fixed-width loop-vectorization materialization, typed dead-code materialization for unreachable pure typed nodes, approval-template metadata, optimized-artifact candidate envelopes, and OpenCL evidence/report/validator guardrails. +That makes it useful while the optimizer is still growing. You can inspect what +it would do, compare artifacts, validate the evidence, and only opt into real +application during local experiments. -The first shared typed-body patch scaffold is `GpuIrTypedBodyGraphPatch`. It owns deterministic typed-node lookup, reachable-node scanning, next-node allocation, root replacement, append-only auxiliary node insertion, intrinsic-call node construction, fail-closed `Plan.apply(...)` validation for text + typed-body patches, and shared patch-blocker taxonomy for provider counters. `clamp`, `step`, `mix`, and `mad/fma` use it for transactional patch mechanics while their matcher logic, runtime-equivalence evidence, and fail-closed selected-IR behavior remain provider-owned. +## When To Use It + +Use the IR Optimizer when you want to: + +- inspect optimization opportunities in generated OpenCL C; +- dump code before and after optimizer passes; +- test optimizer rules on real kernels without changing production behavior; +- compare optimizer evidence with the generated backend source; +- write custom backend-neutral or vendor-specific optimizer providers. + +Do not treat the default optimizer path as a production rewrite switch yet. The +safe workflow is: generate original code, generate an optimized candidate for +review, compare evidence, and then deliberately opt into local apply mode only +when you want to test compiled optimized code. + +## Quick Start + +For the normal safe path, compile as usual and enable diagnostic artifact output +through the runtime options used by your app or example: + +```java +GpuRuntimeCompileOptions.openCl(List.of(), "diagnostic") +``` + +This keeps the runtime in review-only mode. After running a kernel, inspect the +dumped files: + +| File | Meaning | +| --- | --- | +| `original.backend.opencl-c` | Backend code before optimizer review | +| `optimized.backend.opencl-c` | Optimizer candidate, if one was produced | +| `backend.opencl-c` | Code selected for compilation | +| `runtime-ir-handoff.properties` | Which runtime stage/source was selected | +| `runtime-ir-optimizer-evidence.properties` | Optimizer pass evidence and blockers | + +If `optimized.backend.opencl-c` exists but `backend.opencl-c` still looks like +the original code, that is expected. It means the optimizer produced a review +candidate, but the runtime stayed in safe review-only mode. + +## Applying Optimizations Locally + +To test optimized code as the compiled backend source, use the explicit local +apply option: + +```java +GpuRuntimeCompileOptions.openClIrOptimizerExperimentalApply(List.of(), "diagnostic") +``` + +This sets the runtime selection mode to: + +```properties +runtime.irOptimizerSelection=experimental-apply +``` + +Use this mode for local experiments, examples, and optimizer development. It is +designed to answer: "What happens if we actually compile the optimized +candidate?" + +It is not a production toggle. Production-like paths still remain fail-closed +behind proof, rollback, approval, and promotion gates. Validator tooling also +requires intentional local permission for these artifacts, for example with +`--allow-experimental-apply` where applicable. + +## Reading The Output + +Start with these checks: + +1. Open `original.backend.opencl-c` and `optimized.backend.opencl-c` side by side. +2. Check `backend.opencl-c` to see what was actually compiled. +3. Open `runtime-ir-handoff.properties` and look for the optimizer selection mode. +4. Open `runtime-ir-optimizer-evidence.properties` and search for `optimizerFamily`, + `outcome`, `firstBlocker`, and `experimentalApply`. + +Common interpretations: + +| What you see | What it means | +| --- | --- | +| No `optimized.backend.opencl-c` | No materialized candidate was produced | +| Optimized file exists, backend file unchanged | Normal review-only behavior | +| Backend file matches optimized file | Experimental apply selected the candidate | +| Evidence has `firstBlocker` | A rule saw a shape but refused to rewrite safely | +| Evidence says `policy-gate` | `@GPUOptimize` or runtime policy disabled that family | + +## What It Can Optimize Today + +The current optimizer is intentionally conservative. Most rules target common +generated OpenCL patterns and only rewrite when the matcher can prove the shape +is exactly the one it understands. + +| Family | Example before | Example after | Notes | +| --- | --- | --- | --- | +| Constant folding | `((2 + 3) * 4)` | `20` | Safe integer folds and identities like `x + 0`, `x * 1` | +| Safe local CSE | repeated `(value * 1.414F)` | local temp reuse | Reuses existing locals or introduces conservative temps | +| Mad/FMA | `(a * b) + c` | `mad(a, b, c)` | Requires fast-math permission | +| Clamp | `min(max(x, lo), hi)` | `clamp(x, lo, hi)` | Preserves argument order | +| Step | `a > b ? 1.0F : 0.0F` | `1.0F - step(a, b)` | Preserves strict comparison behavior | +| Mix | `a + t * (b - a)` | `mix(a, b, t)` | Expanded algebraic forms may require fast math | +| Loop vectorization | fixed `i < 4` contiguous load reduction | `vload4` + lane reduction | Keeps ordered scalar reduction, no `dot` reassociation | +| Typed dead code | unreachable pure typed nodes | removed from candidate | Review artifact only | + +### Clamp + +Generated code may contain nested min/max calls: + +```c +float clampedWave = min(max(wave1, -1.0F), 1.0F); +``` + +The clamp rule can materialize the clearer GPU intrinsic: + +```c +float clampedWave = clamp(wave1, -1.0F, 1.0F); +``` + +The rule currently matches the conservative shape `min(max(x, lo), hi)`. It does +not yet rewrite every algebraically equivalent ordering, because argument order +and floating-point edge cases are part of the safety contract. + +### Step + +Generated masks often start as ternaries: + +```c +float isSolid = (blend > threshold) ? 1.0F : 0.0F; +``` + +The optimizer can express this through OpenCL `step`. For strict comparisons it +uses an inverted form so equality behavior stays correct: + +```c +float isSolid = 1.0F - step(blend, threshold); +``` + +Inclusive comparisons can usually become direct `step(edge, x)` shapes. + +### Mix + +Linear interpolation patterns can become `mix`: + +```c +float y = a + t * (b - a); +``` + +Candidate output: + +```c +float y = mix(a, b, t); +``` + +The optimizer also recognizes some expanded generated forms, including patterns +that already passed through `mad(...)`. Expanded forms can change operation +ordering, so they require fast-math permission. + +### Loop Vectorization + +The vectorization rule recognizes a narrow, useful shape: + +```c +float sum = 0.0F; +for (int i = 0; i < 4; i = i + 1) { + sum = sum + input[mad(id, 4, i)]; +} +``` + +The review candidate can use a `vload4`-style load and then reduce lanes in the +same order. The rule intentionally avoids `dot` for now because `dot` would +reassociate floating-point additions. + +## Tuning With @GPUOptimize + +`@GPUOptimize` is the public policy hook for optimizer behavior. It lets the +kernel author describe what kind of optimization is allowed without tying the +code to one backend. + +The policy can control: + +- whether optimization is enabled at all; +- optimizer profile, such as `off` or `diagnostic`; +- enabled and disabled optimization families; +- fast-math permission; +- dump/journal hints; +- vendor adaptation permission; +- vectorization and resource-shaping preferences; +- production intent. + +The optimizer treats policy as a gate, not a suggestion. If a family is disabled +or fast math is not allowed, matching providers must skip or block the rewrite +and record that decision in evidence. + +## Safety Model + +The optimizer is designed to fail closed. + +- `ir-validation` remains read-only and never mutates `IrGpu`. +- Optimizer providers must not mutate the input artifact in place. +- A real rewrite must return a distinct optimized artifact. +- Missing proof, missing approval, failed validation, or disabled policy keeps + the original IR selected. +- Review candidates are allowed to exist without being selected for compilation. +- Experimental apply is explicit and recorded in evidence. + +This lets new rules be developed in small steps: first detect the pattern, then +dump evidence, then materialize a review candidate, then eventually prove and +promote the rule. + +## Optimizer Journal + +The optimizer evidence file is the current journal surface. It records which +providers ran, what they proposed, why they skipped, and whether any candidate +was selected. + +Useful fields to search for: + +| Field | Why it helps | +| --- | --- | +| `optimizerFamily` | Which rule family produced the evidence | +| `RewriteKind` | What rewrite was planned or materialized | +| `firstBlocker` | Why a rule refused to continue | +| `policy.*` | Runtime and annotation policy used for the pass | +| `runtimeEquivalencePayload.*` | Proof payload metadata for review | +| `optimizedArtifactCandidate.*` | Candidate artifact selection state | +| `experimentalApply.*` | Whether local apply mode selected optimized code | + +A future higher-level journal can cover the whole run from program discovery to +backend compilation. For now, the optimizer evidence is intentionally structured +so tools can build that user-facing timeline later. + +## Vendor Optimizers + +Backend-neutral rules live in `ir-optimizer`. Vendor or device-specific rules +should live behind the vendor optimizer SPI instead of being loaded silently into +the default path. + +This separation keeps the default optimizer portable while still allowing future +modules such as `ir-vendor-optimizer` to add targeted behavior for a vendor, +driver, device class, or backend. + +Vendor providers use the same proposal model and validation sandwich as normal +providers. They must still provide evidence, diagnostics, rollback information, +and proof metadata before a rewrite can be considered safe. + +## Troubleshooting + +| Problem | Likely cause | What to check | +| --- | --- | --- | +| Optimized file is missing | No rule materialized a candidate | Search evidence for `firstBlocker` | +| `mad(...)` is not emitted | Fast math is disabled | Check `@GPUOptimize(fastMath = true)` / policy fields | +| `mix(...)` is not emitted | Shape is unsupported or fast math is required | Compare expression shape and evidence blockers | +| `backend.opencl-c` is not optimized | Review-only mode is active | Use experimental apply for local testing | +| Validator rejects apply artifacts | Apply mode was not explicitly allowed | Pass the local `--allow-experimental-apply` validator flag | +| Vendor rule does not run | Vendor registry was not explicitly loaded | Check provider registration and runtime wiring | + +## Implementation Reference + +The rest of this page keeps the lower-level optimizer contract details for +authors and maintainers. Most users can stop at the sections above. + +

+Low-level optimizer contract and implementation notes + +### First Alpha Scope + +The first alpha is an evidence and contract boundary, not a production rewrite release. It includes the optional `ir-optimizer` / `ir-vendor-optimizer` module split, immutable proposal contracts, the validation sandwich, preview-only optimizer families, backend-neutral source materialization, constrained review-only constant-folding materialization, review-only safe-local-CSE materialization that reuses existing local values or introduces conservative local temporaries, review-only fast-math `mad/fma` materialization, review-only `clamp` materialization for generated `min(max(x, lo), hi)` shapes, review-only `step` materialization for ternary masks, review-only `mix` materialization for interpolation shapes, review-only fixed-width loop-vectorization materialization, typed dead-code materialization for unreachable pure typed nodes, approval-template metadata, optimized-artifact candidate envelopes, and OpenCL evidence/report/validator guardrails. + +The first shared typed-body patch scaffold is `GpuIrTypedBodyGraphPatch`. It owns deterministic typed-node lookup, reachable-node scanning with missing-root / missing-child diagnostics, next-node allocation, root replacement, append-only auxiliary node insertion, intrinsic-call node construction, fail-closed `Plan.apply(...)` validation for text + typed-body patches, binding-aware text replacement offsets, and shared patch-blocker taxonomy for provider counters. `clamp`, `step`, `mix`, `mad/fma`, constant-folding, and safe-local-CSE use it for transactional patch mechanics; safe-local-CSE plus typed-dead-code preview/materialization also use the shared reachability report while their matcher logic, runtime-equivalence evidence, and fail-closed selected-IR behavior remain provider-owned. It intentionally excludes runtime IR selection, selected-IR replacement, production mutation, real vendor rewrites, and any automatic choice of optimized artifacts. Those remain future gates above runtime-equivalence, rollback, approval, and production-promotion evidence. -## Contract +### Contract - `ir-validation` remains read-only and must not mutate `IrGpu`. - `ir-optimizer` owns backend-neutral optimizer contracts and general passes. @@ -22,7 +292,7 @@ It intentionally excludes runtime IR selection, selected-IR replacement, product - Missing optimizer modules, missing approval, failed proof, or disabled optimization must leave the original IR selected. - `@GPUOptimize` policy metadata is available to proposal providers through the runtime bridge context, including profile hints, fast-math permission, enabled/disabled family lists, journal/dump hints, production intent, vendor adaptation, vectorization preference, and resource-shaping intent. The runtime bridge now uses `enabledFamilies` / `disabledFamilies` and explicit `enabled = false` to gate provider invocation. Policy skips emit `ir-optimizer.policy-gate` proof fields, are aggregated into `runtime-ir-optimizer-evidence.properties` / markdown `policyGate.*` counters, and keep mutation, optimized-artifact selection, and selected-IR replacement disabled. -## Current Skeleton +### Current Skeleton `GpuIrNoOpOptimizationPass` reports a skipped optimizer pass at candidate-discovery stage. It proves that projects can add the optional optimizer module without enabling production mutation or changing generated backend source. @@ -40,9 +310,11 @@ The preview records blockers such as divide-by-zero, non-even division, unsuppor `GpuIrSafeLocalCsePreviewProposalProvider` is a second preview-only proof surface for broader safe local common-subexpression elimination. It scans typed IR for repeated local expressions, records expression/candidate/duplicate/equivalence-class counts, and reports blockers such as unsupported operators, impure operands, and control-flow boundaries. It always returns `NO_CHANGE`, marks the proof as preview-only, and records that dominance, side-effect freedom, runtime equivalence, and approval must be proven before any future broader rewrite can graduate. -`GpuIrSafeLocalCseMaterializationProposalProvider` is the first review-only safe-local-CSE transform. It does not introduce new temporaries; instead it reuses already materialized local bindings in straight-line `ir-text-v1` bodies and runs to a fixed point over reachable roots. For example, after `var int tmp = (a + b)`, later matching assignments such as `set output[0] = (a + b)` and `set output[1] = (a + b)` can become `set output[0] = tmp` and `set output[1] = tmp` in the optimized review artifact. The pass blocks on control-flow boundaries, missing typed bodies, missing child references, unsupported/impure operands, missing text patterns, missing existing local bindings, or OpenCL review-source emission failures. Proof fields use `optimizerFamily=safe-local-cse-materialization`, `RewriteKind=safe-local-cse-reuse-existing-local`, `runtimeEquivalencePayload.CaseIdentity=method-name-and-node-id`, `fixedPoint.pass.count`, `safety.newTemporaryIntroduced=false`, and `provider.mutatesOriginal=false`. By default the bridge still keeps the original runtime IR selected; only `optimized.backend.opencl-c` / `optimized.irgpu.properties` show the review candidate. +`GpuIrSafeLocalCseMaterializationProposalProvider` is the first review-only safe-local-CSE transform. It reuses already materialized local bindings in straight-line `ir-text-v1` bodies and runs to a fixed point over reachable roots. For example, after `var int tmp = (a + b)`, later matching assignments such as `set output[0] = (a + b)` and `set output[1] = (a + b)` can become `set output[0] = tmp` and `set output[1] = tmp` in the optimized review artifact. It can also introduce a conservative local temporary when the repeated pure binary expression has no existing binding, for example repeated `(value * 1.414F)` can become `var float jtg_cse0 = (value * 1.414F)` followed by uses of `jtg_cse0`. Introduced temporaries are limited to straight-line bodies, require all text occurrences to match the typed occurrences exactly, and are blocked if any input variable is reassigned between the first and last use. Existing-binding rewrites still use the shared transactional `GpuIrTypedBodyGraphPatch.Plan` path; introduced-temp rewrites rebuild the review typed roots with a cloned initializer subtree and validate reachability before returning a proposal. The pass blocks on control-flow boundaries, missing typed bodies, missing child references, unsupported/impure operands, missing text patterns, unsafe local invalidation, or OpenCL review-source emission failures. Proof fields use `optimizerFamily=safe-local-cse-materialization`, `RewriteKind=safe-local-cse-reuse-existing-local` or `safe-local-cse-introduce-local`, `introducedTemporary.count`, `runtimeEquivalencePayload.CaseIdentity=method-name-and-node-id`, `fixedPoint.pass.count`, `safety.newTemporaryIntroduced`, and `provider.mutatesOriginal=false`. By default the bridge still keeps the original runtime IR selected; only `optimized.backend.opencl-c` / `optimized.irgpu.properties` show the review candidate. + +Safe-local-CSE runtime evidence now distinguishes existing-local reuse from introduced-local reuse through `safeLocalCseMaterialization.localBinding.count`, `safeLocalCseMaterialization.introducedTemporary.count`, `safety.newTemporaryIntroduced`, and per-case rewrite kinds. Review-ready evidence may use either an existing binding or an introduced local temporary, but production mutation and selected-IR replacement remain disabled. -`GpuIrMadFmaMaterializationProposalProvider` is the first review-only typed peephole materializer. It requires fast-math permission from `@GPUOptimize(fastMath = true)` / optimizer policy, scans reachable typed `a * b + c` and `c + a * b` shapes, rewrites the matching `ir-text-v1` expression to `intrinsic(mad template="" args=[a, b, c])`, and replaces the typed root with a `GpuIrIntrinsicCall` review node. It runs one materialized rewrite per fixed-point pass, requires OpenCL review-source emission for OpenCL requests, and records `optimizerFamily=mad-fma-materialization`, `targetIntrinsic=mad`, `policy.fastMathAllowed`, `safety.fastMathRequired=true`, `safety.strictFloatPreserved=false`, `fixedPoint.pass.count`, `bodyTextReplacement.count`, and per-case `RewriteKind=mad-fma-to-opencl-mad`. Strict/default policy, missing typed body, unsupported text format, missing text pattern, or blocked OpenCL review-source emission returns `NO_CHANGE`. This can make `optimized.backend.opencl-c` show `mad(...)`, but `backend.opencl-c` remains the original selected source unless future proof/approval/selection gates explicitly allow otherwise. +`GpuIrMadFmaMaterializationProposalProvider` is the first review-only typed peephole materializer. It requires fast-math permission from `@GPUOptimize(fastMath = true)` / optimizer policy, scans reachable typed `a * b + c`, `c + a * b`, and `a * b - c` shapes, rewrites the matching `ir-text-v1` expression to `intrinsic(mad template="" args=[a, b, c])` or `intrinsic(mad template="" args=[a, b, (-c)])`, and replaces the typed root with a `GpuIrIntrinsicCall` review node. The subtract form appends a typed unary-negation node for the third argument in the optimized review artifact. It runs one materialized rewrite per fixed-point pass, requires OpenCL review-source emission for OpenCL requests, and records `optimizerFamily=mad-fma-materialization`, `targetIntrinsic=mad`, `policy.fastMathAllowed`, `safety.fastMathRequired=true`, `safety.strictFloatPreserved=false`, `fixedPoint.pass.count`, `bodyTextReplacement.count`, and per-case `RewriteKind=mad-fma-to-opencl-mad` / `RewriteKind=multiply-subtract-to-opencl-mad`. Strict/default policy, missing typed body, unsupported text format, missing text pattern, or blocked OpenCL review-source emission returns `NO_CHANGE`. This can make `optimized.backend.opencl-c` show `mad(...)`, but `backend.opencl-c` remains the original selected source unless future proof/approval/selection gates explicitly allow otherwise. `GpuIrClampMaterializationProposalProvider` is the first review-only non-fast-math typed peephole materializer for generated clamp shapes. It scans reachable typed OpenCL intrinsic-call trees for the conservative shape `min(max(x, lo), hi)`, rewrites the matching `ir-text-v1` expression to `intrinsic(clamp template="" args=[x, lo, hi])`, and replaces the typed root with a `GpuIrIntrinsicCall` review node. It intentionally does not match reordered `min(hi, max(...))` or strict comparison / algebraic variants yet, because argument order, equality behavior, and floating-point edge cases must stay explicit. The proof records `optimizerFamily=clamp-materialization`, `targetIntrinsic=clamp`, `safety.fastMathRequired=false`, `safety.strictFloatPreserved=true`, `safety.argumentOrderPreserved=true`, `fixedPoint.pass.count`, `bodyTextReplacement.count`, and per-case `RewriteKind=min-max-to-opencl-clamp`. This can make `optimized.backend.opencl-c` show `clamp(...)`, but `backend.opencl-c` remains the original selected source unless future proof/approval/selection gates explicitly allow otherwise. @@ -50,7 +322,7 @@ The preview records blockers such as divide-by-zero, non-even division, unsuppor `GpuIrMixMaterializationProposalProvider` is a review-only typed peephole materializer for linear interpolation shapes. It materializes canonical `a + t * (b - a)` / `t * (b - a) + a` forms to `intrinsic(mix template="" args=[a, b, t])` without requiring fast-math permission. It also recognizes expanded weighted forms such as `t * b + (1.0F - t) * a` and generated `mad(b, t, (1.0F - t) * a)` shapes, including the optimizer-journal style `mad(blend, isSolid, ((1.0F - isSolid) * (clampedWave * 0.1F)))`; those expanded forms require `fastMathAllowed` because they change floating-point operation ordering. The proof records `optimizerFamily=mix-materialization`, `targetIntrinsic=mix`, `canonicalMix.count`, `expandedMix.count`, `madExpandedMix.count`, `safety.fastMathRequired`, `safety.strictFloatPreserved`, `safety.algebraicReassociationRequired`, and per-case `RewriteKind=linear-interpolation-to-opencl-mix`, `expanded-linear-interpolation-to-opencl-mix`, or `mad-expanded-linear-interpolation-to-opencl-mix`. This can make `optimized.backend.opencl-c` show `mix(...)` review candidates, but `backend.opencl-c` remains the original selected source unless future proof/approval/selection gates explicitly allow otherwise. -`GpuIrLoopVectorizationMaterializationProposalProvider` is the first review-only loop vectorization materializer. It recognizes a strict fixed-width contiguous float reduction shape such as `float sum = 0.0F; for (int i = 0; i < 4; i++) sum += input[id * 4 + i]`, including equivalent `input[mad(id, 4, i)]` / `input[intrinsic(mad ...)]` index forms, `input[(base + i)]` where `base` is a straight-line local `int` binding for the width-scaled base, reversed `input[(i + base)]`, and constant-offset bases such as `input[((id * 4) + 8 + i)]`. Local base aliases are resolved conservatively from preceding straight-line `int` declarations only, are cleared across control-flow boundaries, and are invalidated by later assignment to the same local. The pass rewrites the review `ir-text-v1` body to `vload4` plus an ordered scalar lane reduction through `Float4`. It also reconstructs a matching review typed body for the supported `ir-text-v1` subset, using `GpuIrIntrinsicCall(vload4)`, `GpuIrUnary(&)`, `GpuIrArrayAccess`, `GpuIrFieldAccess` lane reads, and ordered `GpuIrBinary(+)` reduction nodes; if surrounding body text falls outside that subset, it fails closed to typed-body invalidation for the review candidate. It records `optimizerFamily=loop-vectorization-materialization`, `targetIntrinsic=vload4`, `vectorWidth=4`, `safety.loopTripCountProven=true`, `safety.contiguousLoadProven=true`, `safety.orderedReductionPreserved=true`, `typedBody.materialized.count`, `typedBody.invalidated.count`, and per-case `RewriteKind=fixed-width-contiguous-vload4-ordered-reduction`. The pass intentionally avoids `dot` and reassociation, so it does not require fast-math permission. The original runtime IR remains selected; only `optimized.backend.opencl-c` / `optimized.irgpu.properties` show the `vload4` candidate. +`GpuIrLoopVectorizationMaterializationProposalProvider` is the first review-only loop vectorization materializer. It recognizes a strict fixed-width contiguous float reduction shape such as `float sum = 0.0F; for (int i = 0; i < 4; i++) sum += input[id * 4 + i]`, including equivalent `input[mad(id, 4, i)]` / `input[intrinsic(mad ...)]` index forms, `input[(base + i)]` where `base` is a straight-line local `int` binding for the width-scaled base, reversed `input[(i + base)]`, and constant-offset bases such as `input[((id * 4) + 8 + i)]`. Local base aliases are resolved conservatively from preceding straight-line `int` declarations only, are cleared across control-flow boundaries, and are invalidated by later assignment to the same local. The pass rewrites the review `ir-text-v1` body to `vload4` plus an ordered scalar lane reduction through `Float4`. It also reconstructs a matching review typed body for the supported `ir-text-v1` subset, using `GpuIrIntrinsicCall(vload4)`, `GpuIrUnary(&)`, `GpuIrArrayAccess`, `GpuIrFieldAccess` lane reads, and ordered `GpuIrBinary(+)` reduction nodes; the reconstructed typed body must pass the shared `GpuIrTypedBodyGraphPatch` reachability validation before it is counted as materialized. If surrounding body text falls outside that subset or the reconstructed typed graph is incomplete, it fails closed to typed-body invalidation for the review candidate, and runtime evidence keeps loop-vectorization blocked until typed-body rebuild validates. It records `optimizerFamily=loop-vectorization-materialization`, `targetIntrinsic=vload4`, `vectorWidth=4`, `safety.loopTripCountProven=true`, `safety.contiguousLoadProven=true`, `safety.orderedReductionPreserved=true`, `typedBody.materialized.count`, `typedBody.invalidated.count`, `typedBody.rebuild.*` counters/status/first-blocker, and per-case `RewriteKind=fixed-width-contiguous-vload4-ordered-reduction`. The pass intentionally avoids `dot` and reassociation, so it does not require fast-math permission. The original runtime IR remains selected; only `optimized.backend.opencl-c` / `optimized.irgpu.properties` show the `vload4` candidate. `GpuIrTypedDeadCodePreviewProposalProvider` is a third preview-only proof surface for future typed dead-code cleanup. It walks typed-body roots, records reachable versus unreachable node counts, unreachable-kind breakdown, missing root / missing child-reference blockers, and side-effecting unreachable-node blockers. It always returns `NO_CHANGE`, marks the proof as preview-only, and records that runtime equivalence, approval, and side-effect freedom must be proven before any future cleanup rewrite can graduate. @@ -58,6 +330,8 @@ The preview records blockers such as divide-by-zero, non-even division, unsuppor OpenCL validation aggregates these proof fields into `backendNeutralSourceMaterialization.*`, `constantFoldingPreview.*`, `constantFoldingMaterialization.*`, `safeLocalCsePreview.*`, `safeLocalCseMaterialization.*`, `madFmaMaterialization.*`, `clampMaterialization.*`, `stepMaterialization.*`, `mixMaterialization.*`, `loopVectorizationMaterialization.*`, `typedDeadCodePreview.*`, and `typedDeadCodeMaterialization.*` summary fields inside `runtime-ir-optimizer-evidence.properties`. Backend-neutral source materialization evidence records pass/candidate/source-ready counts, total reconstructed source length, status, and the first blocker so CI can see source-ready review candidates without treating them as selected IR. Materialized constant-folding evidence records pass/transformed-node/literal-rewrite/identity-rewrite/fixed-point-pass counts, runtime-equivalence payload presence/pass counts, status, and the first blocker so CI can see real transformed review candidates without treating them as selected IR. Materialized safe-local-CSE evidence records pass/local-binding/transformed-node/body-text-replacement counts, runtime-equivalence payload presence/pass counts, status, and the first blocker so CI can see repeated pure expressions rewritten to existing local references without treating them as selected IR. Materialized mad/FMA evidence records pass/candidate/transformed-node/body-text-replacement/fixed-point-pass counts, skipped fast-math-policy / body-text-pattern blockers, fast-math policy state, runtime-equivalence payload presence/pass counts, status, and the first blocker so CI can see fast-math peephole candidates without treating them as selected IR. Materialized clamp/step/mix evidence records pass/candidate/transformed-node/body-text-replacement counts, direct/inverted step counts, canonical/expanded/MAD-expanded mix counts, fast-math/reassociation requirements, runtime-equivalence payload presence/pass counts, status, and the first blocker so CI can see intrinsic materialization candidates without treating them as selected IR. Materialized loop-vectorization evidence records pass/candidate/transformed-loop/changed-method-body/body-text-replacement counts, typed-body materialization/invalidation counts, skipped loop-shape / unsupported-width / unsafe-load blockers, ordered-reduction proof, runtime-equivalence payload presence/pass counts, status, and the first blocker so CI can see fixed-width contiguous `vload4` review candidates without treating them as selected IR. Materialized typed dead-code evidence records pass/node/unreachable/removed-node counts, side-effect-freedom proof, runtime-equivalence payload presence/pass counts, status, and the first blocker so CI can see pure unreachable-node cleanup candidates without treating them as selected IR. The same payloads are also routed into `runtime-optimizer-family-equivalence-payload.properties` and durable per-pass component files. The artifact also records `previewReadiness.*` fields that summarize preview-family status across constant folding, safe-local CSE, and typed dead-code cleanup as `not-recorded`, `no-candidates`, `candidates-recorded`, `blocked-by-proof`, or `ready-for-runtime-equivalence-review`. `runtimeEquivalenceReview.*` then exposes a fail-closed review gate over preview readiness, materialized constant-folding candidates, materialized safe-local-CSE candidates, materialized mad/FMA candidates, materialized clamp/step/mix candidates, materialized loop-vectorization candidates, and materialized typed dead-code candidates: it can mark a candidate set as `review-ready`, but production mutation and selected-IR replacement remain disabled/manual-review-only. `reviewPackage.*` sits above that gate as the manual-review bundle boundary: backend-neutral source materialization and materialized candidates can produce pending approval templates, but the package remains pending on approval/manual review and repeats the disabled production mutation / selected-IR replacement guardrails. The generated `Runtime IR Optimizer Evidence` Markdown section shows backend-neutral source materialization status/source-ready counts, constant-folding preview pass/candidate/blocker counts, constant-folding materialization status/materialized-node/literal-rewrite/identity-rewrite/fixed-point-pass/blocker counts, safe-local-CSE preview pass/candidate-expression/duplicate-expression/blocker counts, safe-local-CSE materialization status/materialized-node/blocker counts, mad/FMA materialization status/materialized-node/text-replacement/fast-math/blocker counts, clamp/step/mix materialization status/materialized-node/text-replacement/payload/blocker counts, loop-vectorization materialization status/transformed-loop/text-replacement/typed-body-materialization/typed-body-invalidation/payload/blocker counts, typed dead-code preview pass/unreachable-node/blocker counts, typed dead-code materialization removed-node/payload/blocker counts, aggregate preview-readiness status, runtime-equivalence review eligibility, and review-package status. These fields are report-only and do not participate in source switching, selected-IR replacement, or production mutation gates. +Loop-vectorization evidence also includes `typedBody.rebuild.*` counters and status fields. These distinguish a validated reconstructed typed body from a review artifact where source text was vectorized but the typed body was intentionally invalidated because parsing, typed-body construction, or shared graph reachability validation failed. A rejected or invalidated typed-body rebuild blocks `loopVectorizationMaterialization.status=review-ready` even when the source rewrite and runtime-equivalence payload evidence are otherwise present. + ## Immutable Proposal Flow The optimizer contract is intentionally split into proposal and selection: @@ -71,7 +345,7 @@ If mutation is disabled, a valid optimized artifact is reported as `PROPOSAL_ONL `GpuIrOptimizedArtifactCandidateBuilder` now materializes a separate optimized-artifact candidate envelope after optimized-artifact validation. The envelope records original/optimized IR identities, the deterministic candidate key, validation verdict, proof presence, rollback requirement, mutation policy, and selection blockers. It deliberately keeps `selectionReady=false`, `selectionApplied=false`, and `selectedIrReplacement=false`; final runtime IR selection remains a separate gate even when a candidate artifact exists. -## Policy Controls +### Policy Controls `GpuIrOptimizationPolicy` is the backend-neutral control surface for optimizer providers and runtime adapters. It currently covers: @@ -87,19 +361,21 @@ If mutation is disabled, a valid optimized artifact is reported as `PROPOSAL_ONL `GpuIrOptimizationProposalRequest` preserves the older constructor shape for compatibility, but it now exposes a normalized `policy()` view and serializes `policy.*` fields into context metadata. Provider authors should read the policy instead of inventing backend-specific context keys. Defaults are fail-closed: proposal-only, mutation disabled, fast math disabled, vendor adaptation disabled, register-pressure splitting disabled, rollback required, and proof required. -## Runtime Bridge +### Runtime Bridge `GpuIrOptimizationProposalRegistry` owns backend-neutral provider discovery. It loads `GpuIrOptimizationProposalProvider` implementations through ServiceLoader, orders providers deterministically, and rejects duplicate extension ids before runtime adaptation. `GpuIrProposalRuntimeBridgePass` is the runtime adapter between the existing runtime optimizer pipeline and the immutable proposal-provider contract. It consumes providers from `GpuIrOptimizationProposalRegistry`, runs each provider through the validation sandwich, and reports the result as a runtime optimizer pass report. -The default bridge is intentionally proposal-only. It can surface canonicalization proposals, materialized review candidates, and proof metadata in runtime optimizer reports, but it does not select optimized IR unless constructed with explicit mutation permission. Proposal-only materialized candidates are composed through a separate review-working artifact, so later providers can see earlier review transforms such as constant folding before safe-local-CSE and `mad/fma` materialization run. The final runtime `artifact` remains the original selected IR, while `candidateArtifact`, `optimized.irgpu.properties`, and `optimized.backend.opencl-c` carry the accumulated review candidate. This lets `ir-optimizer` participate in runtime evidence collection without enabling production mutation. +The default bridge is intentionally proposal-only. It can surface canonicalization proposals, materialized review candidates, and proof metadata in runtime optimizer reports, but it does not select optimized IR unless explicit mutation permission is supplied by test wiring or the runtime compile option `runtime.irOptimizerSelection=experimental-apply`. Proposal-only materialized candidates are composed through a separate review-working artifact, so later providers can see earlier review transforms such as constant folding before safe-local-CSE and `mad/fma` materialization run. The final runtime `artifact` remains the original selected IR by default, while `candidateArtifact`, `optimized.irgpu.properties`, and `optimized.backend.opencl-c` carry the accumulated review candidate. This lets `ir-optimizer` participate in runtime evidence collection without enabling production mutation. Runtime artifact dumping records bridge/provider evidence in `runtime-ir-optimizer-evidence.properties`. The artifact is filtered by the stable `javatogpu.ir-optimizer` provider prefix and includes pass counts, outcomes, proposal-only/selected counts, original/transformed IR identities, proof artifact fields, and diagnostics. This is an evidence surface only; it does not authorize optimized IR selection or production mutation by itself. When a provider proposes an optimized artifact, bridge proof fields include `optimizedArtifactCandidate.*` metadata from the fail-closed candidate envelope. These fields make candidate identity, validation result, rollback requirement, mutation policy, and selection blockers visible without marking runtime selection as applied. -The runtime artifact also lifts those proof fields into top-level `optimizedArtifactCandidate.*` aggregate evidence: candidate status/counts, ready/blocked counts, first blockers, selection-ready/applied counts, selected-IR replacement counts, mutation-policy count, and explicit disabled selection/replacement guardrails. The generated OpenCL Markdown report shows candidate status and selection blockers per kernel, and `validateOpenClRuntimeIrOptimizerEvidence` fails if candidate selection or selected-IR replacement becomes enabled before the future selection gate exists. +The runtime artifact also lifts those proof fields into top-level `optimizedArtifactCandidate.*` aggregate evidence: candidate status/counts, ready/blocked counts, first blockers, selection-ready/applied counts, selected-IR replacement counts, mutation-policy count, and explicit disabled selection/replacement guardrails. The generated OpenCL Markdown report shows candidate status and selection blockers per kernel. `validateOpenClRuntimeIrOptimizerEvidence` fails if candidate selection or selected-IR replacement becomes enabled in normal CI mode; local experiments that deliberately compile the optimized artifact must pass `--allow-experimental-apply` and carry `experimentalApply.*` evidence. + +For local runtime experiments, use `GpuRuntimeCompileOptions.openClIrOptimizerExperimentalApply(...)`. This sets `runtime.irOptimizerSelection=experimental-apply`, allows the bridge to select a validated optimized artifact, and makes OpenCL compile the optimized backend source when selection survives runtime-equivalence and production gates. It is not a production switch: production-like profiles still go through the existing production IR acceptance gate, and the default `openCl(...)` / artifact-journal path remains review-only. Runtime peephole/InstCombine proof is also kept read-only. `GpuRuntimeIrPeepholeReplacementPlan` records future structural replacement intent, including root node, covered node ids, input node ids, replacement kind, completeness, and first blocker. `GpuRuntimeIrTypedNodeGraph` is the shared read-only traversal view for typed peephole rules, and `GpuRuntimeIrPeepholeReplacementPlanValidation` checks each emitted plan against that graph before it is surfaced. `GpuRuntimeIrPeepholeTypedRewriteVisitor` is the shared traversal preflight above validated plans: it records deterministic visit order, visitor-ready/blocked counts, first visitor blocker, `visitorImplemented=true`, and disabled replacement-builder / transformed-IR / mutation / selected-IR flags. `GpuRuntimeIrPeepholeReplacementBlueprint` sits above visitor-ready plans as a read-only target-node contract: it records intended intrinsic-call node kind, target operation, argument node ids/roles, blueprint-ready/blocked counts, first blueprint blocker, and disabled replacement-builder / transformed-IR / mutation / selected-IR flags. `GpuRuntimeIrPeepholeRewriteTransactionPreflight` then describes the future graph transaction shape: replaced root ids, removed covered ids, retained input ids, planned added-node count, `plannedAddedNodeIds=not-allocated`, and disabled node-id allocator / graph-rewrite / transformed-IR / mutation / selected-IR flags. `GpuRuntimeIrPeepholeNodeIdAllocationPreflight` sits above transaction-ready plans as a diagnostic-only allocation preview: it records current graph max node id, deterministic candidate node ids, allocation-ready/blocked counts, first blocker, and disabled id reservation / allocator application / graph-rewrite / transformed-IR / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeReplacementNodePreflight` then pairs that candidate id with the intrinsic-call blueprint as a diagnostic-only replacement-node construction preview, keeping `replacementNodeBuilt=false`, `replacementBuilderImplemented=false`, and graph-rewrite / transformed-IR / mutation / selected-IR disabled. `GpuRuntimeIrPeepholeGraphPatchPreflight` records the next read-only patch preview: replacement node id, replaced/removed/retained/inserted node sets, graph-patch ready/blocked counts, first blocker, and disabled graph-patch application / graph-rewrite / transformed-IR / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeTransformedGraphPreflight` adds the materialization boundary above that patch: original IR identity, deterministic materialization key, `transformedGraphIdentity=not-built`, materialization-ready/blocked counts, first blocker, and disabled transformed-graph build / transformed-IR / graph-patch application / graph-rewrite / mutation / selected-IR guardrails. `GpuRuntimeIrPeepholeRewriteSketch` remains the mutation-free rewrite skeleton: a complete, valid replacement plan can become `sketch-ready`, but the sketch still records `rewriteBuilderImplemented=false`, `mutationAllowed=false`, and `selectedIrReplacement=false`; runtime equivalence and approval remain required. `GpuRuntimeIrPeepholeRewriteSelectionReadiness` now sits above ready/blocked sketches as a read-only selection preflight: it reports sketch counts, conflict counts, status, first blocker, missing rewrite-builder/conflict-resolution/proof/approval gates, and disabled mutation/selection flags without choosing any optimized IR. `GpuRuntimeIrPeepholeRewriteProofReadiness` then records the next fail-closed proof boundary: proof required/accepted state, runtime-equivalence payload presence/completeness, rollback evidence, approval acceptance, original IR identity, absent transformed IR identity, and disabled mutation / selected-IR replacement. `GpuRuntimeIrPeepholeRewriteReviewPackage` sits above proof readiness as the manual-review package boundary: it records required/complete state, conflict count, proof acceptance, runtime-equivalence payload, rollback, approval, manual-review-only state, and disabled mutation / selection / selected-IR replacement. The pass emits aggregate `rewriteVisitor.*`, `replacementBlueprint.*`, `rewriteTransaction.*`, `nodeIdAllocation.*`, `replacementNode.*`, `graphPatch.*`, `transformedGraph.*`, `rewriteSelection.*`, `rewriteProof.*`, and `rewriteReviewPackage.*` plus matching per-rule fields, so CI can identify which matcher family is blocked by graph validation, traversal, blueprint construction, transaction shaping, id-allocation preview, replacement-node preview, graph-patch preview, transformed-graph materialization, conflict resolution, missing runtime-equivalence payloads, missing rollback evidence, missing approval, or incomplete review packaging. Validation fields fail closed on missing roots, uncovered roots, or missing covered/input nodes; visitor fields prove only original-node traversal; blueprint fields describe only a future intrinsic-call shape; transaction fields describe only planned replace/remove/retain/add effects; allocation fields preview candidate ids only and keep `nodeIdsReserved=false` and `nodeIdAllocatorApplied=false`; replacement-node fields preview the synthetic node shape only and keep `replacementNodeBuilt=false`; graph-patch fields preview the future diff only and keep `graphPatchApplied=false`; transformed-graph fields preview materialization only and keep `transformedGraphBuilt=false`. They still do not reserve node ids, build replacement nodes, apply graph patches, build transformed graphs, rewrite graphs, or authorize mutation. The current matcher set covers `mad/fma` for `a*b+c` / `c+a*b`, `clamp` for `min(max(x, lo), hi)`, `step` for simple `x < edge ? 0 : 1` / `x >= edge ? 1 : 0` ternaries, `dot` for additive multiply trees such as `(a0*b0)+(a1*b1)`, and `mix` for `a + t * (b - a)` / `t * (b - a) + a`. `runtime-optimizer-drift.properties` aggregates complete/partial replacement-plan counts, structural plan-validation total/valid/invalid counts, rewrite-visitor ready/blocked counts, replacement-blueprint ready/blocked counts, rewrite-transaction ready/blocked counts, node-id allocation ready/blocked counts, replacement-node preview ready/blocked counts, graph-patch preview ready/blocked counts, transformed-graph materialization ready/blocked counts, rewrite-sketch ready/blocked counts, overlap/conflict counters, aggregate and per-rule `rewriteSelection.*` status/first-blocker guardrails, aggregate and per-rule `rewriteProof.*` proof/runtime-equivalence/rollback blockers, aggregate and per-rule `rewriteReviewPackage.*` package-completeness blockers, and compact `optimizerRule.*` summaries for CI drift detection. Workload gates carry those visitor/blueprint/transaction/allocation/replacement-node/graph-patch/transformed-graph fields forward with the rest of the drift payload, while validation reports/history, I3 summaries, and production-promotion explainability treat the optimizer state as evidence-only telemetry. The pass still reports `rewrite-engine-not-implemented` and never mutates the selected IR. @@ -111,13 +387,13 @@ Runtime peephole/InstCombine proof is also kept read-only. `GpuRuntimeIrPeephole The same evidence artifact now records `approvalTemplate.*` fields for each optimizer pass. Real proposal rewrites with distinct original/optimized identities and accepted proof metadata are marked `pending`; preview and no-change proposals are marked `not-applicable` with a first blocker such as `proposal-decision-not-proposed`. Runtime evidence also lifts payload-bound approval readiness into `approvalTemplate.runtimeEquivalencePayloadRequired.count`, `approvalTemplate.runtimeEquivalencePayloadPresent.count`, `approvalTemplate.runtimeEquivalencePayloadPassed.count`, and `approvalTemplate.runtimeEquivalencePayloadComplete.count`, so review packages can show whether pending approvals are bound to complete runtime-equivalence payloads without completing the package or selecting optimized IR. When the bridge has full proposal context, proof fields also include `approvalTemplate.resourcePath`; the dumper exposes this under `pass.N.approvalTemplate.field.resourcePath` as the review package location for the manifest candidate. `GpuIrOptimizationApprovalManifestLoader` then probes that deterministic classpath resource, validates any loaded manifest against the proposal/request binding, and emits `approvalManifest.*` proof fields. `reviewPackage.approvalManifest.*` records the package-level manifest boundary: required state, present/accepted counts, resource-path summary, first blocker, and disabled production mutation / selected-IR replacement. Missing, stale, or payload-incomplete manifests remain fail-closed; accepted manifests are auditable review evidence only and do not select optimized IR. -## Vendor Optimizer SPI +### Vendor Optimizer SPI `GpuIrVendorOptimizationProposalProvider` is the optional vendor/device-specific contract. It receives a `GpuIrVendorOptimizationProposalRequest` with backend target, vendor, device id/label, driver/API text, device class, optimizer profile, mutation flag, and all original context fields. The provider still returns the same immutable `GpuIrOptimizationProposal`, so vendor proposals go through the same validation sandwich and rollback semantics as backend-neutral proposals. `GpuIrVendorOptimizationProposalRegistry` loads vendor providers deterministically, rejects duplicate extension ids, and exposes an adapter back to `GpuIrOptimizationProposalProvider`. The default runtime bridge does not auto-load vendor providers; projects or future vendor modules must opt into that registry explicitly. This keeps `ir-optimizer` backend-neutral while leaving room for a separate `ir-vendor-optimizer` artifact. -## Packaging External Providers +### Packaging External Providers Backend-neutral optimizer providers should depend on `javatogpu-ir-optimizer`, implement `GpuIrOptimizationProposalProvider`, and register the implementation in: @@ -135,7 +411,7 @@ Do not register a vendor provider in the backend-neutral provider descriptor unl Provider authors must expose stable `extensionId`, `extensionVersion`, and `extensionOrder` values. Duplicate ids are rejected by the registry. A provider that cannot prove a safe rewrite should return `NO_CHANGE` or `REJECTED`; it should not return a proposed artifact without proof fields, diagnostics, and rollback/equivalence evidence. -## Approval Manifest +### Approval Manifest `GpuIrOptimizationApprovalManifest` writes a review-only approval template for one immutable optimizer proposal. The template is bound to the optimizer id/version, original and optimized `IrGpu` identities, backend target, optimization profile, optional device vendor/label context, proof source/verdict, runtime-equivalence payload binding when required, and rollback requirement. It is intended for packaging under: @@ -147,6 +423,8 @@ Validation requires `status=approved`, explicit approval id/author/timestamp, ma `GpuIrOptimizationApprovalTemplateFormatter` is the bridge between proposal evidence and approval artifacts. It returns a pending template only for a real `PROPOSED` rewrite with distinct original/optimized identities and accepted proof metadata, emits a deterministic `manifest.resourcePath` under `META-INF/javatogpu/ir-optimization-approvals/`, and exposes compact `runtimeEquivalencePayload.*` template fields so review packages can show whether approval is bound to complete payload evidence. Preview or `NO_CHANGE` proposals return `not-applicable` with a first blocker such as `proposal-decision-not-proposed`, so report tooling can expose why no approval template should be packaged yet. +
+ ## Read Next - [IR Validation](IR-Validation.md) diff --git a/docs/Runtime-Guide.md b/docs/Runtime-Guide.md index b49ac393..87cbc452 100644 --- a/docs/Runtime-Guide.md +++ b/docs/Runtime-Guide.md @@ -229,17 +229,23 @@ High and critical estimates add optimizer diagnostics with the hottest method, e Artifact dumps store the complete result in `runtime-ir-analysis.properties`. Per-method fields include total parameter/local/private-array storage, `peakLiveRegisters`, expression-temporary peak, scoped-variable count, shadowed-variable count, unresolved-reference count, budget, utilization, level, and typed-node counts. Per-call fields include resolution state, inline/recursive flags, caller-live values, argument/result pressure, callee pressure, additional frame pressure, and combined estimate. -Set `-Djavatogpu.opencl.runtimeCompileArtifactDirectory=` to dump the full runtime compile artifact bundle for each OpenCL kernel invocation without enabling the operational validation report path. Each kernel gets a sanitized subdirectory under that root. When IR artifacts are available, the bundle includes `original.irgpu.properties` and `optimized.irgpu.properties` so the pre/post optimizer IR can be compared directly, plus `original.backend.opencl-c` and `optimized.backend.opencl-c` for before/after generated OpenCL backend source. `backend.opencl-c` remains the selected OpenCL source that the backend actually compiles. The bundle also includes `runtime-ir-handoff.properties`, `optimizer-report.txt` when reports exist, backend source artifacts, provenance, and diagnostics. This dump is diagnostic-only and does not select optimized IR, enable production mutation, or change backend source switching. +Set `-Djavatogpu.opencl.runtimeCompileArtifactDirectory=` to dump the full runtime compile artifact bundle for each OpenCL kernel invocation without enabling the operational validation report path. Each kernel gets a sanitized subdirectory under that root. When IR artifacts are available, the bundle includes `original.irgpu.properties` and `optimized.irgpu.properties` so the pre/post optimizer IR can be compared directly, plus `original.backend.opencl-c` and `optimized.backend.opencl-c` for before/after generated OpenCL backend source. `backend.opencl-c` remains the selected OpenCL source that the backend actually compiles. In the default `GpuRuntimeCompileOptions.openCl(...)` path this remains the original/pass-through source; in explicit `GpuRuntimeCompileOptions.openClIrOptimizerExperimentalApply(...)` runs it can become the optimized source if runtime-equivalence and production gates do not reject the selected optimized IR. The bundle also includes `runtime-ir-handoff.properties`, `optimizer-report.txt` when reports exist, backend source artifacts, provenance, and diagnostics. The dump itself is diagnostic-only and does not enable production mutation or source switching. OpenCL isolated runtime-equivalence checks write raw pipeline comparison cases into `runtime-equivalence.properties` when invocation arguments use supported array shapes. Each case records the comparison mode, original invocation inputs, descriptor-source reference outputs, reconstructed-source candidate outputs, exact tolerance metadata, per-output equivalence flags, and diagnostics. Primitive arrays are written as readable vectors, vector and struct arrays as deterministic packed Base64, scalar values as literals, and opaque image/sampler/runtime objects as stable type tags without process-specific handles. This evidence validates source reconstruction and remains separate from per-family optimizer proof. Non-analysis optimizer passes with runtime-equivalence payload evidence also produce `runtime-optimizer-family-equivalence-payload.properties` plus durable files under `runtime-optimizer-family-equivalence-payload/family--/pass-/`. Each pass directory contains `manifest.properties`, CPU/reference, pre-optimization, post-optimization, tolerance, failure-fixture, and diagnostics properties files. When proof fields contain structured case evidence, the component files include deterministic indexed fields for case names, inputs, raw outputs, tolerances, equivalence flags, and diagnostics. Missing components are materialized with `status=not-recorded`; they are never inferred from another pass. Artifact paths are normalized and must remain inside the kernel's `runtime-compile-artifacts` directory. These files are diagnostic evidence only and cannot enable source switching or production mutation. -When the optional IR optimizer bridge participates, each kernel directory may also receive `runtime-ir-optimizer-evidence.properties`. The artifact records only proposal/provider evidence from stable `javatogpu.ir-optimizer` sources: pass counts, proposal-only versus selected optimized counts, rollback counts, IR identities, proof fields, approval-template applicability, and diagnostics. Proposal-only materialized candidates are composed through a separate review-working artifact, so `optimized.irgpu.properties` and `optimized.backend.opencl-c` can accumulate safe review transforms from multiple providers while `backend.opencl-c` still remains the selected source compiled by OpenCL. The validation sandwich now also has an internal optimized-artifact candidate envelope for proposed artifacts; it records candidate identity and selection blockers as report-only evidence while final runtime IR selection remains separate and fail-closed. Candidate envelope proof fields are also lifted into top-level `optimizedArtifactCandidate.*` summary fields for status/counts, ready/blocked counts, first blockers, selection-ready/applied counts, selected-IR replacement counts, mutation-policy count, and disabled selection/replacement guardrails. Backend-neutral source materialization proof is aggregated into `backendNeutralSourceMaterialization.*` fields for pass count, candidate count, source-ready count, total source length, status, and first blocker; it is review-only source metadata evidence and does not require runtime-equivalence review by itself. Constant-folding preview proof is also aggregated into `constantFoldingPreview.*` fields for pass count, candidate count, skipped blockers, required runtime-equivalence/approval gates, and unresolved integer-overflow / floating-point-rounding safety flags. A separate constant-folding materialization provider can now emit a review-only transformed artifact for plain 32-bit integer literal unary `-` plus binary `+`, `-`, `*`, and exact `/` expressions where the divisor is non-zero and the division has no remainder, including nested safe folds materialized to a fixed point. It also supports a narrow pure-symbolic identity slice for `x + 0`, `0 + x`, `x - 0`, `x * 1`, `1 * x`, and `x / 1`, where the retained side can be a deterministic `GpuIrPureExpression` composed of named variables, plain int32 literal leaves, arithmetic binary nodes, and unary minus nodes; mixed identity chains such as `((x / 1) + 0)`, repeated identities across multiple roots, and multiple method bodies are recomputed one reachable rewrite per fixed-point pass so text and typed roots stay aligned. `x * 0`, `1 / x`, and broader division algebra remain excluded until stronger side-effect / unused-operand and equivalence proof exists. Top-level `constantFoldingMaterialization.*` fields record pass/transformed-node/literal-rewrite/identity-rewrite/fixed-point-pass counts, all-method-body rewrite scope, reachable-node-scan and rewrite-granularity metadata, body-text replacement scope, exact-int or symbolic-identity runtime-equivalence payload presence/pass counts, per-case method/node/rewrite-kind identity, status, skipped divide-by-zero/non-even-division counts, and first blocker. The same compact proof payload is routed into `runtime-optimizer-family-equivalence-payload.properties` plus durable component files, so this narrow materialized family can become runtime-equivalence `review-ready` while approval/manual review still blocks completion. Pending approval templates also export `pass.N.approvalTemplate.field.runtimeEquivalencePayload.*`, `pass.N.approvalTemplate.field.resourceDirectory`, and `pass.N.approvalTemplate.field.resourcePath` fields so the manual-review package is visibly bound to the same payload resource, comparison mode, completeness, case count, and manifest location; top-level `approvalTemplate.runtimeEquivalencePayloadRequired.count`, `approvalTemplate.runtimeEquivalencePayloadPresent.count`, `approvalTemplate.runtimeEquivalencePayloadPassed.count`, and `approvalTemplate.runtimeEquivalencePayloadComplete.count` summarize those bindings across pending templates. The bridge also probes the deterministic approval manifest classpath resource and emits `approvalManifest.*` fields for missing, blocked, or accepted manifest validation. `reviewPackage.approvalManifest.*` records the next package boundary over those templates, including manifest requirement, present/accepted counts, resource-path summary, first blocker, manual-review-only state, and disabled production mutation / selected-IR replacement. Safe-local-CSE preview proof is aggregated into `safeLocalCsePreview.*` fields for pass count, expression/candidate/duplicate/equivalence-class counts, unsupported-operator / impure-operand / control-flow-boundary blockers, required runtime-equivalence/approval gates, and unresolved dominance / side-effect-freedom proof flags. Safe-local-CSE materialization proof is aggregated into `safeLocalCseMaterialization.*` fields for pass count, existing-local binding count, transformed-node count, body-text replacement count, fixed-point pass count, runtime-equivalence payload presence/pass counts, status, and first blocker. This materialized family only rewrites repeated pure binary expressions to already existing local references in straight-line `ir-text-v1` bodies; it introduces no new temporary and changes only the optimized review artifact. Intrinsic materialization proof is aggregated into `madFmaMaterialization.*`, `clampMaterialization.*`, `stepMaterialization.*`, and `mixMaterialization.*` fields for pass/candidate/transformed-node/body-text-replacement counts, fast-math or strict-float safety policy, direct/inverted step accounting, canonical/expanded/MAD-expanded mix accounting, runtime-equivalence payload presence/pass counts, status, and first blocker. Loop-vectorization proof is aggregated into `loopVectorizationMaterialization.*` fields for transformed-loop/text-replacement/typed-body materialization counts and ordered-reduction safety evidence. Typed dead-code preview proof is aggregated into `typedDeadCodePreview.*` fields for pass count, typed/reachable/unreachable node counts, missing-root / missing-child-reference / side-effecting-unreachable blockers, required runtime-equivalence/approval gates, and unresolved side-effect-freedom proof flags. The artifact also records `previewReadiness.*` fields with aggregate status, recorded/candidate/blocked family counts, and a per-family status summary so CI can distinguish no-candidate previews from proof-blocked previews and review-ready runtime-equivalence candidates. `runtimeEquivalenceReview.*` records the next fail-closed review boundary: status, eligibility, requirement, first blocker, family summary, disabled production mutation, disabled selected-IR replacement, and manual-review-only state. `reviewPackage.*` records the manual-review package boundary above that review gate, including package status, requirement/completeness, first blocker, proposal-pass count, pending approval count, original/optimized IR and proof-summary requirements, manual-review-only state, and disabled production mutation / selected-IR replacement. `openClValidationReport` aggregates this into `Runtime IR Optimizer Evidence` for CI visibility, including optimized-artifact candidate status/blockers, backend-neutral source materialization readiness, approval-template payload readiness, approval-manifest package readiness, constant-folding preview/materialization, safe-local-CSE preview/materialization, mad/FMA, clamp, step, mix, loop-vectorization, typed dead-code preview/materialization totals, preview-readiness status, runtime-equivalence review eligibility, and review-package status. `validateOpenClRuntimeIrOptimizerEvidence` then validates the raw runtime evidence files and fails CI if those fail-closed guardrails are missing, completed early, or production-enabling. The section and validator are evidence-only and do not participate in history, source switching, selected-IR replacement, or production mutation gates. +When the optional IR optimizer bridge participates, each kernel directory may also receive `runtime-ir-optimizer-evidence.properties`. The artifact records only proposal/provider evidence from stable `javatogpu.ir-optimizer` sources: pass counts, proposal-only versus selected optimized counts, rollback counts, IR identities, proof fields, approval-template applicability, and diagnostics. Proposal-only materialized candidates are composed through a separate review-working artifact, so `optimized.irgpu.properties` and `optimized.backend.opencl-c` can accumulate safe review transforms from multiple providers while `backend.opencl-c` still remains the selected source compiled by OpenCL. The validation sandwich now also has an internal optimized-artifact candidate envelope for proposed artifacts; it records candidate identity and selection blockers as report-only evidence while final runtime IR selection remains separate and fail-closed. Candidate envelope proof fields are also lifted into top-level `optimizedArtifactCandidate.*` summary fields for status/counts, ready/blocked counts, first blockers, selection-ready/applied counts, selected-IR replacement counts, mutation-policy count, and disabled selection/replacement guardrails. Backend-neutral source materialization proof is aggregated into `backendNeutralSourceMaterialization.*` fields for pass count, candidate count, source-ready count, total source length, status, and first blocker; it is review-only source metadata evidence and does not require runtime-equivalence review by itself. Constant-folding preview proof is also aggregated into `constantFoldingPreview.*` fields for pass count, candidate count, skipped blockers, required runtime-equivalence/approval gates, and unresolved integer-overflow / floating-point-rounding safety flags. A separate constant-folding materialization provider can now emit a review-only transformed artifact for plain 32-bit integer literal unary `-` plus binary `+`, `-`, `*`, and exact `/` expressions where the divisor is non-zero and the division has no remainder, including nested safe folds materialized to a fixed point. It also supports a narrow pure-symbolic identity slice for `x + 0`, `0 + x`, `x - 0`, `x * 1`, `1 * x`, and `x / 1`, where the retained side can be a deterministic `GpuIrPureExpression` composed of named variables, plain int32 literal leaves, arithmetic binary nodes, and unary minus nodes; mixed identity chains such as `((x / 1) + 0)`, repeated identities across multiple roots, and multiple method bodies are recomputed one reachable rewrite per fixed-point pass so text and typed roots stay aligned. `x * 0`, `1 / x`, and broader division algebra remain excluded until stronger side-effect / unused-operand and equivalence proof exists. Top-level `constantFoldingMaterialization.*` fields record pass/transformed-node/literal-rewrite/identity-rewrite/fixed-point-pass counts, all-method-body rewrite scope, reachable-node-scan and rewrite-granularity metadata, body-text replacement scope, exact-int or symbolic-identity runtime-equivalence payload presence/pass counts, per-case method/node/rewrite-kind identity, status, skipped divide-by-zero/non-even-division counts, and first blocker. The same compact proof payload is routed into `runtime-optimizer-family-equivalence-payload.properties` plus durable component files, so this narrow materialized family can become runtime-equivalence `review-ready` while approval/manual review still blocks completion. Pending approval templates also export `pass.N.approvalTemplate.field.runtimeEquivalencePayload.*`, `pass.N.approvalTemplate.field.resourceDirectory`, and `pass.N.approvalTemplate.field.resourcePath` fields so the manual-review package is visibly bound to the same payload resource, comparison mode, completeness, case count, and manifest location; top-level `approvalTemplate.runtimeEquivalencePayloadRequired.count`, `approvalTemplate.runtimeEquivalencePayloadPresent.count`, `approvalTemplate.runtimeEquivalencePayloadPassed.count`, and `approvalTemplate.runtimeEquivalencePayloadComplete.count` summarize those bindings across pending templates. The bridge also probes the deterministic approval manifest classpath resource and emits `approvalManifest.*` fields for missing, blocked, or accepted manifest validation. `reviewPackage.approvalManifest.*` records the next package boundary over those templates, including manifest requirement, present/accepted counts, resource-path summary, first blocker, manual-review-only state, and disabled production mutation / selected-IR replacement. Safe-local-CSE preview proof is aggregated into `safeLocalCsePreview.*` fields for pass count, expression/candidate/duplicate/equivalence-class counts, unsupported-operator / impure-operand / control-flow-boundary blockers, required runtime-equivalence/approval gates, and unresolved dominance / side-effect-freedom proof flags. Safe-local-CSE materialization proof is aggregated into `safeLocalCseMaterialization.*` fields for pass count, existing-local binding count, introduced-temporary count, transformed-node count, body-text replacement count, fixed-point pass count, runtime-equivalence payload presence/pass counts, status, and first blocker. This materialized family rewrites repeated pure binary expressions to existing local references or conservative introduced local temporaries in straight-line `ir-text-v1` bodies, and changes only the optimized review artifact. Intrinsic materialization proof is aggregated into `madFmaMaterialization.*`, `clampMaterialization.*`, `stepMaterialization.*`, and `mixMaterialization.*` fields for pass/candidate/transformed-node/body-text-replacement counts, fast-math or strict-float safety policy, direct/inverted step accounting, canonical/expanded/MAD-expanded mix accounting, runtime-equivalence payload presence/pass counts, status, and first blocker. Loop-vectorization proof is aggregated into `loopVectorizationMaterialization.*` fields for transformed-loop/text-replacement/typed-body materialization counts and ordered-reduction safety evidence. Typed dead-code preview proof is aggregated into `typedDeadCodePreview.*` fields for pass count, typed/reachable/unreachable node counts, missing-root / missing-child-reference / side-effecting-unreachable blockers, required runtime-equivalence/approval gates, and unresolved side-effect-freedom proof flags. The artifact also records `previewReadiness.*` fields with aggregate status, recorded/candidate/blocked family counts, and a per-family status summary so CI can distinguish no-candidate previews from proof-blocked previews and review-ready runtime-equivalence candidates. `runtimeEquivalenceReview.*` records the next fail-closed review boundary: status, eligibility, requirement, first blocker, family summary, disabled production mutation, disabled selected-IR replacement, and manual-review-only state. `reviewPackage.*` records the manual-review package boundary above that review gate, including package status, requirement/completeness, first blocker, proposal-pass count, pending approval count, original/optimized IR and proof-summary requirements, manual-review-only state, and disabled production mutation / selected-IR replacement. `openClValidationReport` aggregates this into `Runtime IR Optimizer Evidence` for CI visibility, including optimized-artifact candidate status/blockers, backend-neutral source materialization readiness, approval-template payload readiness, approval-manifest package readiness, constant-folding preview/materialization, safe-local-CSE preview/materialization, mad/FMA, clamp, step, mix, loop-vectorization, typed dead-code preview/materialization totals, preview-readiness status, runtime-equivalence review eligibility, and review-package status. `validateOpenClRuntimeIrOptimizerEvidence` then validates the raw runtime evidence files and fails CI if those fail-closed guardrails are missing, completed early, or production-enabling. The section and validator are evidence-only and do not participate in history, source switching, selected-IR replacement, or production mutation gates. + +For local optimized-artifact execution tests, use `GpuRuntimeCompileOptions.openClIrOptimizerExperimentalApply(...)`. That preset records `experimentalApply.*` fields in `runtime-ir-optimizer-evidence.properties` and allows `backend.opencl-c` to come from the selected optimized IR if runtime-equivalence and production gates allow it. The default validator still rejects applied optimized-artifact selection; pass `--allow-experimental-apply` only for intentionally local apply-mode artifacts. + +Loop-vectorization evidence now includes the `loopVectorizationMaterialization.typedBody.rebuild.*` fields. These record attempted, parsed, built, graph-validated, and rejected typed-body rebuild counts plus rebuild status and first blocker, so dumps explain whether `optimized.irgpu.properties` contains a validated reconstructed typed body or a deliberately invalidated review typed body after the source rewrite. Rejected or invalidated rebuild evidence keeps loop-vectorization blocked even when the source rewrite and runtime-equivalence payload evidence are present. Typed dead-code materialization is now represented as a separate review-only evidence family. `typedDeadCodeMaterialization.*` records pass count, typed/unreachable/removed node counts, missing-root / missing-child-reference / side-effecting-unreachable blockers, runtime-equivalence payload presence/pass counts, side-effect-freedom proof, status, and first blocker. It removes only unreachable pure typed nodes from the optimized review artifact, leaves method text bodies unchanged, and is included in `runtimeEquivalenceReview.*` / OpenCL Markdown summaries without enabling source switching, selected-IR replacement, or production mutation. -Safe-local-CSE materialization is also review-only. `safeLocalCseMaterialization.*` records pass count, existing-local binding count, transformed-node count, body-text replacement count, fixed-point pass count, runtime-equivalence payload presence/pass counts, status, and first blocker. In dumps, this is the easiest first alpha source-changing example to inspect: `optimized.backend.opencl-c` can replace repeated expressions with existing local variables, while `backend.opencl-c` remains the original selected source compiled by OpenCL. +Safe-local-CSE materialization is also review-only. `safeLocalCseMaterialization.*` records pass count, existing-local binding count, introduced-temporary count, transformed-node count, body-text replacement count, fixed-point pass count, runtime-equivalence payload presence/pass counts, status, and first blocker. In dumps, this is the easiest first alpha source-changing example to inspect: `optimized.backend.opencl-c` can replace repeated expressions with existing local variables or conservative generated locals such as `jtg_cse0`, while `backend.opencl-c` remains the original selected source compiled by OpenCL. + +For introduced-local CSE, check `safeLocalCseMaterialization.introduedTemporary.count`, `safety.newTemporaryIntroduced`, and the per-case rewrite kind `safe-local-cse-introduce-local`. The optimizer still requires straight-line `ir-text-v1`, exact text/typed occurrence agreement, no input reassignment between uses, runtime-equivalence payload evidence, and manual review before any future production selection gate can be considered. Intrinsic materialization is review-only too. `madFmaMaterialization.*`, `clampMaterialization.*`, `stepMaterialization.*`, and `mixMaterialization.*` record whether `optimized.backend.opencl-c` can show `mad(...)`, `clamp(...)`, `step(...)`, and `mix(...)` candidates, including text replacement counts and payload pass counts. `backend.opencl-c` remains the selected source unless future proof, approval, production gate, and mutation policy explicitly allow optimized artifact selection. diff --git a/docs/Validation-and-Operations.md b/docs/Validation-and-Operations.md index dc99136e..9cee2624 100644 --- a/docs/Validation-and-Operations.md +++ b/docs/Validation-and-Operations.md @@ -109,7 +109,7 @@ Use `activate` only after manifest validation succeeds. The workflow then writes The vendor workflow also runs `openClOptimizerFamilyPayloadFixtureTest` after the main validation bucket. It must produce `fixture-summary.properties` with two complete families and fourteen durable files. This fixture proves the nested artifact contract is uploadable and path-safe; it does not alter real-workload optimizer-family counts or production readiness. -`validateOpenClRuntimeIrOptimizerEvidence` checks `runtime-ir-optimizer-evidence.properties` under `runtime-compile-artifacts`. It fails if runtime-equivalence review, optimized-artifact candidate, or review-package guardrails stop being fail-closed, if production mutation or selected-IR replacement becomes enabled, if candidate selection is applied, if a required review package has no blocker, or if a package is marked complete before manual-review activation exists. When materialization-family evidence is present, it also verifies family-specific review-ready invariants: safe-local-CSE needs existing-local reuse, dominance/side-effect proof, matching text replacement, required runtime-equivalence/approval gates, and passed payloads; fast-math `mad/fma` needs matching text replacement, fast-math policy evidence, required runtime-equivalence/approval gates, and passed payloads; `clamp` needs matching text replacement, strict-float/no-fast-math safety evidence, required runtime-equivalence/approval gates, and passed payloads; `step` needs matching text replacement, direct/inverted step accounting, strict comparison/equality/NaN safety evidence, required runtime-equivalence/approval gates, and passed payloads; `mix` needs matching text replacement, canonical/expanded/MAD-expanded accounting, fast-math/reassociation evidence when required, required runtime-equivalence/approval gates, and passed payloads; loop vectorization needs matching text replacement, fixed trip-count proof, contiguous-load proof, ordered-reduction preservation, typed-body materialization/invalidation accounting, required runtime-equivalence/approval gates, and passed payloads; typed dead-code removal needs consistent node counts, side-effect proof, required runtime-equivalence/approval gates, and passed payloads. The operational and NVIDIA routines run this validator after `openClValidationReport`. +`validateOpenClRuntimeIrOptimizerEvidence` checks `runtime-ir-optimizer-evidence.properties` under `runtime-compile-artifacts`. It fails if runtime-equivalence review, optimized-artifact candidate, or review-package guardrails stop being fail-closed, if production mutation or selected-IR replacement becomes enabled, if candidate selection is applied, if a required review package has no blocker, or if a package is marked complete before manual-review activation exists. When materialization-family evidence is present, it also verifies family-specific review-ready invariants: safe-local-CSE needs existing-local reuse or introduced-temp evidence, dominance/side-effect proof, matching text replacement, required runtime-equivalence/approval gates, and passed payloads; fast-math `mad/fma` needs matching text replacement, fast-math policy evidence, required runtime-equivalence/approval gates, and passed payloads; `clamp` needs matching text replacement, strict-float/no-fast-math safety evidence, required runtime-equivalence/approval gates, and passed payloads; `step` needs matching text replacement, direct/inverted step accounting, strict comparison/equality/NaN safety evidence, required runtime-equivalence/approval gates, and passed payloads; `mix` needs matching text replacement, canonical/expanded/MAD-expanded accounting, fast-math/reassociation evidence when required, required runtime-equivalence/approval gates, and passed payloads; loop vectorization needs matching text replacement, fixed trip-count proof, contiguous-load proof, ordered-reduction preservation, typed-body materialization/invalidation accounting, required runtime-equivalence/approval gates, and passed payloads; typed dead-code removal needs consistent node counts, side-effect proof, required runtime-equivalence/approval gates, and passed payloads. The operational and NVIDIA routines run this validator after `openClValidationReport`. ## Optional IR Validation diff --git a/examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md b/examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md index 72ede064..60bc95d9 100644 --- a/examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md +++ b/examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md @@ -22,6 +22,14 @@ tasks.register('runOptimizationJournalExample', JavaExec) { ... } GpuRuntimeCompileOptions.openCl(List.of(), "diagnostic") ``` +To compile the optimized IR candidate during a local experiment, switch that call to the explicit apply preset: + +```java +GpuRuntimeCompileOptions.openClIrOptimizerExperimentalApply(List.of(), "diagnostic") +``` + +This is intentionally separate from the default journal mode. `openCl(...)` dumps before/after artifacts while keeping `backend.opencl-c` on the original selected source. `openClIrOptimizerExperimentalApply(...)` lets the validated optimized artifact become the selected runtime IR for that invocation, so `backend.opencl-c` can match `optimized.backend.opencl-c` when runtime-equivalence and production gates do not reject it. + It also enables the runtime artifact journal by setting: ```text @@ -57,16 +65,16 @@ optimizer-report.txt runtime-ir-optimizer-evidence.properties ``` -`original.backend.opencl-c` and `optimized.backend.opencl-c` are the before/after backend source files for comparing generated OpenCL around the optimizer boundary. `optimized.backend.opencl-c` may come from an accumulated review-only materialized candidate, including backend-neutral source materialization metadata, the constrained integer constant-folding transform, safe-local-CSE materialization, the fast-math `mad/fma` peephole materializer, `clamp` materialization, `step` materialization, `mix` materialization, or fixed-width loop vectorization. Constant folding supports plain 32-bit integer literal unary `-`, binary `+`, `-`, `*`, exact `/`, nested safe literal patterns, and the current pure-symbolic identity slice (`x + 0`, `0 + x`, `x - 0`, `x * 1`, `1 * x`, `x / 1`, mixed chains such as `((x / 1) + 0)`, repeated identities across multiple roots, and multiple method bodies) over deterministic retained expressions made from named variables, plain int32 literal leaves, arithmetic binary nodes, and unary minus nodes. Safe-local-CSE materialization reuses already existing local bindings in straight-line `ir-text-v1`, for example replacing later repeated `(value * 3.1415f)` expressions with the earlier local `scale` until no more safe candidates remain. Mad/FMA materialization requires `@GPUOptimize(fastMath = true)` and rewrites reachable `a * b + c` / `c + a * b` text plus typed nodes to `intrinsic(mad template="" args=[a, b, c])` in the optimized review artifact. `x * 0`, `1 / x`, broader division algebra, new CSE temporaries, control-flow-spanning CSE, reordered clamp/minmax variants, non-mask ternaries, and mix forms outside the current canonical / expanded / MAD-expanded shapes remain excluded for now. `backend.opencl-c` is the selected backend source code that the OpenCL backend actually compiles. `original.irgpu.properties` and `optimized.irgpu.properties` are the matching before/after IR files. `runtime-ir-optimizer-evidence.properties` is the machine-readable guardrail summary that keeps the current alpha fail-closed; for materialized review candidates, check `backendNeutralSourceMaterialization.*`, `constantFoldingMaterialization.*`, `safeLocalCseMaterialization.*`, `madFmaMaterialization.*`, `clampMaterialization.*`, `stepMaterialization.*`, `mixMaterialization.*`, `loopVectorizationMaterialization.*`, `typedDeadCodeMaterialization.*`, `runtimeEquivalenceReview.*`, `approvalTemplate.runtimeEquivalencePayload*.count`, `pass.N.approvalTemplate.field.resourcePath`, `pass.N.approvalTemplate.field.approvalManifest.*`, `reviewPackage.approvalManifest.*`, and the durable `runtime-optimizer-family-equivalence-payload/` files to see source-ready counts, transformed-node counts, literal/identity rewrite counts, fixed-point-pass counts, existing-local CSE reuse counts, mad/FMA fast-math policy, clamp/step/mix intrinsic counts, loop-vectorization counts, body-text replacement counts, reachable-node scan / rewrite-granularity fields, per-case method/node identity, exact-int / symbolic-identity / existing-local-reuse / fast-math-mad / intrinsic-materialization payload evidence, approval payload readiness, loaded/accepted manifest evidence, the review manifest location, and the remaining manual-review/approval blocker. +`original.backend.opencl-c` and `optimized.backend.opencl-c` are the before/after backend source files for comparing generated OpenCL around the optimizer boundary. `optimized.backend.opencl-c` may come from an accumulated review-only materialized candidate, including backend-neutral source materialization metadata, the constrained integer constant-folding transform, safe-local-CSE materialization, the fast-math `mad/fma` peephole materializer, `clamp` materialization, `step` materialization, `mix` materialization, or fixed-width loop vectorization. Constant folding supports plain 32-bit integer literal unary `-`, binary `+`, `-`, `*`, exact `/`, nested safe literal patterns, and the current pure-symbolic identity slice (`x + 0`, `0 + x`, `x - 0`, `x * 1`, `1 * x`, `x / 1`, mixed chains such as `((x / 1) + 0)`, repeated identities across multiple roots, and multiple method bodies) over deterministic retained expressions made from named variables, plain int32 literal leaves, arithmetic binary nodes, and unary minus nodes. Safe-local-CSE materialization reuses existing local bindings in straight-line `ir-text-v1`, and can now introduce conservative local temporaries for repeated pure binary expressions such as `(value * 1.414F)` when all text/typed occurrences line up and no input variable is reassigned between uses. Mad/FMA materialization requires `@GPUOptimize(fastMath = true)` and rewrites reachable `a * b + c` / `c + a * b` text plus typed nodes to `intrinsic(mad template="" args=[a, b, c])`; it also rewrites reachable `a * b - c` to `intrinsic(mad template="" args=[a, b, (-c)])` in the optimized review artifact. `x * 0`, `1 / x`, broader division algebra, control-flow-spanning CSE, reordered clamp/minmax variants, non-mask ternaries, and mix forms outside the current canonical / expanded / MAD-expanded shapes remain excluded for now. `backend.opencl-c` is the selected backend source code that the OpenCL backend actually compiles. `original.irgpu.properties` and `optimized.irgpu.properties` are the matching before/after IR files. `runtime-ir-optimizer-evidence.properties` is the machine-readable guardrail summary that keeps the current alpha fail-closed; for materialized review candidates, check `backendNeutralSourceMaterialization.*`, `constantFoldingMaterialization.*`, `safeLocalCseMaterialization.*`, `madFmaMaterialization.*`, `clampMaterialization.*`, `stepMaterialization.*`, `mixMaterialization.*`, `loopVectorizationMaterialization.*`, `typedDeadCodeMaterialization.*`, `runtimeEquivalenceReview.*`, `approvalTemplate.runtimeEquivalencePayload*.count`, `pass.N.approvalTemplate.field.resourcePath`, `pass.N.approvalTemplate.field.approvalManifest.*`, `reviewPackage.approvalManifest.*`, and the durable `runtime-optimizer-family-equivalence-payload/` files to see source-ready counts, transformed-node counts, literal/identity rewrite counts, fixed-point-pass counts, existing-local and introduced-temporary CSE counts, mad/FMA fast-math policy, clamp/step/mix intrinsic counts, loop-vectorization counts, loop typed-body rebuild status via `loopVectorizationMaterialization.typedBody.rebuild.*`, body-text replacement counts, reachable-node scan / rewrite-granularity fields, per-case method/node identity, exact-int / symbolic-identity / local-CSE-reuse / fast-math-mad / intrinsic-materialization payload evidence, approval payload readiness, loaded/accepted manifest evidence, the review manifest location, and the remaining manual-review/approval blocker. Typed dead-code materialization is also review-only. When it participates, check `typedDeadCodeMaterialization.*` for typed/unreachable/removed node counts, side-effect-freedom proof, status, first blocker, and payload pass counts. It removes only unreachable pure typed nodes in the optimized review artifact, leaves method text bodies unchanged, and does not make `backend.opencl-c` switch to the optimized source. -Safe-local-CSE materialization is review-only too. When it participates, check `safeLocalCseMaterialization.*` for existing local binding count, transformed-node count, body-text replacement count, fixed-point pass count, status, first blocker, and payload pass counts. It can make `optimized.backend.opencl-c` visibly different by reusing local values, but it still does not make `backend.opencl-c` switch to the optimized source. +Safe-local-CSE materialization is review-only too. When it participates, check `safeLocalCseMaterialization.*` for existing local binding count, introduced temporary count, transformed-node count, body-text replacement count, fixed-point pass count, status, first blocker, and payload pass counts. It can make `optimized.backend.opencl-c` visibly different by reusing or introducing local values, but it still does not make `backend.opencl-c` switch to the optimized source. -Mad/FMA materialization is review-only as well. When it participates, check `madFmaMaterialization.*` for candidate/transformed-node counts, body-text replacement count, fixed-point pass count, skipped fast-math policy count, `fastMathAllowed`, status, first blocker, and payload pass counts. The raw pass proof fields still include `optimizerFamily=mad-fma-materialization`, `targetIntrinsic=mad`, and per-case `runtimeEquivalencePayload.Case.*`. It can make `optimized.backend.opencl-c` show `mad(...)`, but `backend.opencl-c` remains the selected source unless future proof/approval/selection gates explicitly allow otherwise. +Mad/FMA materialization is review-only as well. When it participates, check `madFmaMaterialization.*` for candidate/transformed-node counts, body-text replacement count, fixed-point pass count, skipped fast-math policy count, `fastMathAllowed`, status, first blocker, and payload pass counts. The raw pass proof fields still include `optimizerFamily=mad-fma-materialization`, `targetIntrinsic=mad`, and per-case `runtimeEquivalencePayload.Case.*`; subtract cases report `RewriteKind=multiply-subtract-to-opencl-mad`. It can make `optimized.backend.opencl-c` show `mad(...)`, but `backend.opencl-c` remains the selected source unless future proof/approval/selection gates explicitly allow otherwise. Clamp, step, and mix materialization are review-only as well. In this example, `computeVoxelDensity` intentionally contains `min(max(wave1, -1.0f), 1.0f)`, a strict ternary mask `blend > threshold ? 1.0f : 0.0f`, canonical interpolation, and expanded weighted interpolation shapes. When the optional optimizer bridge participates, `optimized.backend.opencl-c` can show `clamp(...)`, `step(...)`, and `mix(...)` candidates while `backend.opencl-c` remains the selected source. Check `clampMaterialization.*`, `stepMaterialization.*`, and `mixMaterialization.*` for candidate/transformed-node counts, direct versus inverted step counts, canonical/expanded/MAD-expanded mix counts, fast-math requirements, body-text replacement counts, status, first blocker, and payload pass counts. To tell whether `backend.opencl-c` came from optimized IR or from the original/pass-through IR, open `runtime-ir-handoff.properties` and check `selectedStage` plus `optimizedDiffersFromOriginal`. -Current alpha behavior is intentionally fail-closed: optimizer evidence and proposed artifacts can be dumped for review, but production mutation and selected-IR replacement remain disabled unless future proof, approval, and production gates explicitly allow them. +Current alpha behavior is intentionally fail-closed by default: optimizer evidence and proposed artifacts can be dumped for review, but production mutation and selected-IR replacement remain disabled unless explicit apply/proof/approval/production gates allow them. If you validate an experimental-apply journal with the raw evidence CLI, use `validateOpenClRuntimeIrOptimizerEvidence --allow-experimental-apply `; without that flag, the validator rejects applied optimized-artifact selection on purpose. diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingMaterializationProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingMaterializationProposalProvider.java index 3391f24e..49ed249f 100644 --- a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingMaterializationProposalProvider.java +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrConstantFoldingMaterializationProposalProvider.java @@ -12,7 +12,6 @@ import java.math.BigInteger; import java.util.ArrayList; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -384,7 +383,7 @@ private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody) { changed = true; stats.fixedPointPassCount++; rewrittenBody = scan.body(); - currentTypedBody = rewriteTypedBody(currentTypedBody, scan.transformedNodes()); + currentTypedBody = scan.typedBody(); } if (!changed) { @@ -399,7 +398,11 @@ private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody) { return new MethodRewrite(copyWithBodyAndTypedBody(methodBody, rewrittenBody, currentTypedBody), stats); } - private record BodyScan(String body, LinkedHashMap transformedNodes) { + private record BodyScan( + String body, + IrGpuTypedBody typedBody, + LinkedHashMap transformedNodes + ) { } private static BodyScan scanFoldCandidates( @@ -409,8 +412,8 @@ private static BodyScan scanFoldCandidates( RewriteStats stats, boolean recordSkips ) { - Map nodesById = nodesById(typedBody); - Set reachableNodeIds = reachableNodeIds(typedBody, nodesById); + Map nodesById = GpuIrTypedBodyGraphPatch.nodesById(typedBody); + Set reachableNodeIds = GpuIrTypedBodyGraphPatch.reachableNodeIds(typedBody, nodesById); LinkedHashMap transformedNodes = new LinkedHashMap<>(); String rewrittenBody = body; @@ -421,17 +424,16 @@ private static BodyScan scanFoldCandidates( FoldAnalysis analysis = analyzeFoldCandidate(methodName, node, nodesById); if (analysis.candidate().isPresent()) { FoldCandidate candidate = analysis.candidate().orElseThrow(); - Replacement replacement = replaceFirst(rewrittenBody, candidate.expressionText(), candidate.foldedValue()); - if (!replacement.replaced()) { + GpuIrTypedBodyGraphPatch.Applied patch = graphPatch(candidate).apply(typedBody, rewrittenBody); + if (!patch.applied()) { if (recordSkips) { stats.candidateCount++; - stats.skippedBodyTextPatternMissingCount++; - stats.setFirstBlocker("body-text-pattern-missing"); + stats.recordPatchBlocker(patch.blocker()); } continue; } stats.candidateCount++; - rewrittenBody = replacement.body(); + rewrittenBody = patch.body(); transformedNodes.put(node.id(), candidate); stats.transformedCandidates.add(candidate); stats.transformedNodeCount++; @@ -447,37 +449,14 @@ private static BodyScan scanFoldCandidates( stats.firstFoldedValue = candidate.foldedValue(); } // Recompute nested candidates against the updated typed body on the next fixed-point pass. - return new BodyScan(rewrittenBody, transformedNodes); + return new BodyScan(rewrittenBody, patch.typedBody(), transformedNodes); } if (recordSkips) { recordSkippedFold(stats, analysis.skippedReason()); } } - return new BodyScan(rewrittenBody, transformedNodes); - } - - private static Set reachableNodeIds(IrGpuTypedBody typedBody, Map nodesById) { - LinkedHashSet reachable = new LinkedHashSet<>(); - ArrayList pending = new ArrayList<>(typedBody.rootNodeIds()); - for (int index = 0; index < pending.size(); index++) { - Integer nodeId = pending.get(index); - if (!reachable.add(nodeId)) { - continue; - } - IrGpuTypedNode node = nodesById.get(nodeId); - if (node == null) { - continue; - } - for (List childIds : node.children().values()) { - for (Integer childId : childIds) { - if (!reachable.contains(childId)) { - pending.add(childId); - } - } - } - } - return reachable; + return new BodyScan(rewrittenBody, typedBody, transformedNodes); } private static void recordSkippedFold(RewriteStats stats, String skippedReason) { @@ -907,30 +886,12 @@ private static Optional divideEvenly(BigInteger left, BigInteger rig return Optional.of(result[0]); } - private record Replacement(String body, boolean replaced) { - } - - private static Replacement replaceFirst(String body, String expressionText, String foldedValue) { - String rewritten = Pattern.compile(Pattern.quote(expressionText)) - .matcher(body) - .replaceFirst(Matcher.quoteReplacement(foldedValue)); - return new Replacement(rewritten, !rewritten.equals(body)); - } - - private static IrGpuTypedBody rewriteTypedBody( - IrGpuTypedBody typedBody, - Map transformedNodes - ) { - ArrayList nodes = new ArrayList<>(); - for (IrGpuTypedNode node : typedBody.nodes()) { - FoldCandidate candidate = transformedNodes.get(node.id()); - if (candidate == null) { - nodes.add(node); - continue; - } - nodes.add(candidate.replacementNode(node.id())); - } - return new IrGpuTypedBody(typedBody.format(), typedBody.rootNodeIds(), nodes); + private static GpuIrTypedBodyGraphPatch.Plan graphPatch(FoldCandidate candidate) { + return GpuIrTypedBodyGraphPatch.plan( + candidate.expressionText(), + candidate.foldedValue(), + candidate.replacementNode(candidate.nodeId()) + ); } private static Map> copyChildren(Map> children) { @@ -939,14 +900,6 @@ private static Map> copyChildren(Map return Map.copyOf(copy); } - private static Map nodesById(IrGpuTypedBody typedBody) { - LinkedHashMap nodes = new LinkedHashMap<>(); - for (IrGpuTypedNode node : typedBody.nodes()) { - nodes.put(node.id(), node); - } - return Map.copyOf(nodes); - } - private static boolean fitsInt(BigInteger value) { return value.compareTo(INT_MIN) >= 0 && value.compareTo(INT_MAX) <= 0; } @@ -1035,6 +988,14 @@ private static final class RewriteStats { private final ArrayList transformedCandidates = new ArrayList<>(); private String firstBlocker = "none"; + private void recordPatchBlocker(String blocker) { + switch (GpuIrTypedBodyGraphPatch.blockerKind(blocker)) { + case BODY_TEXT_PATTERN_MISSING -> skippedBodyTextPatternMissingCount++; + case TYPED_BODY_MISSING, TYPED_GRAPH_MISSING, OTHER -> skippedNonLiteralOperandCount++; + } + setFirstBlocker(blocker); + } + private void add(RewriteStats other) { typedBodyCount += other.typedBodyCount; candidateCount += other.candidateCount; diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrLoopVectorizationMaterializationProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrLoopVectorizationMaterializationProposalProvider.java index 67656944..aca0b9d1 100644 --- a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrLoopVectorizationMaterializationProposalProvider.java +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrLoopVectorizationMaterializationProposalProvider.java @@ -133,6 +133,11 @@ private record ArtifactRewrite( int bodyTextReplacementCount, int typedBodyMaterializedCount, int typedBodyInvalidatedCount, + int typedBodyRebuildAttemptedCount, + int typedBodyRebuildParsedCount, + int typedBodyRebuildBuiltCount, + int typedBodyRebuildGraphValidatedCount, + int typedBodyRebuildRejectedCount, int skippedBackendTargetCount, int skippedUnsupportedFormatCount, int skippedParseFailedCount, @@ -144,6 +149,7 @@ private record ArtifactRewrite( String firstTransformedLoop, String firstExpression, String firstReplacement, + String typedBodyRebuildFirstBlocker, String firstBlocker, boolean openClReviewSourceReady, String openClReviewSourceLength, @@ -204,6 +210,11 @@ private static ArtifactRewrite changed( stats.bodyTextReplacementCount, stats.typedBodyMaterializedCount, stats.typedBodyInvalidatedCount, + stats.typedBodyRebuildAttemptedCount, + stats.typedBodyRebuildParsedCount, + stats.typedBodyRebuildBuiltCount, + stats.typedBodyRebuildGraphValidatedCount, + stats.typedBodyRebuildRejectedCount, stats.skippedBackendTargetCount, stats.skippedUnsupportedFormatCount, stats.skippedParseFailedCount, @@ -215,6 +226,7 @@ private static ArtifactRewrite changed( stats.firstTransformedLoop, stats.firstExpression, stats.firstReplacement, + stats.typedBodyRebuildFirstBlocker, "none", openClReviewSourceReady, openClReviewSourceLength, @@ -234,6 +246,11 @@ private static ArtifactRewrite noChange(RewriteStats stats, String firstBlocker, stats.bodyTextReplacementCount, stats.typedBodyMaterializedCount, stats.typedBodyInvalidatedCount, + stats.typedBodyRebuildAttemptedCount, + stats.typedBodyRebuildParsedCount, + stats.typedBodyRebuildBuiltCount, + stats.typedBodyRebuildGraphValidatedCount, + stats.typedBodyRebuildRejectedCount, stats.skippedBackendTargetCount, stats.skippedUnsupportedFormatCount, stats.skippedParseFailedCount, @@ -245,6 +262,7 @@ private static ArtifactRewrite noChange(RewriteStats stats, String firstBlocker, stats.firstTransformedLoop, stats.firstExpression, stats.firstReplacement, + stats.typedBodyRebuildFirstBlocker, firstBlocker, false, "not-ready", @@ -286,6 +304,13 @@ private Map fields(GpuIrOptimizationProposalRequest request) { fields.put("bodyTextReplacement.count", Integer.toString(bodyTextReplacementCount)); fields.put("typedBody.materialized.count", Integer.toString(typedBodyMaterializedCount)); fields.put("typedBody.invalidated.count", Integer.toString(typedBodyInvalidatedCount)); + fields.put("typedBody.rebuild.attempted.count", Integer.toString(typedBodyRebuildAttemptedCount)); + fields.put("typedBody.rebuild.parsed.count", Integer.toString(typedBodyRebuildParsedCount)); + fields.put("typedBody.rebuild.built.count", Integer.toString(typedBodyRebuildBuiltCount)); + fields.put("typedBody.rebuild.graphValidated.count", Integer.toString(typedBodyRebuildGraphValidatedCount)); + fields.put("typedBody.rebuild.rejected.count", Integer.toString(typedBodyRebuildRejectedCount)); + fields.put("typedBody.rebuild.status", typedBodyRebuildStatus()); + fields.put("typedBody.rebuild.firstBlocker", typedBodyRebuildFirstBlocker); fields.put("skipped.backendTarget.count", Integer.toString(skippedBackendTargetCount)); fields.put("skipped.unsupportedFormat.count", Integer.toString(skippedUnsupportedFormatCount)); fields.put("skipped.parseFailed.count", Integer.toString(skippedParseFailedCount)); @@ -361,6 +386,19 @@ private Map fields(GpuIrOptimizationProposalRequest request) { return Map.copyOf(fields); } + private String typedBodyRebuildStatus() { + if (typedBodyRebuildAttemptedCount <= 0) { + return "not-attempted"; + } + if (typedBodyRebuildRejectedCount <= 0) { + return "validated"; + } + if (typedBodyRebuildGraphValidatedCount > 0) { + return "partially-validated"; + } + return "invalidated"; + } + private static String firstBlocker(RewriteStats stats) { if (!"none".equals(stats.firstBlocker)) { return stats.firstBlocker; @@ -404,12 +442,13 @@ private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody) { } String rewrittenBody = rewriteBody(methodBody.body(), candidates); - Optional rewrittenTypedBody = typedBodyFromRewrittenBody(rewrittenBody); + TypedBodyRebuild rewrittenTypedBody = typedBodyFromRewrittenBody(rewrittenBody); stats.transformedLoopCount += candidates.size(); stats.changedMethodBodyCount = 1; stats.bodyTextReplacementCount += candidates.size(); stats.transformedCandidates.addAll(candidates); - if (rewrittenTypedBody.isPresent()) { + stats.recordTypedBodyRebuild(rewrittenTypedBody); + if (rewrittenTypedBody.typedBody().isPresent()) { stats.typedBodyMaterializedCount++; } else { stats.typedBodyInvalidatedCount += methodBody.typedBody().available() ? 1 : 0; @@ -419,22 +458,76 @@ private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody) { stats.firstExpression = first.originalSummary(); stats.firstReplacement = first.replacementSummary(); return new MethodRewrite( - copyWithBodyAndTypedBody(methodBody, rewrittenBody, rewrittenTypedBody.orElseGet(IrGpuTypedBody::none)), + copyWithBodyAndTypedBody(methodBody, rewrittenBody, rewrittenTypedBody.typedBody().orElseGet(IrGpuTypedBody::none)), stats ); } - private static Optional typedBodyFromRewrittenBody(String body) { + private record TypedBodyRebuild( + Optional typedBody, + boolean parsed, + boolean built, + boolean graphValidated, + String firstBlocker + ) { + + private static TypedBodyRebuild accepted(IrGpuTypedBody typedBody) { + return new TypedBodyRebuild(Optional.of(typedBody), true, true, true, "none"); + } + + private static TypedBodyRebuild rejected( + boolean parsed, + boolean built, + boolean graphValidated, + String firstBlocker + ) { + return new TypedBodyRebuild( + Optional.empty(), + parsed, + built, + graphValidated, + firstBlocker == null || firstBlocker.isBlank() ? "typed-body-rebuild-blocked" : firstBlocker + ); + } + } + + private static TypedBodyRebuild typedBodyFromRewrittenBody(String body) { OpenClIrTextBodyParseResult parseResult = OpenClIrTextBodyParser.INSTANCE.parse(body); if (!parseResult.parsed()) { - return Optional.empty(); + return TypedBodyRebuild.rejected( + false, + false, + false, + parseResult.blockers().isEmpty() ? "typed-body-rebuild-parse-failed" : parseResult.blockers().get(0) + ); } Optional> statements = typedStatements(parseResult.statements()); if (statements.isEmpty()) { - return Optional.empty(); + return TypedBodyRebuild.rejected(true, false, false, "typed-body-rebuild-statement-conversion-blocked"); } IrGpuTypedBody typedBody = IrGpuTypedBodyBuilder.fromStatements(statements.orElseThrow()); - return typedBody.available() ? Optional.of(typedBody) : Optional.empty(); + if (!typedBody.available()) { + return TypedBodyRebuild.rejected(true, false, false, "typed-body-rebuild-builder-unavailable"); + } + TypedBodyGraphValidation graphValidation = typedBodyGraphValidation(typedBody); + return graphValidation.valid() + ? TypedBodyRebuild.accepted(typedBody) + : TypedBodyRebuild.rejected(true, true, false, graphValidation.firstBlocker()); + } + + private record TypedBodyGraphValidation(boolean valid, String firstBlocker) { + } + + private static TypedBodyGraphValidation typedBodyGraphValidation(IrGpuTypedBody typedBody) { + GpuIrTypedBodyGraphPatch.Reachability reachability = GpuIrTypedBodyGraphPatch + .reachability(typedBody, GpuIrTypedBodyGraphPatch.nodesById(typedBody)); + if (reachability.rootMissingCount() > 0) { + return new TypedBodyGraphValidation(false, "typed-body-rebuild-missing-root"); + } + if (reachability.missingChildReferenceCount() > 0) { + return new TypedBodyGraphValidation(false, "typed-body-rebuild-missing-child-reference"); + } + return new TypedBodyGraphValidation(true, "none"); } private static Optional> typedStatements(List statements) { @@ -1497,6 +1590,11 @@ private static final class RewriteStats { private int bodyTextReplacementCount; private int typedBodyMaterializedCount; private int typedBodyInvalidatedCount; + private int typedBodyRebuildAttemptedCount; + private int typedBodyRebuildParsedCount; + private int typedBodyRebuildBuiltCount; + private int typedBodyRebuildGraphValidatedCount; + private int typedBodyRebuildRejectedCount; private int skippedBackendTargetCount; private int skippedUnsupportedFormatCount; private int skippedParseFailedCount; @@ -1508,8 +1606,26 @@ private static final class RewriteStats { private String firstTransformedLoop = "none"; private String firstExpression = "none"; private String firstReplacement = "none"; + private String typedBodyRebuildFirstBlocker = "none"; private String firstBlocker = "none"; + private void recordTypedBodyRebuild(TypedBodyRebuild rebuild) { + typedBodyRebuildAttemptedCount++; + if (rebuild.parsed()) { + typedBodyRebuildParsedCount++; + } + if (rebuild.built()) { + typedBodyRebuildBuiltCount++; + } + if (rebuild.graphValidated()) { + typedBodyRebuildGraphValidatedCount++; + } + if (rebuild.typedBody().isEmpty()) { + typedBodyRebuildRejectedCount++; + setTypedBodyRebuildFirstBlocker(rebuild.firstBlocker()); + } + } + private void add(RewriteStats other) { parsedBodyCount += other.parsedBodyCount; typedBodyCount += other.typedBodyCount; @@ -1519,6 +1635,11 @@ private void add(RewriteStats other) { bodyTextReplacementCount += other.bodyTextReplacementCount; typedBodyMaterializedCount += other.typedBodyMaterializedCount; typedBodyInvalidatedCount += other.typedBodyInvalidatedCount; + typedBodyRebuildAttemptedCount += other.typedBodyRebuildAttemptedCount; + typedBodyRebuildParsedCount += other.typedBodyRebuildParsedCount; + typedBodyRebuildBuiltCount += other.typedBodyRebuildBuiltCount; + typedBodyRebuildGraphValidatedCount += other.typedBodyRebuildGraphValidatedCount; + typedBodyRebuildRejectedCount += other.typedBodyRebuildRejectedCount; skippedBackendTargetCount += other.skippedBackendTargetCount; skippedUnsupportedFormatCount += other.skippedUnsupportedFormatCount; skippedParseFailedCount += other.skippedParseFailedCount; @@ -1532,11 +1653,23 @@ private void add(RewriteStats other) { firstExpression = other.firstExpression; firstReplacement = other.firstReplacement; } + if ("none".equals(typedBodyRebuildFirstBlocker) && !"none".equals(other.typedBodyRebuildFirstBlocker)) { + typedBodyRebuildFirstBlocker = other.typedBodyRebuildFirstBlocker; + } if ("none".equals(firstBlocker) && !"none".equals(other.firstBlocker)) { firstBlocker = other.firstBlocker; } } + private void setTypedBodyRebuildFirstBlocker(String blocker) { + if ("none".equals(typedBodyRebuildFirstBlocker) + && blocker != null + && !blocker.isBlank() + && !"none".equals(blocker)) { + typedBodyRebuildFirstBlocker = blocker; + } + } + private void setFirstBlocker(String blocker) { if ("none".equals(firstBlocker) && blocker != null && !blocker.isBlank() && !"none".equals(blocker)) { firstBlocker = blocker; diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMadFmaMaterializationProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMadFmaMaterializationProposalProvider.java index b03d2c81..64e9bffc 100644 --- a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMadFmaMaterializationProposalProvider.java +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMadFmaMaterializationProposalProvider.java @@ -18,7 +18,7 @@ import java.util.Set; /** - * Review-only fast-math materialization for {@code a * b + c} into an OpenCL {@code mad(a, b, c)} intrinsic. + * Review-only fast-math materialization for multiply-add/subtract expressions into OpenCL {@code mad(...)} intrinsics. */ public final class GpuIrMadFmaMaterializationProposalProvider implements GpuIrOptimizationProposalProvider { @@ -240,7 +240,7 @@ private String verdict() { private String diagnostic() { if (changed()) { return "mad/fma materialization rewrote " + transformedNodeCount - + " multiply-add expression(s) to OpenCL mad intrinsic calls"; + + " multiply-add/subtract expression(s) to OpenCL mad intrinsic calls"; } return "mad/fma materialization produced no review candidate: " + firstBlocker; } @@ -302,7 +302,7 @@ private Map fields(GpuIrOptimizationProposalRequest request) { + ", first=" + firstReplacement); fields.put("runtimeEquivalencePayload.Tolerance", "mode=fast-math-mad-review, strictFloat=false"); fields.put("runtimeEquivalencePayload.FailureFixture", "none"); - fields.put("runtimeEquivalencePayload.ReferenceMode", "static-fast-math-multiply-add-to-mad"); + fields.put("runtimeEquivalencePayload.ReferenceMode", "static-fast-math-multiply-add-or-subtract-to-mad"); fields.put("runtimeEquivalencePayload.CaseIdentity", "method-name-and-node-id"); appendRuntimeEquivalenceCases(fields, transformedCandidates); fields.put("reviewPackage.required", Boolean.toString(changed())); @@ -411,10 +411,10 @@ private static Optional firstMaterializableCandidate( Map nodesById = GpuIrTypedBodyGraphPatch.nodesById(typedBody); Set reachableNodeIds = GpuIrTypedBodyGraphPatch.reachableNodeIds(typedBody, nodesById); for (IrGpuTypedNode node : typedBody.nodes()) { - if (!reachableNodeIds.contains(node.id()) || !isBinary(node, "+")) { + if (!reachableNodeIds.contains(node.id()) || !isMadRoot(node)) { continue; } - Optional candidate = analyzeCandidate(methodName, node, nodesById); + Optional candidate = analyzeCandidate(methodName, node, typedBody, nodesById); if (candidate.isEmpty()) { continue; } @@ -437,11 +437,13 @@ private static Optional firstMaterializableCandidate( private static Optional analyzeCandidate( String methodName, - IrGpuTypedNode addNode, + IrGpuTypedNode rootNode, + IrGpuTypedBody typedBody, Map nodesById ) { - Integer left = singleChild(addNode, "left"); - Integer right = singleChild(addNode, "right"); + String rootOperator = rootNode.attributes().getOrDefault("operator", ""); + Integer left = singleChild(rootNode, "left"); + Integer right = singleChild(rootNode, "right"); if (left == null || right == null) { return Optional.empty(); } @@ -452,6 +454,9 @@ private static Optional analyzeCandidate( if (!leftMultiply && !rightMultiply) { return Optional.empty(); } + if ("-".equals(rootOperator) && !leftMultiply) { + return Optional.empty(); + } IrGpuTypedNode multiply = leftMultiply ? leftNode : rightNode; int addendId = leftMultiply ? right : left; Integer multiplyLeft = singleChild(multiply, "left"); @@ -459,25 +464,49 @@ private static Optional analyzeCandidate( if (multiplyLeft == null || multiplyRight == null) { return Optional.empty(); } - Optional expressionText = sourceText(addNode, nodesById); + Optional expressionText = sourceText(rootNode, nodesById); Optional multiplyLeftText = sourceText(nodesById.get(multiplyLeft), nodesById); Optional multiplyRightText = sourceText(nodesById.get(multiplyRight), nodesById); Optional addendText = sourceText(nodesById.get(addendId), nodesById); if (expressionText.isEmpty() || multiplyLeftText.isEmpty() || multiplyRightText.isEmpty() || addendText.isEmpty()) { return Optional.empty(); } + List argumentNodeIds; + List appendedNodes; + String thirdArgumentText; + String rewriteKind; + if ("-".equals(rootOperator)) { + int negatedAddendId = GpuIrTypedBodyGraphPatch.nextNodeId(typedBody); + IrGpuTypedNode negatedAddend = new IrGpuTypedNode( + negatedAddendId, + "GpuIrUnary", + Map.of("operator", "-"), + Map.of("operand", List.of(addendId)) + ); + argumentNodeIds = List.of(multiplyLeft, multiplyRight, negatedAddendId); + appendedNodes = List.of(negatedAddend); + thirdArgumentText = "(-" + addendText.orElseThrow() + ")"; + rewriteKind = "multiply-subtract-to-opencl-mad"; + } else { + argumentNodeIds = List.of(multiplyLeft, multiplyRight, addendId); + appendedNodes = List.of(); + thirdArgumentText = addendText.orElseThrow(); + rewriteKind = "mad-fma-to-opencl-mad"; + } String replacementText = "intrinsic(" + TARGET_INTRINSIC + " template=\"\" args=[" + multiplyLeftText.orElseThrow() + ", " + multiplyRightText.orElseThrow() + ", " - + addendText.orElseThrow() + "])"; + + thirdArgumentText + "])"; return Optional.of(new MadCandidate( methodName, - addNode.id(), + rootNode.id(), multiply.id(), - List.of(multiplyLeft, multiplyRight, addendId), + argumentNodeIds, expressionText.orElseThrow(), replacementText, - List.of(multiplyLeftText.orElseThrow(), multiplyRightText.orElseThrow(), addendText.orElseThrow()) + List.of(multiplyLeftText.orElseThrow(), multiplyRightText.orElseThrow(), thirdArgumentText), + rewriteKind, + appendedNodes )); } @@ -488,9 +517,18 @@ private record MadCandidate( List argumentNodeIds, String expressionText, String replacementText, - List argumentTexts + List argumentTexts, + String rewriteKind, + List appendedNodes ) { + MadCandidate { + argumentNodeIds = argumentNodeIds == null ? List.of() : List.copyOf(argumentNodeIds); + argumentTexts = argumentTexts == null ? List.of() : List.copyOf(argumentTexts); + rewriteKind = rewriteKind == null || rewriteKind.isBlank() ? "mad-fma-to-opencl-mad" : rewriteKind; + appendedNodes = appendedNodes == null ? List.of() : List.copyOf(appendedNodes); + } + private String summary() { return methodName + "#" + rootNodeId + "=" + expressionText + "->" + replacementText; } @@ -507,7 +545,7 @@ private static void appendRuntimeEquivalenceCases( fields.put(prefix + ".Name", candidate.summary()); fields.put(prefix + ".MethodName", candidate.methodName()); fields.put(prefix + ".NodeId", Integer.toString(candidate.rootNodeId())); - fields.put(prefix + ".RewriteKind", "mad-fma-to-opencl-mad"); + fields.put(prefix + ".RewriteKind", candidate.rewriteKind()); fields.put(prefix + ".Successful", "true"); fields.put(prefix + ".Input.Count", "4"); fields.put(prefix + ".Input.0.Name", "expression"); @@ -541,6 +579,14 @@ private static Optional sourceText(IrGpuTypedNode node, Map operand = childSourceText(node, "operand", nodesById); + if (operator.isBlank() || operand.isEmpty()) { + return Optional.empty(); + } + return Optional.of("(" + operator + operand.orElseThrow() + ")"); + } if ("GpuIrBinary".equals(node.kind())) { String operator = node.attributes().getOrDefault("operator", ""); Optional left = childSourceText(node, "left", nodesById); @@ -592,7 +638,8 @@ private static GpuIrTypedBodyGraphPatch.Plan graphPatch(MadCandidate candidate) "fast-math-review", candidate.argumentNodeIds(), "fast-math-review" - ) + ), + candidate.appendedNodes() ); } @@ -615,6 +662,10 @@ private static boolean isBinary(IrGpuTypedNode node, String operator) { && operator.equals(node.attributes().get("operator")); } + private static boolean isMadRoot(IrGpuTypedNode node) { + return isBinary(node, "+") || isBinary(node, "-"); + } + private static String firstAttribute(IrGpuTypedNode node, String... names) { if (node == null || names == null) { return ""; diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePass.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePass.java index 74f0fcca..b59d9dc0 100644 --- a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePass.java +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePass.java @@ -2,6 +2,7 @@ import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifact; import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuArtifactIdentity; +import net.sixik.ga_utils.javatogpu.runtime.GpuBackendCompileOptions; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationOutcome; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationPass; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationPassReport; @@ -66,6 +67,7 @@ public GpuRuntimeIrOptimizationReport run(GpuRuntimeIrOptimizationRequest reques IrGpuArtifact reviewWorkingArtifact = selected; Optional candidateArtifact = Optional.empty(); ArrayList passReports = new ArrayList<>(); + boolean requestMutationAllowed = mutationAllowedFor(request); for (GpuIrOptimizationProposalProvider provider : providers) { ProviderPolicyGate policyGate = providerPolicyGate(request, provider); if (!policyGate.allowed()) { @@ -80,8 +82,8 @@ public GpuRuntimeIrOptimizationReport run(GpuRuntimeIrOptimizationRequest reques GpuIrOptimizationProposalRequest proposalRequest = new GpuIrOptimizationProposalRequest( reviewWorkingArtifact, request.compileRequest().options().optimizationProfile(), - mutationAllowed, - contextFields(request, mutationAllowed) + requestMutationAllowed, + contextFields(request, requestMutationAllowed) ); GpuIrOptimizationSandwichReport sandwichReport = sandwichRunner.run(proposalRequest, provider); passReports.add(toPassReport(provider, sandwichReport, proposalRequest).withStage(stage())); @@ -90,7 +92,7 @@ public GpuRuntimeIrOptimizationReport run(GpuRuntimeIrOptimizationRequest reques candidateArtifact = selectedCandidateArtifact; reviewWorkingArtifact = selectedCandidateArtifact.orElseThrow(); } - if (mutationAllowed) { + if (requestMutationAllowed) { selected = sandwichReport.selectedArtifact(); reviewWorkingArtifact = selected; } @@ -102,6 +104,14 @@ public GpuRuntimeIrOptimizationReport run(GpuRuntimeIrOptimizationRequest reques return new GpuRuntimeIrOptimizationReport(Optional.of(selected), candidateArtifact, passReports); } + private boolean mutationAllowedFor(GpuRuntimeIrOptimizationRequest request) { + return mutationAllowed + || request.compileRequest() + .options() + .backendOptions() + .requestsRuntimeIrOptimizerExperimentalApply(); + } + private static ProviderPolicyGate providerPolicyGate( GpuRuntimeIrOptimizationRequest request, GpuIrOptimizationProposalProvider provider @@ -199,6 +209,7 @@ private static GpuRuntimeIrOptimizationPassReport toPassReport( .orElseGet(() -> proof(report.status().name().toLowerCase(java.util.Locale.ROOT), Map.of())); proofArtifact = withApprovalTemplateFields(proofArtifact, proposal, request, provider.getClass().getClassLoader()); proofArtifact = withCandidateFields(proofArtifact, report); + proofArtifact = withRuntimeSelectionFields(proofArtifact, report, request); return switch (report.status()) { case OPTIMIZED_SELECTED -> new GpuRuntimeIrOptimizationPassReport( @@ -241,6 +252,36 @@ private static GpuRuntimeIrOptimizationPassReport toPassReport( }; } + private static GpuRuntimeIrOptimizationProofArtifact withRuntimeSelectionFields( + GpuRuntimeIrOptimizationProofArtifact proofArtifact, + GpuIrOptimizationSandwichReport report, + GpuIrOptimizationProposalRequest request + ) { + LinkedHashMap fields = new LinkedHashMap<>(proofArtifact.fields()); + boolean selectedOptimized = report.status() == GpuIrOptimizationSandwichStatus.OPTIMIZED_SELECTED; + fields.put("experimentalApply.selected", Boolean.toString(selectedOptimized)); + if (selectedOptimized) { + fields.put("optimizedArtifactCandidate.selectionReady", "true"); + fields.put("optimizedArtifactCandidate.selectionApplied", "true"); + fields.put("optimizedArtifactCandidate.selectedIrReplacement", "true"); + fields.put("optimizedArtifactCandidate.selectionFirstBlocker", "none"); + fields.put("selectionApplied", "true"); + fields.put("optimizedArtifactSelected", "true"); + fields.put("selectedIrReplacement", "true"); + } else { + fields.putIfAbsent("selectionApplied", "false"); + fields.putIfAbsent("optimizedArtifactSelected", "false"); + fields.putIfAbsent("selectedIrReplacement", "false"); + } + fields.putIfAbsent("experimentalApply.requested", Boolean.toString(request.policy().mutationAllowed())); + fields.putIfAbsent("experimentalApply.enabled", Boolean.toString(request.policy().mutationAllowed())); + return GpuRuntimeIrOptimizationProofArtifact.fromFields( + proofArtifact.source(), + proofArtifact.verdict(), + fields + ); + } + private static GpuRuntimeIrOptimizationPassReport skipped( GpuIrOptimizationProposalProvider provider, String originalIdentity, @@ -349,6 +390,21 @@ private static Map contextFields(GpuRuntimeIrOptimizationRequest fields.put("optimizerPolicy.vectorization", request.optimizerPolicy().vectorization()); fields.put("optimizerPolicy.resourceShaping", Boolean.toString(request.optimizerPolicy().resourceShaping())); fields.put("optimizerPolicy.source", request.optimizerPolicy().source()); + fields.put( + "runtimeIrOptimizerSelection", + request.compileRequest().options().backendOptions().properties().getOrDefault( + GpuBackendCompileOptions.RUNTIME_IR_OPTIMIZER_SELECTION_PROPERTY, + GpuBackendCompileOptions.RUNTIME_IR_OPTIMIZER_SELECTION_REVIEW_ONLY + ) + ); + fields.put( + "experimentalApply.requested", + Boolean.toString(request.compileRequest() + .options() + .backendOptions() + .requestsRuntimeIrOptimizerExperimentalApply()) + ); + fields.put("experimentalApply.enabled", Boolean.toString(mutationAllowed)); fields.put("mutationAllowed", Boolean.toString(mutationAllowed)); return Map.copyOf(fields); } diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrSafeLocalCseMaterializationProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrSafeLocalCseMaterializationProposalProvider.java index 5758bfce..a02e0c5f 100644 --- a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrSafeLocalCseMaterializationProposalProvider.java +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrSafeLocalCseMaterializationProposalProvider.java @@ -9,7 +9,6 @@ import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; import net.sixik.ga_utils.javatogpu.runtime.opencl.OpenClIrGpuSourceEmission; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.LinkedHashSet; @@ -93,6 +92,7 @@ private record ArtifactRewrite( int methodBodyCount, int typedBodyCount, int localBindingCount, + int introducedTemporaryCount, int candidateCount, int transformedNodeCount, int changedMethodBodyCount, @@ -164,6 +164,7 @@ private static ArtifactRewrite changed( stats.methodBodyCount, stats.typedBodyCount, stats.localBindingCount, + stats.introducedTemporaryCount, stats.candidateCount, stats.transformedNodeCount, stats.changedMethodBodyCount, @@ -196,6 +197,7 @@ private static ArtifactRewrite noChange(RewriteStats stats, String firstBlocker, stats.methodBodyCount, stats.typedBodyCount, stats.localBindingCount, + stats.introducedTemporaryCount, stats.candidateCount, stats.transformedNodeCount, stats.changedMethodBodyCount, @@ -239,7 +241,7 @@ private String verdict() { private String diagnostic() { if (changed()) { return "safe local CSE materialization rewrote " + transformedNodeCount - + " repeated pure expression(s) to existing local references"; + + " repeated pure expression(s) to local references"; } return "safe local CSE materialization produced no review candidate: " + firstBlocker; } @@ -250,6 +252,7 @@ private Map fields(GpuIrOptimizationProposalRequest request) { fields.put("methodBody.rewriteScope", "all-method-bodies"); fields.put("typedBody.count", Integer.toString(typedBodyCount)); fields.put("localBinding.count", Integer.toString(localBindingCount)); + fields.put("introducedTemporary.count", Integer.toString(introducedTemporaryCount)); fields.put("candidate.count", Integer.toString(candidateCount)); fields.put("transformedNode.count", Integer.toString(transformedNodeCount)); fields.put("changedMethodBody.count", Integer.toString(changedMethodBodyCount)); @@ -307,16 +310,26 @@ private Map fields(GpuIrOptimizationProposalRequest request) { + ", first=" + firstReplacement); fields.put("runtimeEquivalencePayload.Tolerance", "mode=exact-expression-reuse, floatingPoint=unchanged"); fields.put("runtimeEquivalencePayload.FailureFixture", "none"); - fields.put("runtimeEquivalencePayload.ReferenceMode", "static-existing-local-expression-reuse"); + fields.put( + "runtimeEquivalencePayload.ReferenceMode", + introducedTemporaryCount > 0 + ? "static-local-expression-reuse-with-introduced-temporary" + : "static-existing-local-expression-reuse" + ); fields.put("runtimeEquivalencePayload.CaseIdentity", "method-name-and-node-id"); appendRuntimeEquivalenceCases(fields, transformedCandidates); fields.put("reviewPackage.required", Boolean.toString(changed())); fields.put("reviewPackage.firstBlocker", changed() ? "approval-pending" : firstBlocker); - fields.put("safety.scope", "straight-line-existing-local-pure-binary-expression-reuse"); + fields.put( + "safety.scope", + introducedTemporaryCount > 0 + ? "straight-line-existing-or-introduced-local-pure-binary-expression-reuse" + : "straight-line-existing-local-pure-binary-expression-reuse" + ); fields.put("safety.dominanceProven", Boolean.toString(changed())); fields.put("safety.sideEffectFreedomProven", Boolean.toString(changed())); - fields.put("safety.valueNumberingScope", "method-local-straight-line-existing-local"); - fields.put("safety.newTemporaryIntroduced", "false"); + fields.put("safety.valueNumberingScope", "method-local-straight-line"); + fields.put("safety.newTemporaryIntroduced", Boolean.toString(introducedTemporaryCount > 0)); fields.put("openClReview.sourceReady", Boolean.toString(openClReviewSourceReady)); fields.put("openClReview.sourceLength", openClReviewSourceLength); fields.put("firstTransformedNode", firstTransformedNode); @@ -365,8 +378,9 @@ private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody) { IrGpuMethodBody rewrittenMethodBody = methodBody; int maxPasses = Math.max(1, methodBody.typedBody().nodes().size()); for (int pass = 0; pass < maxPasses; pass++) { - Map nodesById = nodesById(rewrittenMethodBody.typedBody()); - Reachability reachability = reachableNodeIds(rewrittenMethodBody.typedBody(), nodesById); + Map nodesById = GpuIrTypedBodyGraphPatch.nodesById(rewrittenMethodBody.typedBody()); + GpuIrTypedBodyGraphPatch.Reachability reachability = GpuIrTypedBodyGraphPatch + .reachability(rewrittenMethodBody.typedBody(), nodesById); if (reachability.rootMissingCount() > 0 || reachability.missingChildReferenceCount() > 0) { stats.skippedMissingChildReferenceCount += reachability.rootMissingCount() + reachability.missingChildReferenceCount(); @@ -384,6 +398,50 @@ private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody) { scanStats ); if (scanResult.candidate().isEmpty()) { + Optional temporaryCandidate = firstTemporaryIntroductionCandidate( + rewrittenMethodBody.name(), + rewrittenMethodBody.body(), + rewrittenMethodBody.typedBody(), + nodesById, + reachability.reachableNodeIds(), + scanStats + ); + if (temporaryCandidate.isPresent()) { + TemporaryCseCandidate candidate = temporaryCandidate.orElseThrow(); + TemporaryPatch patch = applyTemporaryIntroduction( + rewrittenMethodBody.typedBody(), + rewrittenMethodBody.body(), + nodesById, + candidate + ); + stats.add(scanStats); + if (!patch.applied()) { + stats.candidateCount += candidate.occurrenceCount(); + stats.recordPatchBlocker(patch.blocker()); + break; + } + + stats.candidateCount += candidate.occurrenceCount(); + stats.transformedNodeCount += candidate.occurrenceCount(); + stats.changedMethodBodyCount = 1; + stats.bodyTextReplacementCount += candidate.occurrenceCount(); + stats.fixedPointPassCount++; + stats.introducedTemporaryCount++; + stats.transformedCandidates.addAll(candidate.toCseCandidates()); + if ("none".equals(stats.firstTransformedNode)) { + CseCandidate first = candidate.toCseCandidates().get(0); + stats.firstTransformedNode = first.summary(); + stats.firstExpression = first.expressionText(); + stats.firstReplacement = first.replacementText(); + } + + rewrittenMethodBody = copyWithBodyAndTypedBody( + rewrittenMethodBody, + patch.body(), + patch.typedBody() + ); + continue; + } if (stats.transformedNodeCount == 0) { stats.add(scanStats); } @@ -391,17 +449,12 @@ private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody) { } CseCandidate candidate = scanResult.candidate().orElseThrow(); - Replacement replacement = replaceAfter( - rewrittenMethodBody.body(), - candidate.expressionText(), - candidate.replacementText(), - candidate.bindingSearchStart() - ); + GpuIrTypedBodyGraphPatch.Applied patch = graphPatch(candidate) + .apply(rewrittenMethodBody.typedBody(), rewrittenMethodBody.body()); stats.add(scanStats); - if (!replacement.replaced()) { + if (!patch.applied()) { stats.candidateCount++; - stats.skippedBodyTextPatternMissingCount++; - stats.setFirstBlocker("body-text-pattern-missing"); + stats.recordPatchBlocker(patch.blocker()); break; } @@ -417,12 +470,25 @@ private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody) { stats.firstReplacement = candidate.replacementText(); } - IrGpuTypedBody rewrittenTypedBody = rewriteTypedBody(rewrittenMethodBody.typedBody(), candidate); - rewrittenMethodBody = copyWithBodyAndTypedBody(rewrittenMethodBody, replacement.body(), rewrittenTypedBody); + rewrittenMethodBody = copyWithBodyAndTypedBody(rewrittenMethodBody, patch.body(), patch.typedBody()); } return new MethodRewrite(rewrittenMethodBody, stats); } + private static GpuIrTypedBodyGraphPatch.Plan graphPatch(CseCandidate candidate) { + return GpuIrTypedBodyGraphPatch.plan( + candidate.expressionText(), + candidate.replacementText(), + new IrGpuTypedNode( + candidate.nodeId(), + "GpuIrVariableRef", + Map.of("name", candidate.replacementText()), + Map.of() + ), + candidate.bindingSearchStart() + ); + } + private record ScanResult(Optional candidate) { private static ScanResult none() { @@ -434,6 +500,13 @@ private static ScanResult found(CseCandidate candidate) { } } + private record TemporaryPatch(IrGpuTypedBody typedBody, String body, boolean applied, String blocker) { + + private static TemporaryPatch blocked(IrGpuTypedBody typedBody, String body, String blocker) { + return new TemporaryPatch(typedBody, body, false, blocker); + } + } + private static ScanResult scanMethodBody( String methodName, String body, @@ -460,6 +533,215 @@ private static ScanResult scanMethodBody( return ScanResult.none(); } + private static Optional firstTemporaryIntroductionCandidate( + String methodName, + String body, + IrGpuTypedBody typedBody, + Map nodesById, + Set reachableNodeIds, + RewriteStats stats + ) { + LinkedHashMap> occurrencesByFingerprint = new LinkedHashMap<>(); + List roots = typedBody.rootNodeIds(); + for (int rootIndex = 0; rootIndex < roots.size(); rootIndex++) { + IrGpuTypedNode root = nodesById.get(roots.get(rootIndex)); + if (root == null || !reachableNodeIds.contains(root.id())) { + continue; + } + collectExpressionOccurrences(rootIndex, root, nodesById, reachableNodeIds, occurrencesByFingerprint); + } + + Set localNames = collectLocalNames(typedBody, nodesById); + for (ArrayList occurrences : occurrencesByFingerprint.values()) { + if (occurrences.size() < 2) { + continue; + } + ExpressionOccurrence first = occurrences.get(0); + if (hasLocalInvalidationBetween(typedBody, nodesById, first.expression().variableNames(), occurrences)) { + stats.skippedLocalInvalidationCount++; + stats.setFirstBlocker("local-invalidation-between-cse-uses"); + continue; + } + int textOccurrenceCount = allIndexesOf(body, first.expression().sourceText()).size(); + if (textOccurrenceCount != occurrences.size()) { + stats.skippedBodyTextPatternMissingCount++; + stats.setFirstBlocker("body-text-pattern-missing"); + continue; + } + return Optional.of(new TemporaryCseCandidate( + methodName, + nextTemporaryName(localNames), + inferTemporaryType(first.node(), first.expression().sourceText()), + first.expression(), + List.copyOf(occurrences) + )); + } + return Optional.empty(); + } + + private static void collectExpressionOccurrences( + int rootIndex, + IrGpuTypedNode node, + Map nodesById, + Set reachableNodeIds, + LinkedHashMap> occurrencesByFingerprint + ) { + Optional expression = pureExpression(node, nodesById, null); + if (expression.isPresent() && "GpuIrBinary".equals(node.kind())) { + PureExpression pureExpression = expression.orElseThrow(); + occurrencesByFingerprint.computeIfAbsent(pureExpression.fingerprint(), ignored -> new ArrayList<>()) + .add(new ExpressionOccurrence(rootIndex, node.id(), node, pureExpression)); + } + for (Map.Entry> entry : node.children().entrySet()) { + if ("target".equals(entry.getKey())) { + continue; + } + for (Integer childId : entry.getValue()) { + if (!reachableNodeIds.contains(childId)) { + continue; + } + IrGpuTypedNode child = nodesById.get(childId); + if (child != null) { + collectExpressionOccurrences(rootIndex, child, nodesById, reachableNodeIds, occurrencesByFingerprint); + } + } + } + } + + private static boolean hasLocalInvalidationBetween( + IrGpuTypedBody typedBody, + Map nodesById, + Set variableNames, + List occurrences + ) { + if (variableNames.isEmpty() || occurrences.isEmpty()) { + return false; + } + int firstRootIndex = occurrences.stream().mapToInt(ExpressionOccurrence::rootIndex).min().orElse(0); + int lastRootIndex = occurrences.stream().mapToInt(ExpressionOccurrence::rootIndex).max().orElse(firstRootIndex); + List roots = typedBody.rootNodeIds(); + for (int index = firstRootIndex; index <= lastRootIndex && index < roots.size(); index++) { + IrGpuTypedNode root = nodesById.get(roots.get(index)); + if (root == null) { + continue; + } + Optional source = bindingSource(root, nodesById); + if (source.isPresent() && variableNames.contains(source.orElseThrow().targetName())) { + return true; + } + } + return false; + } + + private static TemporaryPatch applyTemporaryIntroduction( + IrGpuTypedBody typedBody, + String body, + Map nodesById, + TemporaryCseCandidate candidate + ) { + if (typedBody == null || !typedBody.available()) { + return TemporaryPatch.blocked(typedBody, body, "typed-body-missing"); + } + if (body == null || candidate.expression().sourceText().isBlank()) { + return TemporaryPatch.blocked(typedBody, body, "body-text-pattern-missing"); + } + List indexes = allIndexesOf(body, candidate.expression().sourceText()); + if (indexes.size() != candidate.occurrenceCount()) { + return TemporaryPatch.blocked(typedBody, body, "body-text-pattern-missing"); + } + + String replacedBody = replaceAll(body, candidate.expression().sourceText(), candidate.temporaryName()); + int lineStart = lineStart(body, indexes.get(0)); + String declaration = lineIndentation(body, lineStart) + + "var " + candidate.typeName() + " " + candidate.temporaryName() + + " = " + candidate.expression().sourceText() + "\n"; + String patchedBody = replacedBody.substring(0, lineStart) + declaration + replacedBody.substring(lineStart); + IrGpuTypedBody patchedTypedBody = introduceTemporaryTypedBody(typedBody, nodesById, candidate); + GpuIrTypedBodyGraphPatch.Reachability reachability = GpuIrTypedBodyGraphPatch.reachability( + patchedTypedBody, + GpuIrTypedBodyGraphPatch.nodesById(patchedTypedBody) + ); + if (reachability.rootMissingCount() > 0 || reachability.missingChildReferenceCount() > 0) { + return TemporaryPatch.blocked(typedBody, body, "typed-temporary-graph-invalid"); + } + return new TemporaryPatch(patchedTypedBody, patchedBody, true, "none"); + } + + private static IrGpuTypedBody introduceTemporaryTypedBody( + IrGpuTypedBody typedBody, + Map nodesById, + TemporaryCseCandidate candidate + ) { + LinkedHashSet occurrenceIds = new LinkedHashSet<>(); + for (ExpressionOccurrence occurrence : candidate.occurrences()) { + occurrenceIds.add(occurrence.nodeId()); + } + IdAllocator ids = new IdAllocator(GpuIrTypedBodyGraphPatch.nextNodeId(typedBody)); + int declarationId = ids.next(); + CloneResult initializer = cloneSubtree(candidate.occurrences().get(0).nodeId(), nodesById, ids); + IrGpuTypedNode declaration = new IrGpuTypedNode( + declarationId, + "GpuIrVariableDeclaration", + Map.of("typeName", candidate.typeName(), "name", candidate.temporaryName()), + Map.of("initializer", List.of(initializer.rootNodeId())) + ); + + ArrayList nodes = new ArrayList<>(); + for (IrGpuTypedNode node : typedBody.nodes()) { + if (occurrenceIds.contains(node.id())) { + nodes.add(new IrGpuTypedNode( + node.id(), + "GpuIrVariableRef", + Map.of("name", candidate.temporaryName()), + Map.of() + )); + } else { + nodes.add(node); + } + } + nodes.add(declaration); + nodes.addAll(initializer.nodes()); + + ArrayList roots = new ArrayList<>(typedBody.rootNodeIds()); + int insertionIndex = Math.max(0, Math.min(candidate.firstRootIndex(), roots.size())); + roots.add(insertionIndex, declarationId); + return new IrGpuTypedBody(typedBody.format(), roots, nodes); + } + + private static CloneResult cloneSubtree( + int nodeId, + Map nodesById, + IdAllocator ids + ) { + IrGpuTypedNode node = nodesById.get(nodeId); + if (node == null) { + int missingId = ids.next(); + return new CloneResult( + missingId, + List.of(new IrGpuTypedNode(missingId, "GpuIrLiteral", Map.of("sourceText", "0"), Map.of())) + ); + } + LinkedHashMap> children = new LinkedHashMap<>(); + ArrayList clonedNodes = new ArrayList<>(); + for (Map.Entry> entry : node.children().entrySet()) { + ArrayList clonedChildIds = new ArrayList<>(); + for (Integer childId : entry.getValue()) { + CloneResult child = cloneSubtree(childId, nodesById, ids); + clonedChildIds.add(child.rootNodeId()); + clonedNodes.addAll(child.nodes()); + } + children.put(entry.getKey(), List.copyOf(clonedChildIds)); + } + int clonedId = ids.next(); + clonedNodes.add(new IrGpuTypedNode( + clonedId, + node.kind(), + new LinkedHashMap<>(node.attributes()), + children + )); + return new CloneResult(clonedId, clonedNodes); + } + private static void updateLocalBindings( IrGpuTypedNode statement, String body, @@ -606,6 +888,14 @@ private static Optional firstReusableExpression( private record PureExpression(String fingerprint, String sourceText, Set variableNames) { } + private record ExpressionOccurrence( + int rootIndex, + int nodeId, + IrGpuTypedNode node, + PureExpression expression + ) { + } + private static Optional pureExpressionChild( IrGpuTypedNode node, String childName, @@ -692,14 +982,178 @@ private record CseCandidate( int nodeId, String expressionText, String replacementText, - int bindingSearchStart + int bindingSearchStart, + String rewriteKind, + String replacementInputName ) { + private CseCandidate( + String methodName, + int nodeId, + String expressionText, + String replacementText, + int bindingSearchStart + ) { + this( + methodName, + nodeId, + expressionText, + replacementText, + bindingSearchStart, + "safe-local-cse-reuse-existing-local", + "existingLocal" + ); + } + private String summary() { return methodName + "#" + nodeId + "=" + expressionText + "->" + replacementText; } } + private record TemporaryCseCandidate( + String methodName, + String temporaryName, + String typeName, + PureExpression expression, + List occurrences + ) { + + private TemporaryCseCandidate { + occurrences = occurrences == null ? List.of() : List.copyOf(occurrences); + } + + private int occurrenceCount() { + return occurrences.size(); + } + + private int firstRootIndex() { + return occurrences.stream().mapToInt(ExpressionOccurrence::rootIndex).min().orElse(0); + } + + private List toCseCandidates() { + ArrayList candidates = new ArrayList<>(); + for (ExpressionOccurrence occurrence : occurrences) { + candidates.add(new CseCandidate( + methodName, + occurrence.nodeId(), + expression.sourceText(), + temporaryName, + 0, + "safe-local-cse-introduce-local", + "introducedLocal" + )); + } + return List.copyOf(candidates); + } + } + + private record CloneResult(int rootNodeId, List nodes) { + } + + private static final class IdAllocator { + private int nextId; + + private IdAllocator(int nextId) { + this.nextId = Math.max(0, nextId); + } + + private int next() { + return nextId++; + } + } + + private static Set collectLocalNames(IrGpuTypedBody typedBody, Map nodesById) { + LinkedHashSet names = new LinkedHashSet<>(); + for (IrGpuTypedNode node : typedBody.nodes()) { + if ("GpuIrVariableDeclaration".equals(node.kind())) { + String name = node.attributes().getOrDefault("name", ""); + if (isSimpleLocalName(name)) { + names.add(name); + } + } + Optional targetName = simpleTargetName(node, nodesById); + targetName.ifPresent(names::add); + } + return Set.copyOf(names); + } + + private static String nextTemporaryName(Set localNames) { + Set names = localNames == null ? Set.of() : localNames; + for (int index = 0; index < 10_000; index++) { + String candidate = "jtg_cse" + index; + if (!names.contains(candidate)) { + return candidate; + } + } + return "jtg_cse_overflow"; + } + + private static String inferTemporaryType(IrGpuTypedNode node, String expressionText) { + String resultType = firstAttribute(node, "resultType", "typeName", "valueType"); + if (!resultType.isBlank()) { + return resultType; + } + String text = expressionText == null ? "" : expressionText; + if (text.matches(".*[0-9][.][0-9].*") || text.matches(".*[fFdD]\\b.*")) { + return "float"; + } + return "int"; + } + + private static String firstAttribute(IrGpuTypedNode node, String... names) { + if (node == null || names == null) { + return ""; + } + for (String name : names) { + String value = node.attributes().getOrDefault(name, ""); + if (!value.isBlank()) { + return value; + } + } + return ""; + } + + private static List allIndexesOf(String text, String pattern) { + if (text == null || pattern == null || pattern.isBlank()) { + return List.of(); + } + ArrayList indexes = new ArrayList<>(); + int index = text.indexOf(pattern); + while (index >= 0) { + indexes.add(index); + index = text.indexOf(pattern, index + pattern.length()); + } + return List.copyOf(indexes); + } + + private static String replaceAll(String text, String pattern, String replacement) { + StringBuilder builder = new StringBuilder(); + int cursor = 0; + for (Integer index : allIndexesOf(text, pattern)) { + builder.append(text, cursor, index).append(replacement); + cursor = index + pattern.length(); + } + builder.append(text.substring(cursor)); + return builder.toString(); + } + + private static int lineStart(String text, int index) { + int previousNewLine = text.lastIndexOf('\n', Math.max(0, index)); + return previousNewLine < 0 ? 0 : previousNewLine + 1; + } + + private static String lineIndentation(String text, int lineStart) { + StringBuilder indentation = new StringBuilder(); + for (int index = lineStart; index < text.length(); index++) { + char ch = text.charAt(index); + if (ch != ' ' && ch != '\t') { + break; + } + indentation.append(ch); + } + return indentation.toString(); + } + private static void appendRuntimeEquivalenceCases( LinkedHashMap fields, List candidates @@ -711,12 +1165,12 @@ private static void appendRuntimeEquivalenceCases( fields.put(prefix + ".Name", candidate.summary()); fields.put(prefix + ".MethodName", candidate.methodName()); fields.put(prefix + ".NodeId", Integer.toString(candidate.nodeId())); - fields.put(prefix + ".RewriteKind", "safe-local-cse-reuse-existing-local"); + fields.put(prefix + ".RewriteKind", candidate.rewriteKind()); fields.put(prefix + ".Successful", "true"); fields.put(prefix + ".Input.Count", "2"); fields.put(prefix + ".Input.0.Name", "expression"); fields.put(prefix + ".Input.0.Value", candidate.expressionText()); - fields.put(prefix + ".Input.1.Name", "existingLocal"); + fields.put(prefix + ".Input.1.Name", candidate.replacementInputName()); fields.put(prefix + ".Input.1.Value", candidate.replacementText()); fields.put(prefix + ".Output.Count", "1"); fields.put(prefix + ".Output.0.Name", "reusedValue"); @@ -729,76 +1183,6 @@ private static void appendRuntimeEquivalenceCases( } } - private record Reachability(Set reachableNodeIds, int rootMissingCount, int missingChildReferenceCount) { - } - - private static Reachability reachableNodeIds(IrGpuTypedBody typedBody, Map nodesById) { - LinkedHashSet reachable = new LinkedHashSet<>(); - ArrayDeque pending = new ArrayDeque<>(); - int rootMissingCount = 0; - int missingChildReferenceCount = 0; - for (Integer rootNodeId : typedBody.rootNodeIds()) { - if (rootNodeId == null || !nodesById.containsKey(rootNodeId)) { - rootMissingCount++; - continue; - } - pending.add(rootNodeId); - } - while (!pending.isEmpty()) { - int nodeId = pending.removeFirst(); - if (!reachable.add(nodeId)) { - continue; - } - IrGpuTypedNode node = nodesById.get(nodeId); - if (node == null) { - continue; - } - for (List childIds : node.children().values()) { - for (Integer childId : childIds) { - if (childId == null || !nodesById.containsKey(childId)) { - missingChildReferenceCount++; - continue; - } - if (!reachable.contains(childId)) { - pending.add(childId); - } - } - } - } - return new Reachability(Set.copyOf(reachable), rootMissingCount, missingChildReferenceCount); - } - - private record Replacement(String body, boolean replaced) { - } - - private static Replacement replaceAfter(String body, String expressionText, String replacementText, int startIndex) { - int index = body.indexOf(expressionText, Math.max(0, startIndex)); - if (index < 0) { - return new Replacement(body, false); - } - return new Replacement( - body.substring(0, index) + replacementText + body.substring(index + expressionText.length()), - true - ); - } - - private static IrGpuTypedBody rewriteTypedBody(IrGpuTypedBody typedBody, CseCandidate candidate) { - ArrayList nodes = new ArrayList<>(); - for (IrGpuTypedNode node : typedBody.nodes()) { - if (node.id() == candidate.nodeId()) { - nodes.add(new IrGpuTypedNode( - node.id(), - "GpuIrVariableRef", - Map.of("name", candidate.replacementText()), - Map.of() - )); - continue; - } - nodes.add(node); - } - return new IrGpuTypedBody(typedBody.format(), typedBody.rootNodeIds(), nodes); - } - private static boolean hasControlFlowBoundary(IrGpuTypedBody typedBody) { for (IrGpuTypedNode node : typedBody.nodes()) { if (List.of("GpuIrIf", "GpuIrForLoop", "GpuIrWhileLoop", "GpuIrDoWhileLoop", "GpuIrSwitch") @@ -809,14 +1193,6 @@ private static boolean hasControlFlowBoundary(IrGpuTypedBody typedBody) { return false; } - private static Map nodesById(IrGpuTypedBody typedBody) { - LinkedHashMap nodes = new LinkedHashMap<>(); - for (IrGpuTypedNode node : typedBody.nodes()) { - nodes.put(node.id(), node); - } - return Map.copyOf(nodes); - } - private static boolean isSimpleLocalName(String name) { return name != null && SIMPLE_LOCAL_NAME.matcher(name).matches(); } @@ -885,6 +1261,7 @@ private static final class RewriteStats { private int methodBodyCount; private int typedBodyCount; private int localBindingCount; + private int introducedTemporaryCount; private int candidateCount; private int transformedNodeCount; private int changedMethodBodyCount; @@ -906,9 +1283,20 @@ private static final class RewriteStats { private String firstReplacement = "none"; private String firstBlocker = "none"; + private void recordPatchBlocker(String blocker) { + switch (GpuIrTypedBodyGraphPatch.blockerKind(blocker)) { + case BODY_TEXT_PATTERN_MISSING -> skippedBodyTextPatternMissingCount++; + case TYPED_BODY_MISSING -> skippedTypedBodyMissingCount++; + case TYPED_GRAPH_MISSING -> skippedMissingChildReferenceCount++; + case OTHER -> skippedImpureOperandCount++; + } + setFirstBlocker(blocker); + } + private void add(RewriteStats other) { typedBodyCount += other.typedBodyCount; localBindingCount += other.localBindingCount; + introducedTemporaryCount += other.introducedTemporaryCount; candidateCount += other.candidateCount; transformedNodeCount += other.transformedNodeCount; changedMethodBodyCount += other.changedMethodBodyCount; diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedBodyGraphPatch.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedBodyGraphPatch.java index 0d2d3834..993a51b5 100644 --- a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedBodyGraphPatch.java +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedBodyGraphPatch.java @@ -32,29 +32,50 @@ static Map nodesById(IrGpuTypedBody typedBody) { } static Set reachableNodeIds(IrGpuTypedBody typedBody, Map nodesById) { + return reachability(typedBody, nodesById).reachableNodeIds(); + } + + static Reachability reachability(IrGpuTypedBody typedBody, Map nodesById) { LinkedHashSet reachable = new LinkedHashSet<>(); - if (typedBody == null || nodesById == null || nodesById.isEmpty()) { - return Set.of(); + if (typedBody == null) { + return new Reachability(Set.of(), 0, 0); + } + Map safeNodesById = nodesById == null ? Map.of() : nodesById; + ArrayList pending = new ArrayList<>(); + int rootMissingCount = 0; + int missingChildReferenceCount = 0; + for (Integer rootNodeId : typedBody.rootNodeIds()) { + if (rootNodeId == null || !safeNodesById.containsKey(rootNodeId)) { + rootMissingCount++; + continue; + } + pending.add(rootNodeId); } - ArrayList pending = new ArrayList<>(typedBody.rootNodeIds()); while (!pending.isEmpty()) { int nodeId = pending.remove(pending.size() - 1); if (!reachable.add(nodeId)) { continue; } - IrGpuTypedNode node = nodesById.get(nodeId); + IrGpuTypedNode node = safeNodesById.get(nodeId); if (node == null) { continue; } for (List childIds : node.children().values()) { for (Integer childId : childIds) { - if (childId != null && nodesById.containsKey(childId) && !reachable.contains(childId)) { + if (childId == null || !safeNodesById.containsKey(childId)) { + missingChildReferenceCount++; + continue; + } + if (!reachable.contains(childId)) { pending.add(childId); } } } } - return Set.copyOf(reachable); + return new Reachability(Set.copyOf(reachable), rootMissingCount, missingChildReferenceCount); + } + + record Reachability(Set reachableNodeIds, int rootMissingCount, int missingChildReferenceCount) { } static int nextNodeId(IrGpuTypedBody typedBody) { @@ -109,7 +130,16 @@ static Plan plan( String replacementText, IrGpuTypedNode replacementNode ) { - return new Plan(sourceText, replacementText, replacementNode, List.of()); + return new Plan(sourceText, replacementText, replacementNode, List.of(), 0); + } + + static Plan plan( + String sourceText, + String replacementText, + IrGpuTypedNode replacementNode, + int searchStartIndex + ) { + return new Plan(sourceText, replacementText, replacementNode, List.of(), searchStartIndex); } static Plan plan( @@ -118,7 +148,17 @@ static Plan plan( IrGpuTypedNode replacementNode, List appendedNodes ) { - return new Plan(sourceText, replacementText, replacementNode, appendedNodes); + return new Plan(sourceText, replacementText, replacementNode, appendedNodes, 0); + } + + static Plan plan( + String sourceText, + String replacementText, + IrGpuTypedNode replacementNode, + List appendedNodes, + int searchStartIndex + ) { + return new Plan(sourceText, replacementText, replacementNode, appendedNodes, searchStartIndex); } static PatchBlockerKind blockerKind(String blocker) { @@ -141,13 +181,15 @@ record Plan( String sourceText, String replacementText, IrGpuTypedNode replacementNode, - List appendedNodes + List appendedNodes, + int searchStartIndex ) { Plan { sourceText = sourceText == null ? "" : sourceText; replacementText = replacementText == null ? "" : replacementText; appendedNodes = appendedNodes == null ? List.of() : List.copyOf(appendedNodes); + searchStartIndex = Math.max(0, searchStartIndex); } Applied apply(IrGpuTypedBody typedBody, String body) { @@ -163,7 +205,7 @@ Applied apply(IrGpuTypedBody typedBody, String body) { if (body == null || sourceText.isBlank()) { return Applied.blocked(typedBody, body, BODY_TEXT_PATTERN_MISSING); } - int index = body.indexOf(sourceText); + int index = body.indexOf(sourceText, searchStartIndex); if (index < 0) { return Applied.blocked(typedBody, body, BODY_TEXT_PATTERN_MISSING); } diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedDeadCodeMaterializationProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedDeadCodeMaterializationProposalProvider.java index 76bf03a8..25927bda 100644 --- a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedDeadCodeMaterializationProposalProvider.java +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedDeadCodeMaterializationProposalProvider.java @@ -8,14 +8,11 @@ import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; import java.util.stream.Collectors; /** @@ -268,8 +265,9 @@ private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody) { } stats.typedBodyCount = 1; IrGpuTypedBody typedBody = methodBody.typedBody(); - Map nodesById = nodesById(typedBody); - Reachability reachability = reachableNodeIds(typedBody, nodesById); + Map nodesById = GpuIrTypedBodyGraphPatch.nodesById(typedBody); + GpuIrTypedBodyGraphPatch.Reachability reachability = GpuIrTypedBodyGraphPatch + .reachability(typedBody, nodesById); stats.nodeCount = typedBody.nodes().size(); stats.reachableNodeCount = reachability.reachableNodeIds().size(); stats.rootMissingCount = reachability.rootMissingCount(); @@ -306,9 +304,6 @@ private static MethodRewrite rewriteMethodBody(IrGpuMethodBody methodBody) { ); } - private record Reachability(Set reachableNodeIds, int rootMissingCount, int missingChildReferenceCount) { - } - private record RemovedNode(String methodName, int nodeId, String nodeKind) { private String summary() { return methodName + "#" + nodeId + "=" + nodeKind; @@ -339,42 +334,6 @@ private static void appendRuntimeEquivalenceCases(LinkedHashMap } } - private static Reachability reachableNodeIds(IrGpuTypedBody typedBody, Map nodesById) { - LinkedHashSet reachable = new LinkedHashSet<>(); - ArrayDeque pending = new ArrayDeque<>(); - int rootMissingCount = 0; - int missingChildReferenceCount = 0; - for (Integer rootNodeId : typedBody.rootNodeIds()) { - if (rootNodeId == null || !nodesById.containsKey(rootNodeId)) { - rootMissingCount++; - continue; - } - pending.add(rootNodeId); - } - while (!pending.isEmpty()) { - int nodeId = pending.removeFirst(); - if (!reachable.add(nodeId)) { - continue; - } - IrGpuTypedNode node = nodesById.get(nodeId); - if (node == null) { - continue; - } - for (List childIds : node.children().values()) { - for (Integer childId : childIds) { - if (childId == null || !nodesById.containsKey(childId)) { - missingChildReferenceCount++; - continue; - } - if (!reachable.contains(childId)) { - pending.add(childId); - } - } - } - } - return new Reachability(Set.copyOf(reachable), rootMissingCount, missingChildReferenceCount); - } - private static boolean isSideEffecting(IrGpuTypedNode node) { return List.of( "GpuIrAssignment", @@ -387,14 +346,6 @@ private static boolean isSideEffecting(IrGpuTypedNode node) { ).contains(node.kind()); } - private static Map nodesById(IrGpuTypedBody typedBody) { - LinkedHashMap nodes = new LinkedHashMap<>(); - for (IrGpuTypedNode node : typedBody.nodes()) { - nodes.put(node.id(), node); - } - return Map.copyOf(nodes); - } - private static IrGpuMethodBody copyWithTypedBody(IrGpuMethodBody methodBody, IrGpuTypedBody typedBody) { return new IrGpuMethodBody( methodBody.role(), diff --git a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedDeadCodePreviewProposalProvider.java b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedDeadCodePreviewProposalProvider.java index daabc0e8..0a7e3c5e 100644 --- a/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedDeadCodePreviewProposalProvider.java +++ b/ir-optimizer/src/main/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedDeadCodePreviewProposalProvider.java @@ -6,13 +6,10 @@ import net.sixik.ga_utils.javatogpu.frontend.ir.artifact.IrGpuTypedNode; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; -import java.util.ArrayDeque; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; import java.util.stream.Collectors; /** @@ -91,8 +88,9 @@ private static Preview from(IrGpuArtifact artifact) { } typedBodyCount++; IrGpuTypedBody typedBody = methodBody.typedBody(); - Map nodesById = nodesById(typedBody); - Reachability reachability = reachableNodeIds(typedBody, nodesById); + Map nodesById = GpuIrTypedBodyGraphPatch.nodesById(typedBody); + GpuIrTypedBodyGraphPatch.Reachability reachability = GpuIrTypedBodyGraphPatch + .reachability(typedBody, nodesById); nodeCount += typedBody.nodes().size(); reachableNodeCount += reachability.reachableNodeIds().size(); rootMissingCount += reachability.rootMissingCount(); @@ -188,45 +186,6 @@ private static String countsSummary(Map counts) { } } - private record Reachability(Set reachableNodeIds, int rootMissingCount, int missingChildReferenceCount) { - } - - private static Reachability reachableNodeIds(IrGpuTypedBody typedBody, Map nodesById) { - HashSet reachable = new HashSet<>(); - ArrayDeque pending = new ArrayDeque<>(); - int rootMissingCount = 0; - int missingChildReferenceCount = 0; - for (Integer rootNodeId : typedBody.rootNodeIds()) { - if (rootNodeId == null || !nodesById.containsKey(rootNodeId)) { - rootMissingCount++; - continue; - } - pending.add(rootNodeId); - } - while (!pending.isEmpty()) { - int nodeId = pending.removeFirst(); - if (!reachable.add(nodeId)) { - continue; - } - IrGpuTypedNode node = nodesById.get(nodeId); - if (node == null) { - continue; - } - for (List childIds : node.children().values()) { - for (Integer childId : childIds) { - if (childId == null || !nodesById.containsKey(childId)) { - missingChildReferenceCount++; - continue; - } - if (!reachable.contains(childId)) { - pending.add(childId); - } - } - } - } - return new Reachability(Set.copyOf(reachable), rootMissingCount, missingChildReferenceCount); - } - private static boolean isSideEffecting(IrGpuTypedNode node) { return List.of( "GpuIrAssignment", @@ -239,11 +198,4 @@ private static boolean isSideEffecting(IrGpuTypedNode node) { ).contains(node.kind()); } - private static Map nodesById(IrGpuTypedBody typedBody) { - LinkedHashMap nodes = new LinkedHashMap<>(); - for (IrGpuTypedNode node : typedBody.nodes()) { - nodes.put(node.id(), node); - } - return Map.copyOf(nodes); - } } diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrLoopVectorizationMaterializationProposalProviderTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrLoopVectorizationMaterializationProposalProviderTest.java index 5fbe4818..6a512aa0 100644 --- a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrLoopVectorizationMaterializationProposalProviderTest.java +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrLoopVectorizationMaterializationProposalProviderTest.java @@ -72,6 +72,13 @@ void materializesFixedWidthContiguousFloatReductionAsVload4ReviewCandidate() { assertEquals("1", fields.get("bodyTextReplacement.count")); assertEquals("1", fields.get("typedBody.materialized.count")); assertEquals("0", fields.get("typedBody.invalidated.count")); + assertEquals("1", fields.get("typedBody.rebuild.attempted.count")); + assertEquals("1", fields.get("typedBody.rebuild.parsed.count")); + assertEquals("1", fields.get("typedBody.rebuild.built.count")); + assertEquals("1", fields.get("typedBody.rebuild.graphValidated.count")); + assertEquals("0", fields.get("typedBody.rebuild.rejected.count")); + assertEquals("validated", fields.get("typedBody.rebuild.status")); + assertEquals("none", fields.get("typedBody.rebuild.firstBlocker")); assertEquals("true", fields.get("rewrite.materialized")); assertEquals("false", fields.get("provider.mutatesOriginal")); assertEquals("true", fields.get("runtimeEquivalencePayload.present")); @@ -176,6 +183,41 @@ void materializesConstantOffsetContiguousBaseAsVload4ReviewCandidate() { assertTrue(proposal.proofArtifact().fields().get("firstReplacement").contains("(&input[((id * 4) + 8)])")); } + @Test + void invalidatesTypedBodyWhenReviewSourceRewriteCannotBeRebuiltAsTypedIr() { + IrGpuArtifact original = artifact(""" + body + var int id = intrinsic(get_global_id template="" args=[0]) + var float sum = 0.0F + for init=(var int i = 0) cond=(i < 4) update=(set i = (i + 1)) + set sum = (sum + input[((id * 4) + i)]) + set output[id] = (flag ? sum : 0.0F) + """); + + GpuIrOptimizationProposal proposal = new GpuIrLoopVectorizationMaterializationProposalProvider() + .propose(openClRequest(original)); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertTrue(optimized.module().methodBodies().get(0).body() + .contains("intrinsic(vload4 template=\"\" args=[0, (&input[(id * 4)])])")); + assertFalse(optimized.module().methodBodies().get(0).typedBody().available()); + + Map fields = proposal.proofArtifact().fields(); + assertEquals("1", fields.get("transformedLoop.count")); + assertEquals("0", fields.get("typedBody.materialized.count")); + assertEquals("1", fields.get("typedBody.invalidated.count")); + assertEquals("1", fields.get("typedBody.rebuild.attempted.count")); + assertEquals("1", fields.get("typedBody.rebuild.parsed.count")); + assertEquals("0", fields.get("typedBody.rebuild.built.count")); + assertEquals("0", fields.get("typedBody.rebuild.graphValidated.count")); + assertEquals("1", fields.get("typedBody.rebuild.rejected.count")); + assertEquals("invalidated", fields.get("typedBody.rebuild.status")); + assertEquals("typed-body-rebuild-statement-conversion-blocked", fields.get("typedBody.rebuild.firstBlocker")); + assertEquals("false", fields.get("safety.typedBodyMaterializedForReview")); + assertEquals("true", fields.get("safety.typedBodyInvalidatedForReview")); + } + @Test void blocksMaterializationWhenBackendTargetIsNotOpenCl() { IrGpuArtifact original = artifact(""" diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMadFmaMaterializationProposalProviderTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMadFmaMaterializationProposalProviderTest.java index 87ff9562..3f9b9a05 100644 --- a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMadFmaMaterializationProposalProviderTest.java +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrMadFmaMaterializationProposalProviderTest.java @@ -68,7 +68,7 @@ void materializesMultiplyAddAsOpenClMadReviewCandidate() { assertEquals("false", fields.get("safety.strictFloatPreserved")); assertEquals("true", fields.get("runtimeEquivalencePayload.present")); assertEquals("true", fields.get("runtimeEquivalencePayload.passed")); - assertEquals("static-fast-math-multiply-add-to-mad", fields.get("runtimeEquivalencePayload.ReferenceMode")); + assertEquals("static-fast-math-multiply-add-or-subtract-to-mad", fields.get("runtimeEquivalencePayload.ReferenceMode")); assertEquals("1", fields.get("runtimeEquivalencePayload.Case.Count")); assertEquals("mad-fma-to-opencl-mad", fields.get("runtimeEquivalencePayload.Case.0.RewriteKind")); assertEquals("((left * right) + addend)", fields.get("runtimeEquivalencePayload.Case.0.Output.0.PreOptimization")); @@ -77,6 +77,53 @@ void materializesMultiplyAddAsOpenClMadReviewCandidate() { assertEquals("kernel#2=((left * right) + addend)->intrinsic(mad template=\"\" args=[left, right, addend])", fields.get("firstTransformedNode")); } + @Test + void materializesMultiplySubtractAsOpenClMadWithNegatedAddend() { + IrGpuArtifact original = subtractArtifact(true); + + GpuIrOptimizationProposal proposal = new GpuIrMadFmaMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of("backendTarget", "OPENCL") + )); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + assertSame(original, proposal.originalArtifact()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertEquals( + "body\n set output[0] = intrinsic(mad template=\"\" args=[value, 3.1415F, (-5.0F)])\n", + optimized.module().methodBodies().get(0).body() + ); + List nodes = optimized.module().methodBodies().get(0).typedBody().nodes(); + IrGpuTypedNode replacement = nodes.get(2); + assertEquals("GpuIrIntrinsicCall", replacement.kind()); + assertEquals("mad", replacement.attributes().get("name")); + assertEquals("mad", replacement.attributes().get("backendName")); + assertEquals(List.of(4, 5, 7), replacement.children().get("arguments")); + IrGpuTypedNode negatedAddend = nodes.get(7); + assertEquals("GpuIrUnary", negatedAddend.kind()); + assertEquals("-", negatedAddend.attributes().get("operator")); + assertEquals(List.of(6), negatedAddend.children().get("operand")); + assertTrue(optimized.regenerationMetadata().backendNeutralSourceReady()); + + Map fields = proposal.proofArtifact().fields(); + assertEquals("mad-fma-materialization", fields.get("optimizerFamily")); + assertEquals("1", fields.get("candidate.count")); + assertEquals("1", fields.get("transformedNode.count")); + assertEquals("1", fields.get("bodyTextReplacement.count")); + assertEquals("multiply-subtract-to-opencl-mad", fields.get("runtimeEquivalencePayload.Case.0.RewriteKind")); + assertEquals("((value * 3.1415F) - 5.0F)", fields.get("runtimeEquivalencePayload.Case.0.Output.0.PreOptimization")); + assertEquals("intrinsic(mad template=\"\" args=[value, 3.1415F, (-5.0F)])", fields.get("runtimeEquivalencePayload.Case.0.Output.0.PostOptimization")); + assertEquals("(-5.0F)", fields.get("runtimeEquivalencePayload.Case.0.Input.3.Value")); + assertEquals("true", fields.get("openClReview.sourceReady")); + assertEquals( + "kernel#2=((value * 3.1415F) - 5.0F)->intrinsic(mad template=\"\" args=[value, 3.1415F, (-5.0F)])", + fields.get("firstTransformedNode") + ); + } + @Test void blocksMaterializationWhenFastMathPolicyIsStrict() { IrGpuArtifact original = artifact(false); @@ -137,6 +184,36 @@ private static IrGpuArtifact artifact(boolean fastMath) { ); } + private static IrGpuArtifact subtractArtifact(boolean fastMath) { + IrGpuMethodBody methodBody = new IrGpuMethodBody( + "entry", + "kernel", + "kernel", + "ir-text-v1", + "body\n set output[0] = ((value * 3.1415F) - 5.0F)\n", + subtractTypedBody(), + IrGpuBodyIndex.empty(), + List.of(), + IrGpuSourceLocation.unknown("kernel") + ); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + new IrGpuModule("kernel", "kernel", List.of(), List.of(), List.of(methodBody)), + List.of( + new IrGpuEntryParameter("output", "float[]", "GLOBAL", false, List.of()), + new IrGpuEntryParameter("value", "float", "PRIVATE", false, List.of()) + ), + IrGpuLaunchMetadata.defaultOneDimensional(), + IrGpuValidationMetadata.frontendSubset(), + IrGpuFeatureMetadata.none(), + fastMath ? IrGpuOptimizerPolicyMetadata.fromGpuOptimize(true) : IrGpuOptimizerPolicyMetadata.defaultStrict(), + IrGpuRegenerationMetadata.transitionalIrText(), + List.of(IrGpuBackendOutput.openClSource("javatogpu/test/Kernel/kernel.cl")), + "opencl", + "off" + ); + } + private static IrGpuTypedBody typedBody() { return new IrGpuTypedBody( IrGpuTypedBody.FORMAT, @@ -161,4 +238,29 @@ private static IrGpuTypedBody typedBody() { ) ); } + + private static IrGpuTypedBody subtractTypedBody() { + return new IrGpuTypedBody( + IrGpuTypedBody.FORMAT, + List.of(0), + List.of( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of( + "target", List.of(1), + "value", List.of(2) + )), + new IrGpuTypedNode(1, "GpuIrVariableRef", Map.of("name", "output[0]"), Map.of()), + new IrGpuTypedNode(2, "GpuIrBinary", Map.of("operator", "-"), Map.of( + "left", List.of(3), + "right", List.of(6) + )), + new IrGpuTypedNode(3, "GpuIrBinary", Map.of("operator", "*"), Map.of( + "left", List.of(4), + "right", List.of(5) + )), + new IrGpuTypedNode(4, "GpuIrVariableRef", Map.of("name", "value"), Map.of()), + new IrGpuTypedNode(5, "GpuIrLiteral", Map.of("sourceText", "3.1415F"), Map.of()), + new IrGpuTypedNode(6, "GpuIrLiteral", Map.of("sourceText", "5.0F"), Map.of()) + ) + ); + } } diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePassTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePassTest.java index 187b4d67..8f4e7e7b 100644 --- a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePassTest.java +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrProposalRuntimeBridgePassTest.java @@ -42,6 +42,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.charset.StandardCharsets; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -52,6 +53,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -393,6 +395,35 @@ void bridgeMaterializesConstantFoldingCandidateButKeepsOriginalSelectedByDefault ); } + @Test + void bridgeSelectsMaterializedCandidateWhenExperimentalApplyCompileOptionIsSet() { + IrGpuArtifact original = constantFoldingArtifact(); + GpuIrProposalRuntimeBridgePass bridge = new GpuIrProposalRuntimeBridgePass( + List.of(new GpuIrConstantFoldingMaterializationProposalProvider()), + GpuIrOptimizationSandwichRunner.alwaysValid(), + false + ); + + GpuRuntimeIrOptimizationReport report = bridge.run(new GpuRuntimeIrOptimizationRequest( + request(original, GpuRuntimeCompileOptions.openClIrOptimizerExperimentalApply(List.of(), "diagnostic")), + Optional.of(original) + )); + + IrGpuArtifact selected = report.artifact().orElseThrow(); + assertNotSame(original, selected); + assertEquals("body\n set output[0] = 5\n", selected.module().methodBodies().get(0).body()); + assertEquals(selected, report.candidateArtifact().orElseThrow()); + assertEquals(GpuRuntimeIrOptimizationOutcome.APPLIED, report.passReports().get(0).outcome()); + assertEquals("optimized-selected", report.passReports().get(0).proofStatus()); + Map fields = report.passReports().get(0).proofArtifact().fields(); + assertEquals("true", fields.get("experimentalApply.requested")); + assertEquals("true", fields.get("experimentalApply.enabled")); + assertEquals("true", fields.get("experimentalApply.selected")); + assertEquals("true", fields.get("optimizedArtifactCandidate.selectionApplied")); + assertEquals("true", fields.get("optimizedArtifactCandidate.selectedIrReplacement")); + assertEquals("none", fields.get("optimizedArtifactCandidate.selectionFirstBlocker")); + } + @Test void bridgeChainsReviewOnlyMaterializationsWithoutSelectingOptimizedIr() { IrGpuArtifact original = chainedMaterializationArtifact(); @@ -587,6 +618,128 @@ void bridgeCandidateEnvelopeReachesRuntimeEvidenceArtifact() throws Exception { )); } + @Test + void bridgeDumpsLoopVectorizationValidatedTypedBodyRebuildAsReviewReadyEvidence() throws Exception { + IrGpuArtifact original = loopVectorizationValidatedArtifact(); + GpuRuntimeCompileRequest compileRequest = request(original); + GpuRuntimeIrOptimizationReport optimizationReport = new GpuIrProposalRuntimeBridgePass( + List.of(new GpuIrLoopVectorizationMaterializationProposalProvider()), + GpuIrOptimizationSandwichRunner.alwaysValid(), + false + ).run(new GpuRuntimeIrOptimizationRequest(compileRequest, Optional.of(original))); + + assertSame(original, optimizationReport.artifact().orElseThrow()); + IrGpuArtifact optimized = optimizationReport.candidateArtifact().orElseThrow(); + assertTrue(optimized.module().methodBodies().get(0).body() + .contains("intrinsic(vload4 template=\"\" args=[0, (&input[(id * 4)])])")); + assertTrue(optimized.module().methodBodies().get(0).typedBody().available()); + assertTrue(optimized.module().methodBodies().get(0).typedBody().nodes().stream().anyMatch(node -> + "GpuIrIntrinsicCall".equals(node.kind()) && "vload4".equals(node.attributes().get("backendName")) + )); + Map fields = optimizationReport.passReports().get(0).proofArtifact().fields(); + assertEquals("validated", fields.get("typedBody.rebuild.status")); + assertEquals("none", fields.get("typedBody.rebuild.firstBlocker")); + + GpuRuntimeCompileArtifactDump dump = runtimeOptimizerDump(original, optimizationReport); + String evidence = dump.artifact(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + + assertTrue(dump.artifact("backend.opencl-c").contains("out[0] = 1")); + assertFalse(dump.artifact("backend.opencl-c").contains("vload4")); + assertTrue(dump.artifact("optimized.backend.opencl-c").contains("vload4")); + String optimizedIrGpu = dump.artifact("optimized.irgpu.properties"); + assertTrue(optimizedIrGpu.contains("GpuIrIntrinsicCall")); + assertTrue(serializedNamedValuePresent(optimizedIrGpu, ".typed.node.", "backendName", "vload4")); + assertTrue(dump.artifact("runtime-ir-handoff.properties").contains("selectedStage=original")); + assertTrue(dump.artifact("runtime-production-mutation-safety.properties").contains("selectedStage=original")); + + assertTrue(evidence.contains("loopVectorizationMaterialization.transformedLoop.count=1")); + assertTrue(evidence.contains("loopVectorizationMaterialization.bodyTextReplacement.count=1")); + assertTrue(evidence.contains("loopVectorizationMaterialization.typedBody.materialized.count=1")); + assertTrue(evidence.contains("loopVectorizationMaterialization.typedBody.invalidated.count=0")); + assertTrue(evidence.contains("loopVectorizationMaterialization.typedBody.rebuild.attempted.count=1")); + assertTrue(evidence.contains("loopVectorizationMaterialization.typedBody.rebuild.parsed.count=1")); + assertTrue(evidence.contains("loopVectorizationMaterialization.typedBody.rebuild.built.count=1")); + assertTrue(evidence.contains("loopVectorizationMaterialization.typedBody.rebuild.graphValidated.count=1")); + assertTrue(evidence.contains("loopVectorizationMaterialization.typedBody.rebuild.rejected.count=0")); + assertTrue(evidence.contains("loopVectorizationMaterialization.typedBody.rebuild.status=validated")); + assertTrue(evidence.contains("loopVectorizationMaterialization.typedBody.rebuild.firstBlocker=none")); + assertTrue(evidence.contains("loopVectorizationMaterialization.runtimeEquivalencePayloadPresent.count=1")); + assertTrue(evidence.contains("loopVectorizationMaterialization.runtimeEquivalencePassed.count=1")); + assertTrue(evidence.contains("loopVectorizationMaterialization.status=review-ready")); + assertTrue(evidence.contains("loopVectorizationMaterialization.firstBlocker=none")); + assertTrue(evidence.contains("runtimeEquivalenceReview.status=review-ready")); + assertTrue(evidence.contains("runtimeEquivalenceReview.firstBlocker=none")); + assertTrue(evidence.contains("loop-vectorization-materialization=review-ready")); + assertTrue(evidence.contains("optimizedArtifactCandidate.selectionApplied=false")); + assertTrue(evidence.contains("optimizedArtifactCandidate.selectedIrReplacement=false")); + + Path evidenceFile = temporaryDirectory.resolve( + GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT + ); + Files.writeString(evidenceFile, evidence, StandardCharsets.UTF_8); + assertDoesNotThrow(() -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main( + new String[]{evidenceFile.toString()} + )); + } + + @Test + void bridgeDumpsLoopVectorizationInvalidatedTypedBodyRebuildAsBlockedEvidence() throws Exception { + IrGpuArtifact original = loopVectorizationInvalidatedArtifact(); + GpuRuntimeCompileRequest compileRequest = request(original); + GpuRuntimeIrOptimizationReport optimizationReport = new GpuIrProposalRuntimeBridgePass( + List.of(new GpuIrLoopVectorizationMaterializationProposalProvider()), + GpuIrOptimizationSandwichRunner.alwaysValid(), + false + ).run(new GpuRuntimeIrOptimizationRequest(compileRequest, Optional.of(original))); + + assertSame(original, optimizationReport.artifact().orElseThrow()); + IrGpuArtifact optimized = optimizationReport.candidateArtifact().orElseThrow(); + assertTrue(optimized.module().methodBodies().get(0).body() + .contains("intrinsic(vload4 template=\"\" args=[0, (&input[(id * 4)])])")); + assertFalse(optimized.module().methodBodies().get(0).typedBody().available()); + Map fields = optimizationReport.passReports().get(0).proofArtifact().fields(); + assertEquals("invalidated", fields.get("typedBody.rebuild.status")); + assertEquals("typed-body-rebuild-statement-conversion-blocked", fields.get("typedBody.rebuild.firstBlocker")); + + String evidence = runtimeOptimizerEvidence(original, optimizationReport); + + assertTrue(evidence.contains("loopVectorizationMaterialization.transformedLoop.count=1")); + assertTrue(evidence.contains("loopVectorizationMaterialization.bodyTextReplacement.count=1")); + assertTrue(evidence.contains("loopVectorizationMaterialization.typedBody.materialized.count=0")); + assertTrue(evidence.contains("loopVectorizationMaterialization.typedBody.invalidated.count=1")); + assertTrue(evidence.contains("loopVectorizationMaterialization.typedBody.rebuild.attempted.count=1")); + assertTrue(evidence.contains("loopVectorizationMaterialization.typedBody.rebuild.parsed.count=1")); + assertTrue(evidence.contains("loopVectorizationMaterialization.typedBody.rebuild.built.count=0")); + assertTrue(evidence.contains("loopVectorizationMaterialization.typedBody.rebuild.graphValidated.count=0")); + assertTrue(evidence.contains("loopVectorizationMaterialization.typedBody.rebuild.rejected.count=1")); + assertTrue(evidence.contains("loopVectorizationMaterialization.typedBody.rebuild.status=invalidated")); + assertTrue(evidence.contains( + "loopVectorizationMaterialization.typedBody.rebuild.firstBlocker=" + + "typed-body-rebuild-statement-conversion-blocked" + )); + assertTrue(evidence.contains("loopVectorizationMaterialization.runtimeEquivalencePayloadPresent.count=1")); + assertTrue(evidence.contains("loopVectorizationMaterialization.runtimeEquivalencePassed.count=1")); + assertTrue(evidence.contains("loopVectorizationMaterialization.status=blocked")); + assertTrue(evidence.contains( + "loopVectorizationMaterialization.firstBlocker=typed-body-rebuild-statement-conversion-blocked" + )); + assertTrue(evidence.contains("runtimeEquivalenceReview.status=blocked")); + assertTrue(evidence.contains( + "runtimeEquivalenceReview.firstBlocker=typed-body-rebuild-statement-conversion-blocked" + )); + assertTrue(evidence.contains("loop-vectorization-materialization=blocked")); + assertTrue(evidence.contains("optimizedArtifactCandidate.selectionApplied=false")); + assertTrue(evidence.contains("optimizedArtifactCandidate.selectedIrReplacement=false")); + + Path evidenceFile = temporaryDirectory.resolve( + GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT + ); + Files.writeString(evidenceFile, evidence, StandardCharsets.UTF_8); + assertDoesNotThrow(() -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main( + new String[]{evidenceFile.toString()} + )); + } + @Test void bridgeCanSelectOptimizedArtifactWhenExplicitlyAllowed() { IrGpuArtifact original = artifact("body \r\n return original\t\r\n"); @@ -604,13 +757,17 @@ void bridgeCanSelectOptimizedArtifactWhenExplicitlyAllowed() { assertEquals(GpuRuntimeIrOptimizationOutcome.APPLIED, report.passReports().get(0).outcome()); assertEquals("optimized-selected", report.passReports().get(0).proofStatus()); assertEquals( - "selection-gate-not-bound", + "none", report.passReports().get(0).proofArtifact().fields().get("optimizedArtifactCandidate.selectionFirstBlocker") ); assertEquals( - "false", + "true", report.passReports().get(0).proofArtifact().fields().get("optimizedArtifactCandidate.selectionApplied") ); + assertEquals( + "true", + report.passReports().get(0).proofArtifact().fields().get("optimizedArtifactCandidate.selectedIrReplacement") + ); assertFalse(report.requiresRollback()); } @@ -659,10 +816,17 @@ void runtimeRegistrySeesBridgeButKeepsOriginalIrByDefault() { } private static GpuRuntimeCompileRequest request(IrGpuArtifact artifact) { + return request(artifact, GpuRuntimeCompileOptions.openCl(List.of(), "diagnostic")); + } + + private static GpuRuntimeCompileRequest request( + IrGpuArtifact artifact, + GpuRuntimeCompileOptions compileOptions + ) { GpuRuntimeDeviceProfile profile = GpuRuntimeDeviceProfile.generic(GpuBackendTarget.OPENCL, "test-device"); return new GpuRuntimeCompileRequest( new GpuKernelDescriptor("run", "test.Kernel.run", "", "test.Kernel.run.irgpu", List.of()), - GpuRuntimeCompileOptions.openCl(List.of(), "diagnostic"), + compileOptions, profile, Optional.of(artifact) ); @@ -671,24 +835,68 @@ private static GpuRuntimeCompileRequest request(IrGpuArtifact artifact) { private static String runtimeOptimizerEvidence( IrGpuArtifact original, GpuRuntimeIrOptimizationReport optimizationReport + ) { + return runtimeOptimizerDump(original, optimizationReport) + .artifact(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + } + + private static boolean serializedNamedValuePresent( + String propertiesText, + String keyScope, + String name, + String value + ) { + Map namesByPrefix = new HashMap<>(); + Map valuesByPrefix = new HashMap<>(); + for (String line : propertiesText.split("\\R")) { + int separator = line.indexOf('='); + if (separator < 0) { + continue; + } + String key = line.substring(0, separator); + if (!key.contains(keyScope)) { + continue; + } + String serializedValue = line.substring(separator + 1); + if (key.endsWith(".name")) { + namesByPrefix.put(key.substring(0, key.length() - ".name".length()), serializedValue); + } else if (key.endsWith(".value")) { + valuesByPrefix.put(key.substring(0, key.length() - ".value".length()), serializedValue); + } + } + return namesByPrefix.entrySet().stream().anyMatch(entry -> + name.equals(entry.getValue()) && value.equals(valuesByPrefix.get(entry.getKey())) + ); + } + + private static GpuRuntimeCompileArtifactDump runtimeOptimizerDump( + IrGpuArtifact original, + GpuRuntimeIrOptimizationReport optimizationReport ) { GpuRuntimeCompileRequest compileRequest = request(original); + GpuRuntimeCompileRequest optimizedRequest = optimizationReport.candidateArtifact() + .map(GpuIrProposalRuntimeBridgePassTest::request) + .orElse(compileRequest); GpuBackendModuleArtifact backendArtifact = GpuBackendModuleArtifact.openClSource( "__kernel void run(__global int* out) { out[0] = 1; }", "runtime/lowered/run.cl", "test-lowerer-v1" ); - GpuRuntimeCompileArtifactDump dump = GpuRuntimeCompileArtifactDumper.dump( + GpuBackendModuleArtifact optimizedBackendArtifact = GpuBackendModuleArtifact.openClSource( + "__kernel void run(__global const float* input, __global float* output) { vload4(0, input); }", + "runtime/lowered/run.optimized.cl", + "test-lowerer-v1" + ); + return GpuRuntimeCompileArtifactDumper.dump( GpuRuntimeCompileArtifactSnapshot.from( compileRequest, - compileRequest, + optimizedRequest, backendArtifact, GpuRuntimeCompileInvalidationStamp.from(compileRequest, backendArtifact, "policy-gate"), GpuRuntimeCompileProvenance.from(compileRequest), optimizationReport - ) + ).withBackendStageModuleArtifacts(backendArtifact, optimizedBackendArtifact) ); - return dump.artifact(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); } private static GpuIrOptimizationProposalRequest proposalRequest( @@ -910,6 +1118,63 @@ private static IrGpuArtifact chainedMaterializationArtifact() { ); } + private static IrGpuArtifact loopVectorizationValidatedArtifact() { + return loopVectorizationArtifact(""" + body + var int id = intrinsic(get_global_id template="" args=[0]) + var float sum = 0.0F + for init=(var int i = 0) cond=(i < 4) update=(set i = (i + 1)) + set sum = (sum + input[((id * 4) + i)]) + set output[id] = sum + """); + } + + private static IrGpuArtifact loopVectorizationInvalidatedArtifact() { + return loopVectorizationArtifact(""" + body + var int id = intrinsic(get_global_id template="" args=[0]) + var float sum = 0.0F + for init=(var int i = 0) cond=(i < 4) update=(set i = (i + 1)) + set sum = (sum + input[((id * 4) + i)]) + set output[id] = (flag ? sum : 0.0F) + """); + } + + private static IrGpuArtifact loopVectorizationArtifact(String body) { + IrGpuMethodBody methodBody = new IrGpuMethodBody( + "entry", + "kernel", + "kernel", + "ir-text-v1", + body, + new IrGpuTypedBody( + IrGpuTypedBody.FORMAT, + List.of(0), + List.of(new IrGpuTypedNode(0, "GpuIrVariableRef", Map.of("name", "sum"), Map.of())) + ), + IrGpuBodyIndex.empty(), + List.of(), + IrGpuSourceLocation.unknown("kernel") + ); + IrGpuModule module = new IrGpuModule("kernel", "kernel", List.of(), List.of(), List.of(methodBody)); + return new IrGpuArtifact( + IrGpuArtifactHeader.javaSourceV1(), + module, + List.of( + new IrGpuEntryParameter("output", "float[]", "GLOBAL", false, List.of()), + new IrGpuEntryParameter("input", "float[]", "GLOBAL", false, List.of()) + ), + IrGpuLaunchMetadata.defaultOneDimensional(), + IrGpuValidationMetadata.frontendSubset(), + IrGpuFeatureMetadata.none(), + IrGpuOptimizerPolicyMetadata.defaultStrict(), + IrGpuRegenerationMetadata.transitionalIrText(), + List.of(IrGpuBackendOutput.openClSource("javatogpu/test/Kernel/kernel.cl")), + "opencl", + "off" + ); + } + private static IrGpuArtifact madFmaArtifact() { IrGpuTypedBody typedBody = new IrGpuTypedBody( IrGpuTypedBody.FORMAT, diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrSafeLocalCseMaterializationProposalProviderTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrSafeLocalCseMaterializationProposalProviderTest.java index a8759dbb..92e05c4a 100644 --- a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrSafeLocalCseMaterializationProposalProviderTest.java +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrSafeLocalCseMaterializationProposalProviderTest.java @@ -69,6 +69,7 @@ void materializesRepeatedPureExpressionAsExistingLocalReference() { assertEquals("safe-local-cse-reuse-existing-local", fields.get("runtimeEquivalencePayload.Case.0.RewriteKind")); assertEquals("(a + b)", fields.get("runtimeEquivalencePayload.Case.0.Output.0.PreOptimization")); assertEquals("tmp", fields.get("runtimeEquivalencePayload.Case.0.Output.0.PostOptimization")); + assertEquals("0", fields.get("introducedTemporary.count")); assertEquals("true", fields.get("safety.dominanceProven")); assertEquals("true", fields.get("safety.sideEffectFreedomProven")); assertEquals("false", fields.get("safety.newTemporaryIntroduced")); @@ -127,6 +128,84 @@ void materializesRepeatedPureExpressionsToFixedPoint() { assertEquals("safe-local-cse-reuse-existing-local", fields.get("runtimeEquivalencePayload.Case.1.RewriteKind")); } + @Test + void introducesLocalTemporaryForRepeatedFloatExpressionWithoutExistingBinding() { + IrGpuArtifact original = floatArtifact( + "body\n" + + " var float coordX = ((value * 1.414F) + 12.0F)\n" + + " var float coordY = ((value * 1.414F) - 7.5F)\n" + + " var float coordZ = ((value * 1.414F) * staticMath)\n", + typedBody( + List.of(0, 6, 12), + declaration(0, "float", "coordX", 1), + binary(1, "+", 2, 5), + binary(2, "*", 3, 4), + variable(3, "value"), + literal(4, "1.414F"), + literal(5, "12.0F"), + declaration(6, "float", "coordY", 7), + binary(7, "-", 8, 11), + binary(8, "*", 9, 10), + variable(9, "value"), + literal(10, "1.414F"), + literal(11, "7.5F"), + declaration(12, "float", "coordZ", 13), + binary(13, "*", 14, 17), + binary(14, "*", 15, 16), + variable(15, "value"), + literal(16, "1.414F"), + variable(17, "staticMath") + ) + ); + + GpuIrOptimizationProposal proposal = new GpuIrSafeLocalCseMaterializationProposalProvider() + .propose(new GpuIrOptimizationProposalRequest( + original, + "diagnostic", + false, + Map.of("backendTarget", "OPENCL") + )); + + assertEquals(GpuIrOptimizationProposalDecision.PROPOSED, proposal.decision()); + IrGpuArtifact optimized = proposal.optimizedArtifact().orElseThrow(); + assertEquals( + "body\n" + + " var float jtg_cse0 = (value * 1.414F)\n" + + " var float coordX = (jtg_cse0 + 12.0F)\n" + + " var float coordY = (jtg_cse0 - 7.5F)\n" + + " var float coordZ = (jtg_cse0 * staticMath)\n", + optimized.module().methodBodies().get(0).body() + ); + IrGpuTypedBody optimizedTypedBody = optimized.module().methodBodies().get(0).typedBody(); + assertEquals(List.of(18, 0, 6, 12), optimizedTypedBody.rootNodeIds()); + IrGpuTypedNode temporaryDeclaration = optimizedTypedBody.nodes().stream() + .filter(node -> node.id() == 18) + .findFirst() + .orElseThrow(); + assertEquals("GpuIrVariableDeclaration", temporaryDeclaration.kind()); + assertEquals("float", temporaryDeclaration.attributes().get("typeName")); + assertEquals("jtg_cse0", temporaryDeclaration.attributes().get("name")); + assertTrue(optimizedTypedBody.nodes().stream().filter(node -> List.of(2, 8, 14).contains(node.id())).allMatch(node -> + "GpuIrVariableRef".equals(node.kind()) && "jtg_cse0".equals(node.attributes().get("name")) + )); + + Map fields = proposal.proofArtifact().fields(); + assertEquals("3", fields.get("localBinding.count")); + assertEquals("1", fields.get("introducedTemporary.count")); + assertEquals("3", fields.get("candidate.count")); + assertEquals("3", fields.get("transformedNode.count")); + assertEquals("3", fields.get("bodyTextReplacement.count")); + assertEquals("1", fields.get("fixedPoint.pass.count")); + assertEquals("true", fields.get("safety.newTemporaryIntroduced")); + assertEquals("static-local-expression-reuse-with-introduced-temporary", fields.get("runtimeEquivalencePayload.ReferenceMode")); + assertEquals("3", fields.get("runtimeEquivalencePayload.Case.Count")); + assertEquals("safe-local-cse-introduce-local", fields.get("runtimeEquivalencePayload.Case.0.RewriteKind")); + assertEquals("introducedLocal", fields.get("runtimeEquivalencePayload.Case.0.Input.1.Name")); + assertEquals("(value * 1.414F)", fields.get("firstExpression")); + assertEquals("jtg_cse0", fields.get("firstReplacement")); + assertTrue(optimized.regenerationMetadata().backendNeutralSourceReady()); + } + @Test void doesNotMaterializeWithoutExistingLocalBinding() { IrGpuArtifact original = artifact( @@ -204,10 +283,7 @@ void materializationProviderIsServiceLoaded() { private static IrGpuTypedBody repeatedExpressionTypedBody() { return typedBody( List.of(0, 4), - new IrGpuTypedNode(0, "GpuIrVariableDeclaration", Map.of( - "typeName", "int", - "name", "tmp" - ), Map.of("initializer", List.of(1))), + declaration(0, "int", "tmp", 1), binary(1, "+", 2, 3), variable(2, "a"), variable(3, "b"), @@ -219,6 +295,13 @@ private static IrGpuTypedBody repeatedExpressionTypedBody() { ); } + private static IrGpuTypedNode declaration(int id, String typeName, String name, int initializerId) { + return new IrGpuTypedNode(id, "GpuIrVariableDeclaration", Map.of( + "typeName", typeName, + "name", name + ), Map.of("initializer", List.of(initializerId))); + } + private static IrGpuTypedNode assignment(int id, int targetId, int valueId) { return new IrGpuTypedNode(id, "GpuIrAssignment", Map.of(), Map.of( "target", List.of(targetId), @@ -237,11 +320,30 @@ private static IrGpuTypedNode variable(int id, String name) { return new IrGpuTypedNode(id, "GpuIrVariableRef", Map.of("name", name), Map.of()); } + private static IrGpuTypedNode literal(int id, String sourceText) { + return new IrGpuTypedNode(id, "GpuIrLiteral", Map.of("sourceText", sourceText), Map.of()); + } + private static IrGpuTypedBody typedBody(List rootNodeIds, IrGpuTypedNode... nodes) { return new IrGpuTypedBody(IrGpuTypedBody.FORMAT, rootNodeIds, List.of(nodes)); } private static IrGpuArtifact artifact(String body, IrGpuTypedBody typedBody) { + return artifact(body, typedBody, List.of( + new IrGpuEntryParameter("output", "int[]", "GLOBAL", false, List.of()), + new IrGpuEntryParameter("a", "int", "PRIVATE", false, List.of()), + new IrGpuEntryParameter("b", "int", "PRIVATE", false, List.of()) + )); + } + + private static IrGpuArtifact floatArtifact(String body, IrGpuTypedBody typedBody) { + return artifact(body, typedBody, List.of( + new IrGpuEntryParameter("value", "float", "PRIVATE", false, List.of()), + new IrGpuEntryParameter("staticMath", "float", "PRIVATE", false, List.of()) + )); + } + + private static IrGpuArtifact artifact(String body, IrGpuTypedBody typedBody, List parameters) { IrGpuMethodBody methodBody = new IrGpuMethodBody( "entry", "kernel", @@ -254,13 +356,9 @@ private static IrGpuArtifact artifact(String body, IrGpuTypedBody typedBody) { IrGpuSourceLocation.unknown("kernel") ); return new IrGpuArtifact( - IrGpuArtifactHeader.javaSourceV1(), - new IrGpuModule("kernel", "kernel", List.of(), List.of(), List.of(methodBody)), - List.of( - new IrGpuEntryParameter("output", "int[]", "GLOBAL", false, List.of()), - new IrGpuEntryParameter("a", "int", "PRIVATE", false, List.of()), - new IrGpuEntryParameter("b", "int", "PRIVATE", false, List.of()) - ), + IrGpuArtifactHeader.javaSourceV1(), + new IrGpuModule("kernel", "kernel", List.of(), List.of(), List.of(methodBody)), + parameters, List.of(IrGpuBackendOutput.openClSource("javatogpu/test/Kernel/kernel.cl")), "opencl", "off" diff --git a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedBodyGraphPatchTest.java b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedBodyGraphPatchTest.java index 685aee49..0b673b8a 100644 --- a/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedBodyGraphPatchTest.java +++ b/ir-optimizer/src/test/java/net/sixik/ga_utils/javatogpu/iroptimizer/GpuIrTypedBodyGraphPatchTest.java @@ -56,6 +56,27 @@ void replacesRootNodeAndAppendsAuxiliaryNodeWithoutChangingRoots() { assertEquals("1.0F", GpuIrTypedBodyGraphPatch.nodesById(patched).get(100).attributes().get("sourceText")); } + @Test + void reportsMissingRootsAndChildReferencesDuringReachability() { + IrGpuTypedBody body = new IrGpuTypedBody( + IrGpuTypedBody.FORMAT, + List.of(0, 404), + List.of( + new IrGpuTypedNode(0, "GpuIrAssignment", Map.of(), Map.of("value", List.of(1, 405))), + new IrGpuTypedNode(1, "GpuIrLiteral", Map.of("sourceText", "1"), Map.of()) + ) + ); + + GpuIrTypedBodyGraphPatch.Reachability reachability = GpuIrTypedBodyGraphPatch + .reachability(body, GpuIrTypedBodyGraphPatch.nodesById(body)); + + assertEquals(1, reachability.rootMissingCount()); + assertEquals(1, reachability.missingChildReferenceCount()); + assertTrue(reachability.reachableNodeIds().contains(0)); + assertTrue(reachability.reachableNodeIds().contains(1)); + assertFalse(reachability.reachableNodeIds().contains(404)); + } + @Test void keepsOriginalBodyWhenReplacementRootWasNotPresent() { IrGpuTypedBody body = typedBody(); @@ -96,6 +117,22 @@ void appliesTextAndTypedBodyPatchAsSinglePlan() { assertEquals("mad", GpuIrTypedBodyGraphPatch.nodesById(applied.typedBody()).get(2).attributes().get("name")); } + @Test + void appliesTextPatchAfterSearchStartIndex() { + IrGpuTypedBody body = typedBody(); + IrGpuTypedNode replacement = new IrGpuTypedNode(2, "GpuIrVariableRef", Map.of("name", "tmp"), Map.of()); + String text = "body\n var int tmp = (out[0] + 2)\n set tmp2 = (out[0] + 2)\n"; + + GpuIrTypedBodyGraphPatch.Applied applied = GpuIrTypedBodyGraphPatch + .plan("(out[0] + 2)", "tmp", replacement, text.indexOf("set tmp2")) + .apply(body, text); + + assertTrue(applied.applied()); + assertEquals("body\n var int tmp = (out[0] + 2)\n set tmp2 = tmp\n", applied.body()); + assertEquals("GpuIrVariableRef", GpuIrTypedBodyGraphPatch.nodesById(applied.typedBody()).get(2).kind()); + assertEquals("tmp", GpuIrTypedBodyGraphPatch.nodesById(applied.typedBody()).get(2).attributes().get("name")); + } + @Test void blocksPlanWhenTextPatternIsMissingWithoutChangingTypedBody() { IrGpuTypedBody body = typedBody(); diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendCompileOptions.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendCompileOptions.java index 0eb0b5af..6b8bf3bb 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendCompileOptions.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuBackendCompileOptions.java @@ -22,6 +22,9 @@ public record GpuBackendCompileOptions( public static final String OPENCL_PRODUCTION_SOURCE_SWITCHING_PROPERTY = "opencl.productionSourceSwitching"; public static final String OPENCL_PRODUCTION_SOURCE_SWITCHING_DISABLED = "disabled"; public static final String OPENCL_PRODUCTION_SOURCE_SWITCHING_ENABLED = "enabled"; + public static final String RUNTIME_IR_OPTIMIZER_SELECTION_PROPERTY = "runtime.irOptimizerSelection"; + public static final String RUNTIME_IR_OPTIMIZER_SELECTION_REVIEW_ONLY = "review-only"; + public static final String RUNTIME_IR_OPTIMIZER_SELECTION_EXPERIMENTAL_APPLY = "experimental-apply"; public static final String PRODUCTION_PROMOTION_DECISION_MODE_PROPERTY = "productionPromotion.decisionMode"; public static final String PRODUCTION_PROMOTION_OPERATOR_ACCEPTED_PROPERTY = "productionPromotion.operatorAccepted"; public static final String RUNTIME_DEVICE_SELF_TEST_PROPERTY = "runtime.deviceSelfTest"; @@ -91,6 +94,29 @@ public boolean enablesOpenClProductionSourceSwitching() { ); } + public boolean requestsRuntimeIrOptimizerExperimentalApply() { + return RUNTIME_IR_OPTIMIZER_SELECTION_EXPERIMENTAL_APPLY.equals( + properties.get(RUNTIME_IR_OPTIMIZER_SELECTION_PROPERTY) + ); + } + + public GpuBackendCompileOptions withRuntimeIrOptimizerSelection(String selection) { + String normalizedSelection = selection == null || selection.isBlank() + ? RUNTIME_IR_OPTIMIZER_SELECTION_REVIEW_ONLY + : selection; + Map updated = new LinkedHashMap<>(properties); + if (RUNTIME_IR_OPTIMIZER_SELECTION_REVIEW_ONLY.equals(normalizedSelection)) { + updated.remove(RUNTIME_IR_OPTIMIZER_SELECTION_PROPERTY); + } else { + updated.put(RUNTIME_IR_OPTIMIZER_SELECTION_PROPERTY, normalizedSelection); + } + return new GpuBackendCompileOptions(backendTarget, flags, updated); + } + + public GpuBackendCompileOptions withRuntimeIrOptimizerExperimentalApply() { + return withRuntimeIrOptimizerSelection(RUNTIME_IR_OPTIMIZER_SELECTION_EXPERIMENTAL_APPLY); + } + public String productionPromotionDecisionMode() { return properties.getOrDefault( PRODUCTION_PROMOTION_DECISION_MODE_PROPERTY, diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java index e8ce2cf2..da126a70 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumper.java @@ -744,6 +744,12 @@ private static String formatRuntimeIrOptimizerEvidence(GpuRuntimeIrOptimizationR builder.append("failed.count=").append(countOutcome(irOptimizerReports, GpuRuntimeIrOptimizationOutcome.FAILED)).append('\n'); builder.append("proposalOnly.count=").append(countProofStatus(irOptimizerReports, "proposal-only")).append('\n'); builder.append("selectedOptimized.count=").append(countProofStatus(irOptimizerReports, "optimized-selected")).append('\n'); + ExperimentalApplyEvidence experimentalApply = experimentalApplyEvidence(irOptimizerReports); + builder.append("experimentalApply.requested=").append(experimentalApply.requested()).append('\n'); + builder.append("experimentalApply.enabled=").append(experimentalApply.enabled()).append('\n'); + builder.append("experimentalApply.requested.count=").append(experimentalApply.requestedCount()).append('\n'); + builder.append("experimentalApply.enabled.count=").append(experimentalApply.enabledCount()).append('\n'); + builder.append("experimentalApply.selected.count=").append(experimentalApply.selectedCount()).append('\n'); builder.append("approvalTemplate.pending.count=") .append(countApprovalTemplateStatus(irOptimizerReports, "pending")) .append('\n'); @@ -903,6 +909,8 @@ private static String formatRuntimeIrOptimizerEvidence(GpuRuntimeIrOptimizationR .append(safeLocalCseMaterialization.passCount()).append('\n'); builder.append("safeLocalCseMaterialization.localBinding.count=") .append(safeLocalCseMaterialization.localBindingCount()).append('\n'); + builder.append("safeLocalCseMaterialization.introducedTemporary.count=") + .append(safeLocalCseMaterialization.introducedTemporaryCount()).append('\n'); builder.append("safeLocalCseMaterialization.candidate.count=") .append(safeLocalCseMaterialization.candidateCount()).append('\n'); builder.append("safeLocalCseMaterialization.transformedNode.count=") @@ -1039,6 +1047,20 @@ private static String formatRuntimeIrOptimizerEvidence(GpuRuntimeIrOptimizationR .append(loopVectorizationMaterialization.typedBodyMaterializedCount()).append('\n'); builder.append("loopVectorizationMaterialization.typedBody.invalidated.count=") .append(loopVectorizationMaterialization.typedBodyInvalidatedCount()).append('\n'); + builder.append("loopVectorizationMaterialization.typedBody.rebuild.attempted.count=") + .append(loopVectorizationMaterialization.typedBodyRebuildAttemptedCount()).append('\n'); + builder.append("loopVectorizationMaterialization.typedBody.rebuild.parsed.count=") + .append(loopVectorizationMaterialization.typedBodyRebuildParsedCount()).append('\n'); + builder.append("loopVectorizationMaterialization.typedBody.rebuild.built.count=") + .append(loopVectorizationMaterialization.typedBodyRebuildBuiltCount()).append('\n'); + builder.append("loopVectorizationMaterialization.typedBody.rebuild.graphValidated.count=") + .append(loopVectorizationMaterialization.typedBodyRebuildGraphValidatedCount()).append('\n'); + builder.append("loopVectorizationMaterialization.typedBody.rebuild.rejected.count=") + .append(loopVectorizationMaterialization.typedBodyRebuildRejectedCount()).append('\n'); + builder.append("loopVectorizationMaterialization.typedBody.rebuild.status=") + .append(loopVectorizationMaterialization.typedBodyRebuildStatus()).append('\n'); + builder.append("loopVectorizationMaterialization.typedBody.rebuild.firstBlocker=") + .append(safePropertyValue(loopVectorizationMaterialization.typedBodyRebuildFirstBlocker())).append('\n'); builder.append("loopVectorizationMaterialization.skipped.loopShape.count=") .append(loopVectorizationMaterialization.skippedLoopShapeCount()).append('\n'); builder.append("loopVectorizationMaterialization.skipped.unsupportedWidth.count=") @@ -1451,6 +1473,37 @@ private static OptimizedArtifactCandidateEvidence optimizedArtifactCandidateEvid ); } + private static ExperimentalApplyEvidence experimentalApplyEvidence( + List passReports + ) { + int requestedCount = 0; + int enabledCount = 0; + int selectedCount = 0; + for (GpuRuntimeIrOptimizationPassReport passReport : passReports) { + if (passReport == null || passReport.proofArtifact() == null) { + continue; + } + Map fields = passReport.proofArtifact().fields(); + if (parseBoolean(fields.get("experimentalApply.requested"))) { + requestedCount++; + } + if (parseBoolean(fields.get("experimentalApply.enabled"))) { + enabledCount++; + } + if (parseBoolean(fields.get("experimentalApply.selected")) + || "optimized-selected".equals(passReport.proofStatus())) { + selectedCount++; + } + } + return new ExperimentalApplyEvidence( + requestedCount > 0, + enabledCount > 0, + requestedCount, + enabledCount, + selectedCount + ); + } + private static boolean hasOptimizedArtifactCandidate(Map fields) { return fields != null && fields.keySet().stream() .anyMatch(key -> key.startsWith("optimizedArtifactCandidate.")); @@ -1828,6 +1881,7 @@ private static SafeLocalCseMaterializationEvidence safeLocalCseMaterializationEv ) { int passCount = 0; int localBindingCount = 0; + int introducedTemporaryCount = 0; int candidateCount = 0; int transformedNodeCount = 0; int changedMethodBodyCount = 0; @@ -1858,6 +1912,7 @@ private static SafeLocalCseMaterializationEvidence safeLocalCseMaterializationEv } passCount++; localBindingCount += parseNonNegativeInt(fields.get("localBinding.count")); + introducedTemporaryCount += parseNonNegativeInt(fields.get("introducedTemporary.count")); candidateCount += parseNonNegativeInt(fields.get("candidate.count")); transformedNodeCount += parseNonNegativeInt(fields.get("transformedNode.count")); changedMethodBodyCount += parseNonNegativeInt(fields.get("changedMethodBody.count")); @@ -1927,6 +1982,7 @@ private static SafeLocalCseMaterializationEvidence safeLocalCseMaterializationEv return new SafeLocalCseMaterializationEvidence( passCount, localBindingCount, + introducedTemporaryCount, candidateCount, transformedNodeCount, changedMethodBodyCount, @@ -2451,6 +2507,11 @@ private static LoopVectorizationMaterializationEvidence loopVectorizationMateria int bodyTextReplacementCount = 0; int typedBodyMaterializedCount = 0; int typedBodyInvalidatedCount = 0; + int typedBodyRebuildAttemptedCount = 0; + int typedBodyRebuildParsedCount = 0; + int typedBodyRebuildBuiltCount = 0; + int typedBodyRebuildGraphValidatedCount = 0; + int typedBodyRebuildRejectedCount = 0; int skippedLoopShapeCount = 0; int skippedUnsupportedWidthCount = 0; int skippedUnsafeLoadPatternCount = 0; @@ -2481,6 +2542,13 @@ private static LoopVectorizationMaterializationEvidence loopVectorizationMateria bodyTextReplacementCount += parseNonNegativeInt(fields.get("bodyTextReplacement.count")); typedBodyMaterializedCount += parseNonNegativeInt(fields.get("typedBody.materialized.count")); typedBodyInvalidatedCount += parseNonNegativeInt(fields.get("typedBody.invalidated.count")); + typedBodyRebuildAttemptedCount += parseNonNegativeInt(fields.get("typedBody.rebuild.attempted.count")); + typedBodyRebuildParsedCount += parseNonNegativeInt(fields.get("typedBody.rebuild.parsed.count")); + typedBodyRebuildBuiltCount += parseNonNegativeInt(fields.get("typedBody.rebuild.built.count")); + typedBodyRebuildGraphValidatedCount += parseNonNegativeInt( + fields.get("typedBody.rebuild.graphValidated.count") + ); + typedBodyRebuildRejectedCount += parseNonNegativeInt(fields.get("typedBody.rebuild.rejected.count")); skippedLoopShapeCount += parseNonNegativeInt(fields.get("skipped.loopShape.count")); skippedUnsupportedWidthCount += parseNonNegativeInt(fields.get("skipped.unsupportedWidth.count")); skippedUnsafeLoadPatternCount += parseNonNegativeInt(fields.get("skipped.unsafeLoadPattern.count")); @@ -2510,6 +2578,22 @@ private static LoopVectorizationMaterializationEvidence loopVectorizationMateria } } + String typedBodyRebuildStatus; + String typedBodyRebuildFirstBlocker; + if (typedBodyRebuildAttemptedCount <= 0) { + typedBodyRebuildStatus = "not-attempted"; + typedBodyRebuildFirstBlocker = "not-attempted"; + } else if (typedBodyRebuildRejectedCount <= 0) { + typedBodyRebuildStatus = "validated"; + typedBodyRebuildFirstBlocker = "none"; + } else if (typedBodyRebuildGraphValidatedCount > 0) { + typedBodyRebuildStatus = "partially-validated"; + typedBodyRebuildFirstBlocker = firstLoopVectorizationTypedBodyRebuildBlocker(passReports); + } else { + typedBodyRebuildStatus = "invalidated"; + typedBodyRebuildFirstBlocker = firstLoopVectorizationTypedBodyRebuildBlocker(passReports); + } + int blockerCount = skippedLoopShapeCount + skippedUnsupportedWidthCount + skippedUnsafeLoadPatternCount; String status; if (passCount <= 0) { @@ -2527,6 +2611,13 @@ private static LoopVectorizationMaterializationEvidence loopVectorizationMateria } else if (transformedLoopCount <= 0) { status = "no-candidates"; firstBlocker = "no-materialized-candidates"; + } else if (typedBodyRebuildRejectedCount > 0 || typedBodyInvalidatedCount > 0) { + status = "blocked"; + firstBlocker = typedBodyRebuildFirstBlocker == null + || typedBodyRebuildFirstBlocker.isBlank() + || "none".equals(typedBodyRebuildFirstBlocker) + ? "typed-body-rebuild-blocked" + : typedBodyRebuildFirstBlocker; } else if (!loopTripCountProven || !contiguousLoadProven || !orderedReductionPreserved) { status = "blocked"; firstBlocker = "loop-vectorization-proof-incomplete"; @@ -2548,6 +2639,13 @@ private static LoopVectorizationMaterializationEvidence loopVectorizationMateria bodyTextReplacementCount, typedBodyMaterializedCount, typedBodyInvalidatedCount, + typedBodyRebuildAttemptedCount, + typedBodyRebuildParsedCount, + typedBodyRebuildBuiltCount, + typedBodyRebuildGraphValidatedCount, + typedBodyRebuildRejectedCount, + typedBodyRebuildStatus, + typedBodyRebuildFirstBlocker, skippedLoopShapeCount, skippedUnsupportedWidthCount, skippedUnsafeLoadPatternCount, @@ -2581,6 +2679,28 @@ private static String loopVectorizationMaterializationFirstBlocker( return "loop-vectorization-materialization-blocked"; } + private static String firstLoopVectorizationTypedBodyRebuildBlocker( + List passReports + ) { + for (GpuRuntimeIrOptimizationPassReport passReport : passReports) { + if (passReport == null || passReport.proofArtifact() == null) { + continue; + } + Map fields = passReport.proofArtifact().fields(); + String source = passReport.proofArtifact().source() == null ? "" : passReport.proofArtifact().source(); + String optimizerVersion = passReport.optimizerVersion() == null ? "" : passReport.optimizerVersion(); + if (!source.contains("loop-vectorization-materialization") + && !optimizerVersion.contains("loop-vectorization-materialization")) { + continue; + } + String blocker = fields.getOrDefault("typedBody.rebuild.firstBlocker", ""); + if (blocker != null && !blocker.isBlank() && !"none".equals(blocker)) { + return blocker; + } + } + return "typed-body-rebuild-blocked"; + } + private static SafeLocalCsePreviewEvidence safeLocalCsePreviewEvidence( List passReports ) { @@ -3098,6 +3218,7 @@ private record TypedDeadCodeMaterializationEvidence( private record SafeLocalCseMaterializationEvidence( int passCount, int localBindingCount, + int introducedTemporaryCount, int candidateCount, int transformedNodeCount, int changedMethodBodyCount, @@ -3184,6 +3305,13 @@ private record LoopVectorizationMaterializationEvidence( int bodyTextReplacementCount, int typedBodyMaterializedCount, int typedBodyInvalidatedCount, + int typedBodyRebuildAttemptedCount, + int typedBodyRebuildParsedCount, + int typedBodyRebuildBuiltCount, + int typedBodyRebuildGraphValidatedCount, + int typedBodyRebuildRejectedCount, + String typedBodyRebuildStatus, + String typedBodyRebuildFirstBlocker, int skippedLoopShapeCount, int skippedUnsupportedWidthCount, int skippedUnsafeLoadPatternCount, @@ -3346,6 +3474,15 @@ private boolean selectedIrReplacement() { } } + private record ExperimentalApplyEvidence( + boolean requested, + boolean enabled, + int requestedCount, + int enabledCount, + int selectedCount + ) { + } + private static ApprovalTemplateEvidence approvalTemplateEvidence(GpuRuntimeIrOptimizationPassReport passReport) { if (passReport == null) { return ApprovalTemplateEvidence.notApplicable("pass-report-missing", Map.of()); diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileOptions.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileOptions.java index 709a7c44..544cd39f 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileOptions.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileOptions.java @@ -14,6 +14,7 @@ public record GpuRuntimeCompileOptions( ) { public static final String OPENCL_IRGPU_SOURCE_REVIEW_PROFILE = "source-reconstruction-review"; + public static final String IR_OPTIMIZER_EXPERIMENTAL_APPLY_PROFILE = "ir-optimizer-experimental-apply"; public GpuRuntimeCompileOptions( GpuBackendTarget backendTarget, @@ -95,6 +96,32 @@ public static GpuRuntimeCompileOptions openClProductionIrGpuSource( ); } + public static GpuRuntimeCompileOptions openClIrOptimizerExperimentalApply( + List compileArgs, + String optimizationProfile + ) { + return new GpuRuntimeCompileOptions( + GpuBackendTarget.OPENCL, + compileArgs, + optimizationProfile, + GpuBackendCompileOptions.openCl(compileArgs).withRuntimeIrOptimizerExperimentalApply() + ); + } + + public static GpuRuntimeCompileOptions openClIrOptimizerExperimentalApply(List compileArgs) { + return openClIrOptimizerExperimentalApply(compileArgs, IR_OPTIMIZER_EXPERIMENTAL_APPLY_PROFILE); + } + + public GpuRuntimeCompileOptions withRuntimeIrOptimizerExperimentalApply() { + return new GpuRuntimeCompileOptions( + backendTarget, + compileArgs, + optimizationProfile, + backendOptions.withRuntimeIrOptimizerExperimentalApply(), + deviceOverride + ); + } + public GpuRuntimeCompileOptions withProductionPromotionDecision(GpuProductionPromotionDecision decision) { return new GpuRuntimeCompileOptions( backendTarget, diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackend.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackend.java index 3e1c6ee7..bf88b1a7 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackend.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackend.java @@ -398,6 +398,7 @@ public final void invoke(GpuKernelInvocation invocation) { ? lowerBackendModule(compileRequest) : null; GpuBackendModuleArtifact optimizedModuleArtifact = runtimeCompileArtifactsConfigured() + || runtimeIrOptimizerExperimentalApplyRequested(optimizedCompileRequest) ? lowerOptimizedReviewModule(optimizedCompileRequest, optimizationResult.report()) : lowerBackendModule(optimizedCompileRequest); GpuRuntimeEquivalenceEvidence runtimeEquivalenceEvidence = executeRuntimeEquivalence(new GpuRuntimeEquivalenceRequest( @@ -1131,9 +1132,7 @@ private GpuBackendModuleArtifact lowerOptimizedReviewModule( GpuRuntimeCompileRequest optimizedCompileRequest, GpuRuntimeIrOptimizationReport optimizationReport ) { - if (optimizationReport == null - || optimizationReport.candidateArtifact().isEmpty() - || sameIr(optimizationReport.artifact(), optimizationReport.candidateArtifact())) { + if (optimizationReport == null || optimizationReport.candidateArtifact().isEmpty()) { return lowerBackendModule(optimizedCompileRequest); } GpuBackendSourceReconstructionResult reconstruction = OpenClIrGpuSourceReconstructor.INSTANCE.reconstruct( @@ -1152,6 +1151,15 @@ private GpuBackendModuleArtifact lowerOptimizedReviewModule( ); } + private static boolean runtimeIrOptimizerExperimentalApplyRequested( + GpuRuntimeCompileRequest optimizedCompileRequest + ) { + return optimizedCompileRequest != null + && optimizedCompileRequest.options() + .backendOptions() + .requestsRuntimeIrOptimizerExperimentalApply(); + } + private static boolean sameIr(Optional first, Optional second) { return IrGpuArtifactIdentity.stableIdentity(first).equals(IrGpuArtifactIdentity.stableIdentity(second)); } @@ -2806,6 +2814,22 @@ private void validateCompileOptions(GpuRuntimeCompileOptions compileOptions) { + "'" ); } + String optimizerSelection = compileOptions.backendOptions() + .properties() + .get(GpuBackendCompileOptions.RUNTIME_IR_OPTIMIZER_SELECTION_PROPERTY); + if (optimizerSelection != null + && !GpuBackendCompileOptions.RUNTIME_IR_OPTIMIZER_SELECTION_REVIEW_ONLY.equals(optimizerSelection) + && !GpuBackendCompileOptions.RUNTIME_IR_OPTIMIZER_SELECTION_EXPERIMENTAL_APPLY.equals(optimizerSelection)) { + throw new IllegalArgumentException( + "Unsupported runtime IR optimizer selection compile option '" + + optimizerSelection + + "'; supported values are '" + + GpuBackendCompileOptions.RUNTIME_IR_OPTIMIZER_SELECTION_REVIEW_ONLY + + "' and '" + + GpuBackendCompileOptions.RUNTIME_IR_OPTIMIZER_SELECTION_EXPERIMENTAL_APPLY + + "'" + ); + } OpenClCompileOptionValidator.toBuildOptions(compileOptions.compileArgs()); } diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java index 09c8a495..533c40bf 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceSummary.java @@ -698,6 +698,53 @@ int totalLoopVectorizationMaterializationTypedBodyInvalidatedCount() { return entries.stream().mapToInt(Entry::loopVectorizationMaterializationTypedBodyInvalidatedCount).sum(); } + int totalLoopVectorizationMaterializationTypedBodyRebuildAttemptedCount() { + return entries.stream().mapToInt(Entry::loopVectorizationMaterializationTypedBodyRebuildAttemptedCount).sum(); + } + + int totalLoopVectorizationMaterializationTypedBodyRebuildGraphValidatedCount() { + return entries.stream() + .mapToInt(Entry::loopVectorizationMaterializationTypedBodyRebuildGraphValidatedCount) + .sum(); + } + + int totalLoopVectorizationMaterializationTypedBodyRebuildRejectedCount() { + return entries.stream().mapToInt(Entry::loopVectorizationMaterializationTypedBodyRebuildRejectedCount).sum(); + } + + String loopVectorizationMaterializationTypedBodyRebuildStatus() { + if (totalLoopVectorizationMaterializationTypedBodyRebuildAttemptedCount() <= 0) { + return "not-attempted"; + } + if (entries.stream().anyMatch(entry -> "invalidated".equals( + entry.loopVectorizationMaterializationTypedBodyRebuildStatus() + ))) { + return "invalidated"; + } + if (entries.stream().anyMatch(entry -> "partially-validated".equals( + entry.loopVectorizationMaterializationTypedBodyRebuildStatus() + ))) { + return "partially-validated"; + } + return totalLoopVectorizationMaterializationTypedBodyRebuildRejectedCount() > 0 + ? "partially-validated" + : "validated"; + } + + String loopVectorizationMaterializationTypedBodyRebuildFirstBlocker() { + for (Entry entry : entries) { + if (entry.loopVectorizationMaterializationTypedBodyRebuildRejectedCount() > 0 + && !"none".equals(entry.loopVectorizationMaterializationTypedBodyRebuildFirstBlocker())) { + return entry.loopVectorizationMaterializationTypedBodyRebuildFirstBlocker(); + } + } + return switch (loopVectorizationMaterializationTypedBodyRebuildStatus()) { + case "not-attempted" -> "not-attempted"; + case "validated" -> "none"; + default -> "typed-body-rebuild-blocked"; + }; + } + int totalLoopVectorizationMaterializationSkippedCount() { return entries.stream().mapToInt(Entry::loopVectorizationMaterializationSkippedCount).sum(); } @@ -1371,6 +1418,16 @@ String toMarkdown() { .append(totalLoopVectorizationMaterializationTypedBodyMaterializedCount()).append("`\n"); markdown.append("- Loop vectorization materialization typed bodies invalidated: `") .append(totalLoopVectorizationMaterializationTypedBodyInvalidatedCount()).append("`\n"); + markdown.append("- Loop vectorization typed-body rebuild status: `") + .append(loopVectorizationMaterializationTypedBodyRebuildStatus()).append("`\n"); + markdown.append("- Loop vectorization typed-body rebuild attempts: `") + .append(totalLoopVectorizationMaterializationTypedBodyRebuildAttemptedCount()).append("`\n"); + markdown.append("- Loop vectorization typed-body rebuild graph validations: `") + .append(totalLoopVectorizationMaterializationTypedBodyRebuildGraphValidatedCount()).append("`\n"); + markdown.append("- Loop vectorization typed-body rebuild rejections: `") + .append(totalLoopVectorizationMaterializationTypedBodyRebuildRejectedCount()).append("`\n"); + markdown.append("- Loop vectorization typed-body rebuild first blocker: `") + .append(inline(loopVectorizationMaterializationTypedBodyRebuildFirstBlocker())).append("`\n"); markdown.append("- Loop vectorization materialization skipped blockers: `") .append(totalLoopVectorizationMaterializationSkippedCount()).append("`\n"); markdown.append("- Loop vectorization materialization runtime-equivalence payloads: `") @@ -1694,6 +1751,13 @@ record Entry( int loopVectorizationMaterializationBodyTextReplacementCount, int loopVectorizationMaterializationTypedBodyMaterializedCount, int loopVectorizationMaterializationTypedBodyInvalidatedCount, + int loopVectorizationMaterializationTypedBodyRebuildAttemptedCount, + int loopVectorizationMaterializationTypedBodyRebuildParsedCount, + int loopVectorizationMaterializationTypedBodyRebuildBuiltCount, + int loopVectorizationMaterializationTypedBodyRebuildGraphValidatedCount, + int loopVectorizationMaterializationTypedBodyRebuildRejectedCount, + String loopVectorizationMaterializationTypedBodyRebuildStatus, + String loopVectorizationMaterializationTypedBodyRebuildFirstBlocker, int loopVectorizationMaterializationSkippedLoopShapeCount, int loopVectorizationMaterializationSkippedUnsupportedWidthCount, int loopVectorizationMaterializationSkippedUnsafeLoadPatternCount, @@ -1961,6 +2025,34 @@ record Entry( 0, loopVectorizationMaterializationTypedBodyInvalidatedCount ); + loopVectorizationMaterializationTypedBodyRebuildAttemptedCount = Math.max( + 0, + loopVectorizationMaterializationTypedBodyRebuildAttemptedCount + ); + loopVectorizationMaterializationTypedBodyRebuildParsedCount = Math.max( + 0, + loopVectorizationMaterializationTypedBodyRebuildParsedCount + ); + loopVectorizationMaterializationTypedBodyRebuildBuiltCount = Math.max( + 0, + loopVectorizationMaterializationTypedBodyRebuildBuiltCount + ); + loopVectorizationMaterializationTypedBodyRebuildGraphValidatedCount = Math.max( + 0, + loopVectorizationMaterializationTypedBodyRebuildGraphValidatedCount + ); + loopVectorizationMaterializationTypedBodyRebuildRejectedCount = Math.max( + 0, + loopVectorizationMaterializationTypedBodyRebuildRejectedCount + ); + loopVectorizationMaterializationTypedBodyRebuildStatus = normalize( + loopVectorizationMaterializationTypedBodyRebuildStatus, + "not-attempted" + ); + loopVectorizationMaterializationTypedBodyRebuildFirstBlocker = normalize( + loopVectorizationMaterializationTypedBodyRebuildFirstBlocker, + "not-attempted" + ); loopVectorizationMaterializationSkippedLoopShapeCount = Math.max( 0, loopVectorizationMaterializationSkippedLoopShapeCount @@ -2208,7 +2300,9 @@ static Entry from(String kernelResource, Properties properties) { 0, 0, 0, 0, 0, 0, "not-recorded", "not-recorded", 0, 0, 0, 0, 0, 0, 0, 0, false, false, 0, 0, false, false, "not-recorded", "not-recorded", - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "not-recorded", "not-recorded", + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, "not-attempted", "not-attempted", + 0, 0, 0, 0, 0, "not-recorded", "not-recorded", 0, 0, 0, 0, 0, 0, 0, 0, false, false, 0, 0, false, false, "not-recorded", "not-recorded", 0, 0, 0, 0, 0, 0, 0, 0, false, false, false, false, @@ -2331,6 +2425,29 @@ static Entry from(String kernelResource, Properties properties) { parseInt(properties.getProperty("loopVectorizationMaterialization.bodyTextReplacement.count"), 0), parseInt(properties.getProperty("loopVectorizationMaterialization.typedBody.materialized.count"), 0), parseInt(properties.getProperty("loopVectorizationMaterialization.typedBody.invalidated.count"), 0), + parseInt(properties.getProperty( + "loopVectorizationMaterialization.typedBody.rebuild.attempted.count" + ), 0), + parseInt(properties.getProperty( + "loopVectorizationMaterialization.typedBody.rebuild.parsed.count" + ), 0), + parseInt(properties.getProperty( + "loopVectorizationMaterialization.typedBody.rebuild.built.count" + ), 0), + parseInt(properties.getProperty( + "loopVectorizationMaterialization.typedBody.rebuild.graphValidated.count" + ), 0), + parseInt(properties.getProperty( + "loopVectorizationMaterialization.typedBody.rebuild.rejected.count" + ), 0), + properties.getProperty( + "loopVectorizationMaterialization.typedBody.rebuild.status", + "not-attempted" + ), + properties.getProperty( + "loopVectorizationMaterialization.typedBody.rebuild.firstBlocker", + "not-attempted" + ), parseInt(properties.getProperty("loopVectorizationMaterialization.skipped.loopShape.count"), 0), parseInt(properties.getProperty("loopVectorizationMaterialization.skipped.unsupportedWidth.count"), 0), parseInt(properties.getProperty("loopVectorizationMaterialization.skipped.unsafeLoadPattern.count"), 0), diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCli.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCli.java index 80dcc467..b25395d1 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCli.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCli.java @@ -19,10 +19,13 @@ private OpenClRuntimeIrOptimizerEvidenceValidatorCli() { } public static void main(String[] args) throws IOException { - if (args.length != 1 || args[0] == null || args[0].isBlank()) { - throw new IllegalArgumentException("Expected runtime IR optimizer evidence file or artifact directory"); + CliArguments cliArguments = parseArguments(args); + if (cliArguments.artifactPath().isEmpty()) { + throw new IllegalArgumentException( + "Expected runtime IR optimizer evidence file or artifact directory" + ); } - Path artifactPath = Path.of(args[0]); + Path artifactPath = cliArguments.artifactPath().orElseThrow(); List artifacts = evidenceArtifacts(artifactPath); if (artifacts.isEmpty()) { throw new IllegalStateException("Missing runtime IR optimizer evidence artifact under " + artifactPath); @@ -38,7 +41,7 @@ public static void main(String[] args) throws IOException { throw new IllegalStateException("Runtime IR optimizer evidence must be recorded for " + artifact + ": status=" + status); } - validateGuardrails(artifact, properties); + validateGuardrails(artifact, properties, cliArguments.allowExperimentalApply()); String reviewPackageStatus = properties.getProperty("reviewPackage.status", "not-recorded"); if ("not-required".equals(reviewPackageStatus)) { notRequired++; @@ -50,7 +53,30 @@ public static void main(String[] args) throws IOException { System.out.println("Runtime IR optimizer evidence OK: recorded=" + recorded + ", reviewPackage.notRequired=" + notRequired - + ", reviewPackage.pendingManualReview=" + pendingManualReview); + + ", reviewPackage.pendingManualReview=" + pendingManualReview + + ", experimentalApply.allowed=" + cliArguments.allowExperimentalApply()); + } + + private static CliArguments parseArguments(String[] args) { + if (args == null || args.length == 0 || args.length > 2) { + return new CliArguments(java.util.Optional.empty(), false); + } + Path artifactPath = null; + boolean allowExperimentalApply = false; + for (String arg : args) { + if (arg == null || arg.isBlank()) { + continue; + } + if ("--allow-experimental-apply".equals(arg)) { + allowExperimentalApply = true; + continue; + } + if (artifactPath != null) { + return new CliArguments(java.util.Optional.empty(), allowExperimentalApply); + } + artifactPath = Path.of(arg); + } + return new CliArguments(java.util.Optional.ofNullable(artifactPath), allowExperimentalApply); } private static List evidenceArtifacts(Path path) throws IOException { @@ -77,7 +103,11 @@ private static Properties loadProperties(Path path) throws IOException { return properties; } - private static void validateGuardrails(Path artifact, Properties properties) { + private static void validateGuardrails( + Path artifact, + Properties properties, + boolean allowExperimentalApply + ) { requireValue(artifact, properties, "runtimeEquivalenceReview.productionMutation", "disabled"); requireValue(artifact, properties, "runtimeEquivalenceReview.selectedIrReplacement", "disabled"); requireValue(artifact, properties, "runtimeEquivalenceReview.manualReviewOnly", "true"); @@ -87,7 +117,7 @@ private static void validateGuardrails(Path artifact, Properties properties) { requireValue(artifact, properties, "reviewPackage.originalIrRequired", "true"); requireValue(artifact, properties, "reviewPackage.optimizedIrRequired", "true"); requireValue(artifact, properties, "reviewPackage.proofSummaryRequired", "true"); - validateOptimizedArtifactCandidateGuardrails(artifact, properties); + validateOptimizedArtifactCandidateGuardrails(artifact, properties, allowExperimentalApply); validateSafeLocalCseMaterializationEvidence(artifact, properties); validateMadFmaMaterializationEvidence(artifact, properties); validateIntrinsicMaterializationEvidence( @@ -158,6 +188,11 @@ private static void validateSafeLocalCseMaterializationEvidence(Path artifact, P int passCount = parseNonNegativeInt(artifact, properties, "safeLocalCseMaterialization.pass.count"); int localBindingCount = parseNonNegativeInt(artifact, properties, "safeLocalCseMaterialization.localBinding.count"); + int introducedTemporaryCount = parseNonNegativeInt( + artifact, + properties, + "safeLocalCseMaterialization.introducedTemporary.count" + ); parseNonNegativeInt(artifact, properties, "safeLocalCseMaterialization.candidate.count"); int transformedNodeCount = parseNonNegativeInt( artifact, @@ -237,8 +272,9 @@ private static void validateSafeLocalCseMaterializationEvidence(Path artifact, P throw new IllegalStateException("safe-local-CSE runtime-equivalence passed count exceeds present count for " + artifact + ": passed=" + payloadPassedCount + ", present=" + payloadPresentCount); } - if (transformedNodeCount > 0 && (localBindingCount <= 0 || bodyTextReplacementCount <= 0)) { - throw new IllegalStateException("safe-local-CSE materialized nodes must have existing local bindings and text replacements for " + if (transformedNodeCount > 0 + && (localBindingCount + introducedTemporaryCount <= 0 || bodyTextReplacementCount <= 0)) { + throw new IllegalStateException("safe-local-CSE materialized nodes must have existing or introduced local bindings and text replacements for " + artifact); } if (transformedNodeCount > 0 && (!payloadRequired || !approvalRequired)) { @@ -293,7 +329,11 @@ private static void validateSafeLocalCseMaterializationEvidence(Path artifact, P } } - private static void validateOptimizedArtifactCandidateGuardrails(Path artifact, Properties properties) { + private static void validateOptimizedArtifactCandidateGuardrails( + Path artifact, + Properties properties, + boolean allowExperimentalApply + ) { String status = requirePresent(artifact, properties, "optimizedArtifactCandidate.status"); int count = parseNonNegativeInt(artifact, properties, "optimizedArtifactCandidate.count"); int readyCount = parseNonNegativeInt(artifact, properties, "optimizedArtifactCandidate.ready.count"); @@ -314,14 +354,24 @@ private static void validateOptimizedArtifactCandidateGuardrails(Path artifact, "optimizedArtifactCandidate.selectedIrReplacement.count" ); parseNonNegativeInt(artifact, properties, "optimizedArtifactCandidate.mutationAllowed.count"); + int experimentalApplySelectedCount = parseOptionalNonNegativeInt( + artifact, + properties, + "experimentalApply.selected.count", + 0 + ); + boolean experimentalApplyRequested = parseBoolean(properties.getProperty("experimentalApply.requested")); + boolean experimentalApplyEnabled = parseBoolean(properties.getProperty("experimentalApply.enabled")); String firstBlocker = requirePresent(artifact, properties, "optimizedArtifactCandidate.firstBlocker"); String selectionFirstBlocker = requirePresent( artifact, properties, "optimizedArtifactCandidate.selectionFirstBlocker" ); - requireValue(artifact, properties, "optimizedArtifactCandidate.selectionApplied", "false"); - requireValue(artifact, properties, "optimizedArtifactCandidate.selectedIrReplacement", "false"); + if (!allowExperimentalApply) { + requireValue(artifact, properties, "optimizedArtifactCandidate.selectionApplied", "false"); + requireValue(artifact, properties, "optimizedArtifactCandidate.selectedIrReplacement", "false"); + } if (!List.of("not-recorded", "candidate-ready", "blocked", "mixed").contains(status)) { throw new IllegalStateException("Unsupported optimized artifact candidate status for " + artifact @@ -344,16 +394,35 @@ private static void validateOptimizedArtifactCandidateGuardrails(Path artifact, throw new IllegalStateException("Recorded optimized artifact candidates cannot use not-recorded status for " + artifact); } - if (count > 0 && ("none".equals(selectionFirstBlocker) || "no-candidates".equals(selectionFirstBlocker))) { + if (count > 0 + && !allowExperimentalApply + && ("none".equals(selectionFirstBlocker) || "no-candidates".equals(selectionFirstBlocker))) { throw new IllegalStateException("Optimized artifact candidates must keep selection blocked for " + artifact + ": selectionFirstBlocker=" + selectionFirstBlocker); } - if (selectionReadyCount > 0 || selectionAppliedCount > 0 || selectedIrReplacementCount > 0) { + if (!allowExperimentalApply + && (selectionReadyCount > 0 || selectionAppliedCount > 0 || selectedIrReplacementCount > 0)) { throw new IllegalStateException("Optimized artifact candidates must not become runtime selection for " + artifact + ": selectionReady=" + selectionReadyCount + ", selectionApplied=" + selectionAppliedCount + ", selectedIrReplacement=" + selectedIrReplacementCount); } + if (allowExperimentalApply + && (selectionReadyCount > 0 || selectionAppliedCount > 0 || selectedIrReplacementCount > 0)) { + if (!experimentalApplyRequested || !experimentalApplyEnabled || experimentalApplySelectedCount <= 0) { + throw new IllegalStateException("Experimental optimized artifact selection requires explicit apply evidence for " + + artifact); + } + if (selectionAppliedCount != selectedIrReplacementCount) { + throw new IllegalStateException("Experimental optimized artifact selection must keep selection/replacement counts aligned for " + + artifact + ": selectionApplied=" + selectionAppliedCount + + ", selectedIrReplacement=" + selectedIrReplacementCount); + } + if (!"none".equals(selectionFirstBlocker)) { + throw new IllegalStateException("Experimental optimized artifact selection must clear selection blocker for " + + artifact + ": selectionFirstBlocker=" + selectionFirstBlocker); + } + } } private static void validateMadFmaMaterializationEvidence(Path artifact, Properties properties) { @@ -754,8 +823,51 @@ private static void validateLoopVectorizationMaterializationEvidence(Path artifa "loopVectorizationMaterialization.bodyTextReplacement.count" ); parseNonNegativeInt(artifact, properties, "loopVectorizationMaterialization.changedMethodBody.count"); - parseNonNegativeInt(artifact, properties, "loopVectorizationMaterialization.typedBody.materialized.count"); - parseNonNegativeInt(artifact, properties, "loopVectorizationMaterialization.typedBody.invalidated.count"); + int typedBodyMaterializedCount = parseNonNegativeInt( + artifact, + properties, + "loopVectorizationMaterialization.typedBody.materialized.count" + ); + int typedBodyInvalidatedCount = parseNonNegativeInt( + artifact, + properties, + "loopVectorizationMaterialization.typedBody.invalidated.count" + ); + int typedBodyRebuildAttemptedCount = parseNonNegativeInt( + artifact, + properties, + "loopVectorizationMaterialization.typedBody.rebuild.attempted.count" + ); + int typedBodyRebuildParsedCount = parseNonNegativeInt( + artifact, + properties, + "loopVectorizationMaterialization.typedBody.rebuild.parsed.count" + ); + int typedBodyRebuildBuiltCount = parseNonNegativeInt( + artifact, + properties, + "loopVectorizationMaterialization.typedBody.rebuild.built.count" + ); + int typedBodyRebuildGraphValidatedCount = parseNonNegativeInt( + artifact, + properties, + "loopVectorizationMaterialization.typedBody.rebuild.graphValidated.count" + ); + int typedBodyRebuildRejectedCount = parseNonNegativeInt( + artifact, + properties, + "loopVectorizationMaterialization.typedBody.rebuild.rejected.count" + ); + String typedBodyRebuildStatus = requirePresent( + artifact, + properties, + "loopVectorizationMaterialization.typedBody.rebuild.status" + ); + String typedBodyRebuildFirstBlocker = requirePresent( + artifact, + properties, + "loopVectorizationMaterialization.typedBody.rebuild.firstBlocker" + ); int skippedLoopShapeCount = parseNonNegativeInt( artifact, properties, @@ -826,6 +938,48 @@ private static void validateLoopVectorizationMaterializationEvidence(Path artifa throw new IllegalStateException("loop-vectorization transformed loops must have matching body text replacements for " + artifact); } + if (typedBodyRebuildParsedCount > typedBodyRebuildAttemptedCount + || typedBodyRebuildBuiltCount > typedBodyRebuildParsedCount + || typedBodyRebuildGraphValidatedCount > typedBodyRebuildBuiltCount + || typedBodyRebuildRejectedCount > typedBodyRebuildAttemptedCount) { + throw new IllegalStateException("loop-vectorization typed-body rebuild counters are inconsistent for " + artifact); + } + if (transformedLoopCount > 0 && typedBodyRebuildAttemptedCount <= 0) { + throw new IllegalStateException("loop-vectorization transformed loops must attempt typed-body rebuild for " + + artifact); + } + switch (typedBodyRebuildStatus) { + case "not-attempted" -> { + if (typedBodyRebuildAttemptedCount > 0 || !"not-attempted".equals(typedBodyRebuildFirstBlocker)) { + throw new IllegalStateException("loop-vectorization typed-body rebuild not-attempted evidence is inconsistent for " + + artifact); + } + } + case "validated" -> { + if (typedBodyRebuildAttemptedCount <= 0 || typedBodyRebuildRejectedCount > 0 + || typedBodyMaterializedCount <= 0 || !"none".equals(typedBodyRebuildFirstBlocker)) { + throw new IllegalStateException("loop-vectorization typed-body rebuild validated evidence is inconsistent for " + + artifact); + } + } + case "partially-validated", "invalidated" -> { + if (typedBodyRebuildRejectedCount <= 0 || typedBodyInvalidatedCount <= 0 + || "none".equals(typedBodyRebuildFirstBlocker) + || "not-attempted".equals(typedBodyRebuildFirstBlocker)) { + throw new IllegalStateException("loop-vectorization typed-body rebuild rejection evidence is inconsistent for " + + artifact); + } + } + default -> throw new IllegalStateException("Unsupported loop-vectorization typed-body rebuild status for " + + artifact + ": " + typedBodyRebuildStatus); + } + boolean typedBodyRebuildClean = "validated".equals(typedBodyRebuildStatus); + if (transformedLoopCount > 0 + && (typedBodyRebuildRejectedCount > 0 || typedBodyInvalidatedCount > 0) + && !"blocked".equals(status)) { + throw new IllegalStateException("loop-vectorization rejected typed-body rebuild evidence must block review readiness for " + + artifact); + } if (transformedLoopCount > 0 && (!payloadRequired || !approvalRequired)) { throw new IllegalStateException("loop-vectorization transformed loops must require runtime-equivalence and approval for " + artifact); @@ -848,7 +1002,7 @@ private static void validateLoopVectorizationMaterializationEvidence(Path artifa } } case "pending-runtime-equivalence" -> { - if (transformedLoopCount <= 0 || payloadPresentCount > 0 || !proofClean + if (transformedLoopCount <= 0 || payloadPresentCount > 0 || !proofClean || !typedBodyRebuildClean || !"runtime-equivalence-payload-not-recorded".equals(firstBlocker)) { throw new IllegalStateException("loop-vectorization pending runtime-equivalence evidence is inconsistent for " + artifact); @@ -856,7 +1010,7 @@ private static void validateLoopVectorizationMaterializationEvidence(Path artifa } case "runtime-equivalence-not-passed" -> { if (transformedLoopCount <= 0 || payloadPresentCount <= 0 - || payloadPassedCount >= payloadPresentCount || !proofClean + || payloadPassedCount >= payloadPresentCount || !proofClean || !typedBodyRebuildClean || !"runtime-equivalence-not-passed".equals(firstBlocker)) { throw new IllegalStateException("loop-vectorization failed runtime-equivalence evidence is inconsistent for " + artifact); @@ -864,7 +1018,7 @@ private static void validateLoopVectorizationMaterializationEvidence(Path artifa } case "review-ready" -> { if (transformedLoopCount <= 0 || payloadPresentCount <= 0 - || payloadPassedCount < payloadPresentCount || !proofClean + || payloadPassedCount < payloadPresentCount || !proofClean || !typedBodyRebuildClean || !"none".equals(firstBlocker)) { throw new IllegalStateException("loop-vectorization review-ready evidence must include proofs and passed payloads for " + artifact); @@ -1025,6 +1179,9 @@ private enum IntrinsicMaterializationKind { MIX } + private record CliArguments(java.util.Optional artifactPath, boolean allowExperimentalApply) { + } + private static String requirePresent(Path artifact, Properties properties, String key) { String value = properties.getProperty(key); if (value == null || value.isBlank()) { @@ -1047,6 +1204,23 @@ private static boolean parseBoolean(String value) { private static int parseNonNegativeInt(Path artifact, Properties properties, String key) { String value = requirePresent(artifact, properties, key); + return parseNonNegativeInt(artifact, key, value); + } + + private static int parseOptionalNonNegativeInt( + Path artifact, + Properties properties, + String key, + int fallback + ) { + String value = properties.getProperty(key); + if (value == null || value.isBlank()) { + return fallback; + } + return parseNonNegativeInt(artifact, key, value); + } + + private static int parseNonNegativeInt(Path artifact, String key, String value) { try { int parsed = Integer.parseInt(value); if (parsed < 0) { diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java index a12e2b16..7f3c108c 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeCompileArtifactDumperTest.java @@ -3268,6 +3268,7 @@ void dumpsSafeLocalCseMaterializationReviewReadyRuntimeEquivalencePayload() { assertTrue(evidence.contains("safeLocalCseMaterialization.pass.count=1")); assertTrue(evidence.contains("safeLocalCseMaterialization.localBinding.count=1")); + assertTrue(evidence.contains("safeLocalCseMaterialization.introducedTemporary.count=0")); assertTrue(evidence.contains("safeLocalCseMaterialization.transformedNode.count=1")); assertTrue(evidence.contains("safeLocalCseMaterialization.bodyTextReplacement.count=1")); assertTrue(evidence.contains("safeLocalCseMaterialization.fixedPoint.pass.count=1")); @@ -3673,6 +3674,114 @@ void dumpsIntrinsicMaterializationRuntimeEquivalenceReviewEvidence() { )); } + @Test + void dumpsLoopVectorizationInvalidatedTypedBodyRebuildAsBlockedEvidence() { + IrGpuArtifact original = artifact("body\n return loop vectorization candidates\n"); + GpuBackendModuleArtifact backendArtifact = GpuBackendModuleArtifact.openClSource( + "__kernel void kernel(__global const float* input, __global float* out) { out[0] = input[0]; }", + "runtime/lowered/kernel.cl", + "test-lowerer-v1" + ); + GpuRuntimeCompileRequest request = new GpuRuntimeCompileRequest( + descriptor(), + new GpuRuntimeCompileOptions(GpuBackendTarget.OPENCL, List.of(), "diagnostic"), + GpuRuntimeDeviceProfile.generic(GpuBackendTarget.OPENCL, "OpenCL"), + Optional.of(original) + ); + GpuRuntimeIrOptimizationPassReport materializationReport = new GpuRuntimeIrOptimizationPassReport( + GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, + "javatogpu.ir-optimizer.loop-vectorization-materialization:1", + GpuRuntimeIrOptimizationOutcome.SKIPPED, + "irgpu:sha256:original", + "irgpu:sha256:loop-vectorized-source-only", + "proposal-only", + "", + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "ir-optimizer.loop-vectorization-materialization", + "materialized-review-candidate", + Map.ofEntries( + Map.entry("candidate.count", "1"), + Map.entry("transformedLoop.count", "1"), + Map.entry("changedMethodBody.count", "1"), + Map.entry("bodyTextReplacement.count", "1"), + Map.entry("typedBody.materialized.count", "0"), + Map.entry("typedBody.invalidated.count", "1"), + Map.entry("typedBody.rebuild.attempted.count", "1"), + Map.entry("typedBody.rebuild.parsed.count", "1"), + Map.entry("typedBody.rebuild.built.count", "0"), + Map.entry("typedBody.rebuild.graphValidated.count", "0"), + Map.entry("typedBody.rebuild.rejected.count", "1"), + Map.entry( + "typedBody.rebuild.firstBlocker", + "typed-body-rebuild-statement-conversion-blocked" + ), + Map.entry("skipped.loopShape.count", "0"), + Map.entry("skipped.unsupportedWidth.count", "0"), + Map.entry("skipped.unsafeLoadPattern.count", "0"), + Map.entry("rewrite.proposed", "true"), + Map.entry("rewrite.materialized", "true"), + Map.entry("optimizerFamily", "loop-vectorization-materialization"), + Map.entry("proof.runtimeEquivalenceRequiredBeforeSelection", "true"), + Map.entry("proof.runtimeEquivalencePayloadRequiredBeforeSelection", "true"), + Map.entry("proof.approvalRequiredBeforeProduction", "true"), + Map.entry("runtimeEquivalencePayload.status", "recorded"), + Map.entry("runtimeEquivalencePayload.required", "true"), + Map.entry("runtimeEquivalencePayload.present", "true"), + Map.entry("runtimeEquivalencePayload.passed", "true"), + Map.entry("runtimeEquivalencePayload.firstBlocker", "none"), + Map.entry("safety.loopTripCountProven", "true"), + Map.entry("safety.contiguousLoadProven", "true"), + Map.entry("safety.orderedReductionPreserved", "true"), + Map.entry("firstBlocker", "typed-body-rebuild-statement-conversion-blocked") + ) + ), + List.of("loop vectorization source rewrite succeeded but typed body rebuild was invalidated") + ); + GpuRuntimeIrOptimizationReport optimizationReport = new GpuRuntimeIrOptimizationReport( + Optional.of(original), + List.of(materializationReport), + GpuOptimizationStrategyDecision.none(request) + ); + GpuRuntimeCompileArtifactSnapshot snapshot = GpuRuntimeCompileArtifactSnapshot.from( + request, + request, + backendArtifact, + GpuRuntimeCompileInvalidationStamp.from(request, backendArtifact, "optimizer:loop-vectorization"), + GpuRuntimeCompileProvenance.from(request), + optimizationReport, + GpuRuntimeEquivalenceEvidence.passed(request, 1, 1, List.of("source rewrite payload matched")) + ); + + String evidence = GpuRuntimeCompileArtifactDumper.dump(snapshot) + .artifact(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + + assertTrue(evidence.contains("loopVectorizationMaterialization.transformedLoop.count=1")); + assertTrue(evidence.contains("loopVectorizationMaterialization.bodyTextReplacement.count=1")); + assertTrue(evidence.contains("loopVectorizationMaterialization.typedBody.materialized.count=0")); + assertTrue(evidence.contains("loopVectorizationMaterialization.typedBody.invalidated.count=1")); + assertTrue(evidence.contains("loopVectorizationMaterialization.typedBody.rebuild.attempted.count=1")); + assertTrue(evidence.contains("loopVectorizationMaterialization.typedBody.rebuild.parsed.count=1")); + assertTrue(evidence.contains("loopVectorizationMaterialization.typedBody.rebuild.built.count=0")); + assertTrue(evidence.contains("loopVectorizationMaterialization.typedBody.rebuild.graphValidated.count=0")); + assertTrue(evidence.contains("loopVectorizationMaterialization.typedBody.rebuild.rejected.count=1")); + assertTrue(evidence.contains("loopVectorizationMaterialization.typedBody.rebuild.status=invalidated")); + assertTrue(evidence.contains( + "loopVectorizationMaterialization.typedBody.rebuild.firstBlocker=" + + "typed-body-rebuild-statement-conversion-blocked" + )); + assertTrue(evidence.contains("loopVectorizationMaterialization.runtimeEquivalencePayloadPresent.count=1")); + assertTrue(evidence.contains("loopVectorizationMaterialization.runtimeEquivalencePassed.count=1")); + assertTrue(evidence.contains("loopVectorizationMaterialization.status=blocked")); + assertTrue(evidence.contains( + "loopVectorizationMaterialization.firstBlocker=typed-body-rebuild-statement-conversion-blocked" + )); + assertTrue(evidence.contains("runtimeEquivalenceReview.status=blocked")); + assertTrue(evidence.contains( + "runtimeEquivalenceReview.firstBlocker=typed-body-rebuild-statement-conversion-blocked" + )); + assertTrue(evidence.contains("loop-vectorization-materialization=blocked")); + } + @Test void dumpsTypedDeadCodeMaterializationReviewReadyRuntimeEquivalencePayload() { IrGpuArtifact original = artifact("body\n return live\n"); diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackendTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackendTest.java index 1eb2d212..1360e6c5 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackendTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackendTest.java @@ -324,6 +324,127 @@ protected OpenClCompiledKernel compileKernel( } } + @Test + void experimentalIrOptimizerApplySelectsMaterializedCandidateForRuntimeCompile() throws Exception { + Path artifactRoot = Files.createTempDirectory("javatogpu-runtime-ir-experimental-apply-dump"); + Path classpathRoot = Files.createTempDirectory("javatogpu-runtime-ir-experimental-apply-classpath"); + Path artifactPath = classpathRoot.resolve("javatogpu/sample/Demo/kernel.irgpu.properties"); + Files.createDirectories(artifactPath.getParent()); + Files.writeString(artifactPath, irGpuArtifactProperties("return original")); + String property = "javatogpu.opencl.runtimeCompileArtifactDirectory"; + String previousArtifactRoot = System.getProperty(property); + AtomicReference capturedSnapshot = new AtomicReference<>(); + AtomicReference finalCompileRequest = new AtomicReference<>(); + try (URLClassLoader classLoader = new URLClassLoader( + new URL[]{classpathRoot.toUri().toURL()}, + OpenClGpuRuntimeBackendTest.class.getClassLoader() + )) { + System.setProperty(property, artifactRoot.toString()); + OpenClGpuRuntimeBackend backend = new SnapshotCapturingBackend(capturedSnapshot) { + @Override + protected GpuRuntimeIrOptimizationResult optimizeRuntimeIrWithReport(GpuRuntimeCompileRequest compileRequest) { + IrGpuArtifact originalArtifact = compileRequest.irGpuArtifact().orElseThrow(); + IrGpuArtifact candidateArtifact = testIrGpuArtifact("body\n return optimized candidate\n"); + String originalIdentity = IrGpuArtifactIdentity.stableIdentity(originalArtifact); + String candidateIdentity = IrGpuArtifactIdentity.stableIdentity(candidateArtifact); + GpuRuntimeIrOptimizationPassReport passReport = new GpuRuntimeIrOptimizationPassReport( + net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationStage.CANDIDATE_DISCOVERY, + "javatogpu.ir-optimizer:test-experimental-apply", + net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationOutcome.APPLIED, + originalIdentity, + candidateIdentity, + "optimized-selected", + "", + GpuRuntimeIrOptimizationProofArtifact.fromFields( + "test.experimentalApply", + "candidate-ready", + java.util.Map.ofEntries( + java.util.Map.entry("experimentalApply.requested", "true"), + java.util.Map.entry("experimentalApply.enabled", "true"), + java.util.Map.entry("experimentalApply.selected", "true"), + java.util.Map.entry("optimizedArtifactCandidate.status", "candidate-ready"), + java.util.Map.entry("optimizedArtifactCandidate.selectionReady", "true"), + java.util.Map.entry("optimizedArtifactCandidate.selectionApplied", "true"), + java.util.Map.entry("optimizedArtifactCandidate.selectedIrReplacement", "true"), + java.util.Map.entry("optimizedArtifactCandidate.mutationAllowed", "true"), + java.util.Map.entry("optimizedArtifactCandidate.firstBlocker", "none"), + java.util.Map.entry("optimizedArtifactCandidate.selectionFirstBlocker", "none") + ) + ), + List.of("candidate materialized and selected by experimental apply mode") + ); + return new GpuRuntimeIrOptimizationResult( + compileRequest.withIrGpuArtifact(Optional.of(candidateArtifact)), + new GpuRuntimeIrOptimizationReport( + Optional.of(candidateArtifact), + Optional.of(candidateArtifact), + List.of(passReport) + ) + ); + } + + @Override + protected GpuBackendModuleArtifact lowerBackendModule(GpuRuntimeCompileRequest compileRequest) { + String body = compileRequest.irGpuArtifact() + .map(artifact -> artifact.module().methodBodies().get(0).body()) + .orElse(""); + boolean optimized = body.contains("optimized candidate"); + return GpuBackendModuleArtifact.openClSource( + optimized + ? "__kernel void kernel(__global int* output) { output[0] = 2; }" + : "__kernel void kernel(__global int* output) { output[0] = 1; }", + optimized + ? "runtime/lowered/kernel-optimized.cl" + : "runtime/lowered/kernel-original.cl", + "test-lowerer-v1" + ); + } + + @Override + protected OpenClCompiledKernel compileKernel( + GpuRuntimeCompileRequest compileRequest, + GpuBackendModuleArtifact moduleArtifact + ) { + finalCompileRequest.set(compileRequest); + return super.compileKernel(compileRequest, moduleArtifact); + } + }; + + backend.invoke(new GpuKernelInvocation( + descriptorWithIrGpuResource(), + new Object[]{new int[]{0}}, + GpuRuntimeCompileOptions.openClIrOptimizerExperimentalApply(List.of(), "diagnostic") + ).withArtifactClassLoader(classLoader)); + + GpuRuntimeCompileArtifactSnapshot snapshot = capturedSnapshot.get(); + String originalIdentity = IrGpuArtifactIdentity.stableIdentity(snapshot.originalIrGpuArtifact()); + String optimizedIdentity = IrGpuArtifactIdentity.stableIdentity(snapshot.optimizedIrGpuArtifact()); + assertNotEquals(originalIdentity, optimizedIdentity); + assertEquals(optimizedIdentity, IrGpuArtifactIdentity.stableIdentity(finalCompileRequest.get().irGpuArtifact())); + assertEquals("optimized", snapshot.runtimeIrSelection().selectedStage()); + assertTrue(snapshot.runtimeIrSelection().transformed()); + assertFalse(snapshot.runtimeIrSelection().optimizedRejected()); + assertTrue(snapshot.backendModuleArtifact().source().contains("output[0] = 2")); + + Path artifactDirectory; + try (java.util.stream.Stream directories = Files.list(artifactRoot)) { + artifactDirectory = directories.filter(Files::isDirectory).findFirst().orElseThrow(); + } + assertTrue(Files.readString(artifactDirectory.resolve("backend.opencl-c")).contains("output[0] = 2")); + assertTrue(Files.readString(artifactDirectory.resolve("runtime-ir-handoff.properties")).contains("selectedStage=optimized")); + assertTrue(Files.readString(artifactDirectory.resolve("runtime-ir-optimizer-evidence.properties")) + .contains("experimentalApply.selected.count=1")); + assertTrue(Files.readString(artifactDirectory.resolve("runtime-ir-optimizer-evidence.properties")) + .contains("optimizedArtifactCandidate.selectionApplied=true")); + } finally { + if (previousArtifactRoot == null) { + System.clearProperty(property); + } else { + System.setProperty(property, previousArtifactRoot); + } + } + } + @Test void writesOptimizerFamilyPayloadFixtureArtifacts() throws Exception { String outputDirectory = System.getProperty(OPTIMIZER_FAMILY_PAYLOAD_FIXTURE_DIRECTORY_PROPERTY); @@ -1174,6 +1295,60 @@ protected void executeKernel(OpenClPreparedExecution execution) { assertTrue(exception.getMessage().contains(GpuBackendCompileOptions.OPENCL_PRODUCTION_SOURCE_SWITCHING_ENABLED)); } + @Test + void rejectsUnsupportedRuntimeIrOptimizerSelectionBeforeRuntimeCapabilityLookup() { + GpuKernelDescriptor descriptor = new GpuKernelDescriptor( + "kernel", + "javatogpu/sample/Demo/kernel.cl", + "__kernel void kernel(__global int* output) { output[0] = 1; }", + java.util.List.of( + new GpuKernelParameterDescriptor("output", "int[]", GpuKernelParameterAccess.READ_WRITE) + ) + ); + GpuRuntimeCompileOptions compileOptions = new GpuRuntimeCompileOptions( + GpuBackendTarget.OPENCL, + java.util.List.of(), + "diagnostic", + GpuBackendCompileOptions.openCl( + java.util.List.of(), + java.util.Map.of( + GpuBackendCompileOptions.RUNTIME_IR_OPTIMIZER_SELECTION_PROPERTY, + "auto" + ) + ) + ); + AtomicInteger capabilityLookups = new AtomicInteger(); + + OpenClGpuRuntimeBackend backend = new OpenClGpuRuntimeBackend() { + @Override + protected OpenClRuntimeCapabilities runtimeCapabilities() { + capabilityLookups.incrementAndGet(); + return new OpenClRuntimeCapabilities("Mock GPU", "OpenCL 3.0 Mock", true, true, true, 32_768L, 256L); + } + + @Override + protected OpenClCompiledKernel compileKernel(GpuRuntimeCompileRequest compileRequest) { + return new OpenClCompiledKernel(compileRequest.descriptor(), "compiled:test"); + } + + @Override + protected void executeKernel(OpenClPreparedExecution execution) { + // no-op + } + }; + + GpuRuntimeCompileOptionsException exception = assertThrows( + GpuRuntimeCompileOptionsException.class, + () -> backend.invoke(new GpuKernelInvocation(descriptor, new Object[]{new int[]{0}}, compileOptions)) + ); + + assertEquals(0, capabilityLookups.get()); + assertTrue(exception.getMessage().contains("Unsupported runtime IR optimizer selection compile option")); + assertTrue(exception.getMessage().contains("auto")); + assertTrue(exception.getMessage().contains(GpuBackendCompileOptions.RUNTIME_IR_OPTIMIZER_SELECTION_REVIEW_ONLY)); + assertTrue(exception.getMessage().contains(GpuBackendCompileOptions.RUNTIME_IR_OPTIMIZER_SELECTION_EXPERIMENTAL_APPLY)); + } + @Test void rejectsCompileOptionsForOtherBackendBeforeRuntimeCapabilityLookup() { GpuKernelDescriptor descriptor = new GpuKernelDescriptor( diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCliTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCliTest.java index 24ea538a..2686476c 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCliTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCliTest.java @@ -104,6 +104,27 @@ void rejectsOptimizedArtifactCandidateSelectedIrReplacementRegression() throws E ); } + @Test + void rejectsExperimentalApplySelectionWithoutExplicitValidatorFlag() throws Exception { + Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + Files.writeString(artifact, experimentalApplyEvidence()); + + assertThrows( + IllegalStateException.class, + () -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main(new String[]{artifact.toString()}) + ); + } + + @Test + void acceptsExperimentalApplySelectionWithExplicitValidatorFlag() throws Exception { + Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + Files.writeString(artifact, experimentalApplyEvidence()); + + assertDoesNotThrow(() -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main( + new String[]{"--allow-experimental-apply", artifact.toString()} + )); + } + @Test void rejectsRequiredPackageWithoutBlocker() throws Exception { Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); @@ -288,6 +309,62 @@ void acceptsLoopVectorizationReviewReadyEvidenceAsManualReviewOnly() throws Exce assertDoesNotThrow(() -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main(new String[]{artifact.toString()})); } + @Test + void rejectsLoopVectorizationReviewReadyWithInvalidatedTypedBodyRebuild() throws Exception { + Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + String invalidatedLoopEvidence = loopVectorizationMaterializationEvidence( + "review-ready", + "none", + "1", + "1", + "1", + "true", + "true", + "true" + ) + .replace( + "loopVectorizationMaterialization.typedBody.materialized.count=1", + "loopVectorizationMaterialization.typedBody.materialized.count=0" + ) + .replace( + "loopVectorizationMaterialization.typedBody.invalidated.count=0", + "loopVectorizationMaterialization.typedBody.invalidated.count=1" + ) + .replace( + "loopVectorizationMaterialization.typedBody.rebuild.built.count=1", + "loopVectorizationMaterialization.typedBody.rebuild.built.count=0" + ) + .replace( + "loopVectorizationMaterialization.typedBody.rebuild.graphValidated.count=1", + "loopVectorizationMaterialization.typedBody.rebuild.graphValidated.count=0" + ) + .replace( + "loopVectorizationMaterialization.typedBody.rebuild.rejected.count=0", + "loopVectorizationMaterialization.typedBody.rebuild.rejected.count=1" + ) + .replace( + "loopVectorizationMaterialization.typedBody.rebuild.status=validated", + "loopVectorizationMaterialization.typedBody.rebuild.status=invalidated" + ) + .replace( + "loopVectorizationMaterialization.typedBody.rebuild.firstBlocker=none", + "loopVectorizationMaterialization.typedBody.rebuild.firstBlocker=" + + "typed-body-rebuild-statement-conversion-blocked" + ); + Files.writeString(artifact, failClosedEvidence( + "pending-manual-review", + "true", + "approval-template-pending", + "1", + "1" + ) + invalidatedLoopEvidence); + + assertThrows( + IllegalStateException.class, + () -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main(new String[]{artifact.toString()}) + ); + } + @Test void rejectsLoopVectorizationReviewReadyWithoutProofs() throws Exception { Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); @@ -336,6 +413,37 @@ void acceptsSafeLocalCseReviewReadyEvidenceAsManualReviewOnly() throws Exception assertDoesNotThrow(() -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main(new String[]{artifact.toString()})); } + @Test + void acceptsSafeLocalCseIntroducedTemporaryReviewReadyEvidenceAsManualReviewOnly() throws Exception { + Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); + String introducedTemporaryEvidence = safeLocalCseMaterializationEvidence( + "review-ready", + "none", + "1", + "1", + "1", + "true", + "true" + ) + .replace( + "safeLocalCseMaterialization.localBinding.count=1", + "safeLocalCseMaterialization.localBinding.count=0" + ) + .replace( + "safeLocalCseMaterialization.introducedTemporary.count=0", + "safeLocalCseMaterialization.introducedTemporary.count=1" + ); + Files.writeString(artifact, failClosedEvidence( + "pending-manual-review", + "true", + "approval-template-pending", + "1", + "1" + ) + introducedTemporaryEvidence); + + assertDoesNotThrow(() -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main(new String[]{artifact.toString()})); + } + @Test void rejectsSafeLocalCseReviewReadyWithoutProofs() throws Exception { Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); @@ -449,6 +557,36 @@ private static String failClosedEvidence( ); } + private static String experimentalApplyEvidence() { + return failClosedEvidence( + "pending-manual-review", + "true", + "approval-template-pending", + "1", + "1" + ) + .replace("selectedOptimized.count=0", "selectedOptimized.count=1") + .replace("optimizedArtifactCandidate.status=not-recorded", "optimizedArtifactCandidate.status=candidate-ready") + .replace("optimizedArtifactCandidate.count=0", "optimizedArtifactCandidate.count=1") + .replace("optimizedArtifactCandidate.ready.count=0", "optimizedArtifactCandidate.ready.count=1") + .replace("optimizedArtifactCandidate.selectionReady.count=0", "optimizedArtifactCandidate.selectionReady.count=1") + .replace("optimizedArtifactCandidate.selectionApplied.count=0", "optimizedArtifactCandidate.selectionApplied.count=1") + .replace("optimizedArtifactCandidate.selectedIrReplacement.count=0", "optimizedArtifactCandidate.selectedIrReplacement.count=1") + .replace("optimizedArtifactCandidate.mutationAllowed.count=0", "optimizedArtifactCandidate.mutationAllowed.count=1") + .replace("optimizedArtifactCandidate.firstBlocker=no-candidates", "optimizedArtifactCandidate.firstBlocker=none") + .replace("optimizedArtifactCandidate.selectionFirstBlocker=no-candidates", "optimizedArtifactCandidate.selectionFirstBlocker=none") + .replace("optimizedArtifactCandidate.selectionApplied=false", "optimizedArtifactCandidate.selectionApplied=true") + .replace("optimizedArtifactCandidate.selectedIrReplacement=false", "optimizedArtifactCandidate.selectedIrReplacement=true") + + String.join("\n", + "experimentalApply.requested=true", + "experimentalApply.enabled=true", + "experimentalApply.requested.count=1", + "experimentalApply.enabled.count=1", + "experimentalApply.selected.count=1", + "" + ); + } + private static String madFmaMaterializationEvidence( String status, String firstBlocker, @@ -619,6 +757,7 @@ private static String safeLocalCseMaterializationEvidence( return String.join("\n", "safeLocalCseMaterialization.pass.count=1", "safeLocalCseMaterialization.localBinding.count=1", + "safeLocalCseMaterialization.introducedTemporary.count=0", "safeLocalCseMaterialization.candidate.count=1", "safeLocalCseMaterialization.transformedNode.count=" + transformedNodeCount, "safeLocalCseMaterialization.changedMethodBody.count=1", @@ -659,6 +798,13 @@ private static String loopVectorizationMaterializationEvidence( "loopVectorizationMaterialization.bodyTextReplacement.count=1", "loopVectorizationMaterialization.typedBody.materialized.count=1", "loopVectorizationMaterialization.typedBody.invalidated.count=0", + "loopVectorizationMaterialization.typedBody.rebuild.attempted.count=1", + "loopVectorizationMaterialization.typedBody.rebuild.parsed.count=1", + "loopVectorizationMaterialization.typedBody.rebuild.built.count=1", + "loopVectorizationMaterialization.typedBody.rebuild.graphValidated.count=1", + "loopVectorizationMaterialization.typedBody.rebuild.rejected.count=0", + "loopVectorizationMaterialization.typedBody.rebuild.status=validated", + "loopVectorizationMaterialization.typedBody.rebuild.firstBlocker=none", "loopVectorizationMaterialization.skipped.loopShape.count=0", "loopVectorizationMaterialization.skipped.unsupportedWidth.count=0", "loopVectorizationMaterialization.skipped.unsafeLoadPattern.count=0", From 9b4283839f255c92dc3f9c5189e415216b4528d1 Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Wed, 15 Jul 2026 02:34:59 +0300 Subject: [PATCH 29/32] Update ROADMAP.md --- ROADMAP.md | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index bc707cd4..6c7483bb 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -627,6 +627,18 @@ Current rule: keep the existing OpenCL build-time source path working while I3 i `GpuRuntimeCompileArtifactSnapshot` now carries an optional backend-neutral `GpuRuntimeDeviceSelection`. OpenCL attaches the live selection without forcing context creation in mock/custom backends, and `GpuRuntimeCompileArtifactDumper` emits `runtime-device-selection.properties`. The artifact records selected device identity/class/vendor/driver, deterministic candidate scores and rejection state, policy ids/versions, score adjustments, capability facts, quirks, compile-option diagnostics, extension execution outcomes/failure policies, blockers, and final diagnostics. - [ ] Persist remaining extension participation in `IrGpu` and cross-pipeline artifacts. `kernel.irgpu.properties`, optimizer drift artifacts, promotion gates, validation history, and future backend artifacts should record the relevant extension ids, versions, phases, decisions, diagnostics, and whether each extension was advisory or production-affecting. Runtime compile dumps now include a normalized `runtime-extension-participation.properties` aggregate for build-time `IrGpu` validator metadata, device-selection, runtime optimizer, runtime-equivalence evidence, backend-lowerer/module production-affecting participation, and backend compiler-feedback execution reports. The frontend artifact writer now records `ARTIFACT_EMISSION` participation inside `kernel.irgpu.properties`, including paired OpenCL/IrGpu resource diagnostics. OpenCL workload gates, workload summaries, workload-summary CLI output, validation reports, and validation history now surface per-kernel and aggregate `Runtime Extension Participation` evidence, including source breakdowns such as `original-irgpu:ir-validation`, `runtime-ir-optimization`, `runtime-equivalence`, `backend-lowerer`, and `backend-compiler-feedback`. Future backend-specific artifacts still need the same normalized participation model. This keeps generated artifacts auditable when a project depends on company-specific plugins or third-party GPU libraries. +- [ ] Add a first-class compile/runtime lifecycle event bus. + Provide opt-in event listeners with stable immutable event objects for the whole pipeline: descriptor discovery, `IrGpu` load, validation start/end, optimizer-provider discovery, optimizer pass start/end, backend lowerer selection, source-selection decision, backend compilation start/end, compiler feedback parsing, module load, invocation start/end, fallback/rollback, artifact dump, and shutdown. Events must be observable without changing behavior by default, ordered deterministically, failure-isolated, and persisted into diagnostics when enabled. +- [ ] Add safe core hook points for replacing or augmenting library behavior without forking JavaToGpu. + Expose deliberate extension hooks for intrinsic registration, vector/scalar/pointer type descriptors, backend lowerer selection, source emission helpers, artifact emission, diagnostics rendering, device policy, runtime-equivalence execution, and compiler-feedback parsing. Hooks should allow additive behavior and explicit replacement only through permission-aware contracts, versioned extension ids, precedence rules, and audit artifacts; they must not rely on classpath shadowing or hidden monkey-patching. +- [ ] Add an extension override/patch-pack model for company or project-specific integrations. + Define how an external module can package custom rules, descriptors, diagnostics, backend metadata, fallback variants, and policy defaults as one auditable extension pack. The pack should declare compatible JavaToGpu versions, required capabilities, optional backends, production-affecting permissions, and rollback/fallback behavior so users can install or remove it without maintaining a fork. +- [ ] Add developer-friendly hook/event examples and test harnesses. + Provide small example modules that implement a read-only lifecycle listener, a custom validator, a custom device policy, a custom compiler-feedback parser, and a custom artifact emitter. Add fixture utilities so extension authors can run their hooks against synthetic descriptors/`IrGpu` artifacts without needing real OpenCL hardware for every test. +- [ ] Add JavaDocs for the public Core/API/SPI surface. + Prioritize JavaDocs for annotations, `GPU` facade helpers, runtime compile options, runtime scopes/backends, structured runtime exceptions, `IrGpu` public model classes, extension registry types, validation providers, optimizer proposal providers, backend lowerers, device policies, artifact dumpers, and compiler-feedback providers. JavaDocs should explain lifecycle, thread-safety, fail-closed behavior, ownership, mutation rules, and whether an API is stable, experimental, or internal. +- [ ] Add QOL diagnostics and discoverability improvements for extension users. + Add concise diagnostics for duplicate extension ids, wrong phase/capability/permission, stale extension versions, missing ServiceLoader descriptors, unsupported backend hooks, denied production-affecting hooks, and policy-gated skips. Prefer actionable messages with class names, extension ids, expected registration files, and next-step hints rather than raw registry exceptions. #### Future IR optimization passes @@ -785,15 +797,17 @@ If the goal is to move forward pragmatically from the current state, the best or Start with the non-mutating foundation: canonical `IrGpu` artifact, dual output beside current OpenCL source, OpenCL-from-`IrGpu` parity, runtime compile request/options, backend-lowering boundary, and source/ASM frontends feeding the same IR storage path. Keep runtime optimization profile `off` by default. 4. `I4 Optional IR optimizer module split` Before implementing real optimizer transforms, freeze the module boundary: validator stays read-only, backend-neutral optimizer contracts live behind optional dependencies, vendor optimizers are separate providers, and all mutation remains proposal/opt-in/fail-closed until proof and rollback are present. -5. `I4.2 IR-Optimizer v2 e-graph core` +5. `I3.7 Core extension hooks, lifecycle events, JavaDocs, and QOL` + Before adding more optimizer complexity, harden the Core extension surface: lifecycle event bus, safe hook points, extension patch packs, example modules, JavaDocs, test harnesses, and friendlier diagnostics. This lets users observe, customize, and replace parts of the library deliberately without maintaining forks. +6. `I4.2 IR-Optimizer v2 e-graph core` After the current alpha optimizer remains useful in review/apply mode, start the v2 algebraic core on pure expression regions only: typed-IR extraction, guarded rewrite catalog, bounded equality saturation, backend-aware cost model, review evidence, and immutable proposal lowering. Keep existing peephole materializers as fallback/regression fixtures until each family migrates safely. -6. `A1 Intel runner bring-up when hardware exists` +7. `A1 Intel runner bring-up when hardware exists` The remaining cross-vendor production gate stays open until an Intel OpenCL stack can run the same bucket set. -7. `Improved ASM parser/frontend` +8. `Improved ASM parser/frontend` Broader ASM ingestion should lower into `IrGpu` first, then reuse the same runtime compile request, validation, backend lowering, and future optimizer path instead of growing a separate pipeline. The first broader-ASM diagnostics layer is already in place through `asmFailure.*` metadata and public preflight APIs. -8. `I3/I4 prototype runtime optimization` +9. `I3/I4 prototype runtime optimization` After `IrGpu` storage and OpenCL parity are stable, add opt-in prototype optimization with rollback, pre/post runtime-equivalence artifacts, and NVIDIA/AMD evidence first. Keep production mutation disabled until A1/A2 confidence and the future Intel gate are satisfied. -9. `H Lower-priority backend/optimization work` +10. `H Lower-priority backend/optimization work` ## Current Working Conclusion @@ -801,7 +815,7 @@ JavaToGpu is already beyond the "toy compiler" stage. The broad repetitive intrinsic-family generation detour is now closed for current priorities, so the active focus returns to production-core/runtime validation rather than expanding optional API symmetry. -After the first serious dogfooding pass, the main repo-local language/runtime gaps for the selected workload classes are no longer the active blocker. Section C and F1 are closed for current practical workload coverage: 3D launch config, union-style packed views, root-blob ergonomics, launch-sensitive attributes, and the focused packed/root-blob dogfooding slice are all covered. Operational confidence now includes NVIDIA and AMD lanes; Intel remains the remaining hardware validation gap. The next major architecture frontier is the I3 Production IR pipeline plus the I4 optional optimizer split: `IrGpu` should become the stored backend-neutral artifact, OpenCL/CUDA/Vulkan/Metal should become backend lowerers selected at runtime from compile options and device profile, `ir-validation` must remain read-only, and future `ir-optimizer` / `ir-vendor-optimizer` modules should plug in as optional proposal/opt-in/fail-closed providers. The current rule-based IR-Optimizer alpha is a useful evidence/materialization layer, but the long-term v2 core should become an e-graph / equality-saturation optimizer for pure algebraic expression regions so broad generated-form optimization does not devolve into ordering-sensitive hand-written tree conditions. Broader ASM ingestion should feed that same `IrGpu` path instead of creating a second compiler pipeline. +After the first serious dogfooding pass, the main repo-local language/runtime gaps for the selected workload classes are no longer the active blocker. Section C and F1 are closed for current practical workload coverage: 3D launch config, union-style packed views, root-blob ergonomics, launch-sensitive attributes, and the focused packed/root-blob dogfooding slice are all covered. Operational confidence now includes NVIDIA and AMD lanes; Intel remains the remaining hardware validation gap. The next major architecture frontier is the I3 Production IR pipeline plus the I4 optional optimizer split: `IrGpu` should become the stored backend-neutral artifact, OpenCL/CUDA/Vulkan/Metal should become backend lowerers selected at runtime from compile options and device profile, `ir-validation` must remain read-only, and future `ir-optimizer` / `ir-vendor-optimizer` modules should plug in as optional proposal/opt-in/fail-closed providers. Before adding another wave of optimizer logic, the Core extension surface should become a first-class product feature: lifecycle events, safe hook points, extension packs, JavaDocs, examples, and QOL diagnostics should let users observe and customize the library without maintaining forks. The current rule-based IR-Optimizer alpha is a useful evidence/materialization layer, but the long-term v2 core should become an e-graph / equality-saturation optimizer for pure algebraic expression regions so broad generated-form optimization does not devolve into ordering-sensitive hand-written tree conditions. Broader ASM ingestion should feed that same `IrGpu` path instead of creating a second compiler pipeline. Latest I2/L2 reconciliation: the current CI contract layer is closed for present priorities. Direct contract coverage now pins current readiness CI summaries, optimizer-gate consistency/acceptance, optimizer CI gate-index consistency/acceptance, regression/baseline CI summaries, nested artifact-field composition, and opt-in real-example dogfooding reports for `examples-app` / `test-app`. The dogfooding contract keeps safety fail-fast while treating optimizer readiness as a read-only stability artifact: production mutation must remain disabled, rewrite applicability must stay false, known CSE policy blockers are counted, and no-candidate CSE literal-promotion / auto-vectorization blockers are accepted as the current baseline. The remaining I2 items are intentionally broader frontier work: deeper transformation-safety proofs, stronger canonicalization/common-computation detection, runtime-equivalence evidence for selected optimizer families, and eventual production rewrite promotion after A1/A2 runtime confidence is stable. These should stay open until backed by runtime evidence rather than being marked complete from read-only artifact coverage alone. From b39b72bd85cf64c91d21e68e0d5be8a74b3a61bd Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Wed, 15 Jul 2026 14:01:41 +0300 Subject: [PATCH 30/32] refactor: Clean API & add Services hook --- .github/workflows/opencl-vendor-matrix.yaml | 61 +- README.md | 2 +- docs/API-Overview.md | 2 +- docs/OpenCL-Runner-Contract.md | 11 +- docs/Public-API-And-Extension-Contract.md | 4 +- docs/Runtime-Guide.md | 2 +- docs/Validation-and-Operations.md | 2 +- examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md | 61 ++ examples-app/build.gradle | 15 +- .../ExampleLifecycleTraceService.java | 77 ++ .../examples/OptimizationJournalExample.java | 47 +- ...vatogpu.runtime.GpuRuntimeLifecycleService | 1 + .../ExampleLifecycleTraceServiceTest.java | 111 +++ .../OptimizationJournalExampleTest.java | 20 +- .../extension/GpuExtensionCapability.java | 1 + .../extension/GpuExtensionPhase.java | 1 + .../runtime/GpuRuntimeLifecycleEvent.java | 92 ++ .../runtime/GpuRuntimeLifecycleEventBus.java | 107 +++ .../runtime/GpuRuntimeLifecycleEventKind.java | 33 + .../GpuRuntimeLifecycleEventListener.java | 35 + .../GpuRuntimeLifecycleEventReport.java | 54 ++ ...puRuntimeLifecycleFileJournalListener.java | 198 +++++ .../runtime/GpuRuntimeLifecycleService.java | 12 + .../opencl/OpenClGpuRuntimeBackend.java | 831 ++++++++++++++++-- ...vatogpu.runtime.GpuRuntimeLifecycleService | 1 + .../GpuRuntimeLifecycleEventBusTest.java | 157 ++++ ...ntimeLifecycleFileJournalListenerTest.java | 144 +++ .../opencl/OpenClGpuRuntimeBackendTest.java | 114 +++ .../opencl/OpenClValidationReportTest.java | 12 + 29 files changed, 2120 insertions(+), 88 deletions(-) create mode 100644 examples-app/src/main/java/net/sixik/ga_utils/examples/ExampleLifecycleTraceService.java create mode 100644 examples-app/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleService create mode 100644 examples-app/src/test/java/net/sixik/ga_utils/examples/ExampleLifecycleTraceServiceTest.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleEvent.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleEventBus.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleEventKind.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleEventListener.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleEventReport.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleFileJournalListener.java create mode 100644 processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleService.java create mode 100644 processor/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleService create mode 100644 processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleEventBusTest.java create mode 100644 processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleFileJournalListenerTest.java diff --git a/.github/workflows/opencl-vendor-matrix.yaml b/.github/workflows/opencl-vendor-matrix.yaml index 88fb8872..05fdff23 100644 --- a/.github/workflows/opencl-vendor-matrix.yaml +++ b/.github/workflows/opencl-vendor-matrix.yaml @@ -26,6 +26,8 @@ on: options: - all - nvidia + - nvidia-rtx5070 + - nvidia-rtx3060 - amd production_promotion_manifest_mode: description: 'Manual production-promotion mode: skip, template, validate, or activate.' @@ -67,12 +69,17 @@ jobs: fail-fast: false matrix: >- ${{ fromJSON( - github.event.inputs.validation_lane == 'nvidia' && + github.event_name == 'workflow_dispatch' && github.event.inputs.validation_lane == 'nvidia-rtx5070' && '{"include":[{"vendor":"nvidia","vendor_name":"NVIDIA","device_name":"RTX 5070","artifact_suffix":"nvidia-rtx5070","runner_labels":["self-hosted","Windows","X64","nvidia","rtx5070"],"routine_task":":processor:openClNvidiaRoutine"}]}' - || github.event.inputs.validation_lane == 'amd' && + || github.event_name == 'workflow_dispatch' && github.event.inputs.validation_lane == 'nvidia-rtx3060' && + '{"include":[{"vendor":"nvidia","vendor_name":"NVIDIA","device_name":"RTX 3060","artifact_suffix":"nvidia-rtx3060","runner_labels":["self-hosted","Windows","X64","nvidia","rtx3060"],"routine_task":":processor:openClNvidiaRoutine"}]}' + || + github.event_name == 'workflow_dispatch' && github.event.inputs.validation_lane == 'nvidia' && + '{"include":[{"vendor":"nvidia","vendor_name":"NVIDIA","device_name":"RTX 5070","artifact_suffix":"nvidia-rtx5070","runner_labels":["self-hosted","Windows","X64","nvidia","rtx5070"],"routine_task":":processor:openClNvidiaRoutine"},{"vendor":"nvidia","vendor_name":"NVIDIA","device_name":"RTX 3060","artifact_suffix":"nvidia-rtx3060","runner_labels":["self-hosted","Windows","X64","nvidia","rtx3060"],"routine_task":":processor:openClNvidiaRoutine"}]}' + || github.event_name == 'workflow_dispatch' && github.event.inputs.validation_lane == 'amd' && '{"include":[{"vendor":"amd","vendor_name":"AMD","device_name":"RX 7800 XT","artifact_suffix":"amd-rx7800xt","runner_labels":["self-hosted","Windows","X64","amd","rx7800xt"],"routine_task":":processor:openClOperationalRoutine"}]}' || - '{"include":[{"vendor":"amd","vendor_name":"AMD","device_name":"RX 7800 XT","artifact_suffix":"amd-rx7800xt","runner_labels":["self-hosted","Windows","X64","amd","rx7800xt"],"routine_task":":processor:openClOperationalRoutine"},{"vendor":"nvidia","vendor_name":"NVIDIA","device_name":"RTX 5070","artifact_suffix":"nvidia-rtx5070","runner_labels":["self-hosted","Windows","X64","nvidia","rtx5070"],"routine_task":":processor:openClNvidiaRoutine"}]}' + '{"include":[{"vendor":"amd","vendor_name":"AMD","device_name":"RX 7800 XT","artifact_suffix":"amd-rx7800xt","runner_labels":["self-hosted","Windows","X64","amd","rx7800xt"],"routine_task":":processor:openClOperationalRoutine"},{"vendor":"nvidia","vendor_name":"NVIDIA","device_name":"RTX 5070","artifact_suffix":"nvidia-rtx5070","runner_labels":["self-hosted","Windows","X64","nvidia","rtx5070"],"routine_task":":processor:openClNvidiaRoutine"},{"vendor":"nvidia","vendor_name":"NVIDIA","device_name":"RTX 3060","artifact_suffix":"nvidia-rtx3060","runner_labels":["self-hosted","Windows","X64","nvidia","rtx3060"],"routine_task":":processor:openClNvidiaRoutine"}]}' ) }} runs-on: ${{ matrix.runner_labels }} @@ -82,11 +89,12 @@ jobs: JTG_VALIDATION_VENDOR: ${{ matrix.vendor }} JTG_VALIDATION_DEVICE: ${{ matrix.device_name }} JTG_OPENCL_CI: 'true' - JTG_SOURCE_PROMOTION_BASELINE_MODE: ${{ github.event.inputs.source_promotion_baseline_mode || 'advisory' }} - JTG_LAUNCH_ADVISORY_NEGATIVE_FIXTURE: ${{ github.event.inputs.launch_advisory_negative_fixture || 'false' }} - JTG_PRODUCTION_PROMOTION_MANIFEST_MODE: ${{ github.event.inputs.production_promotion_manifest_mode || 'skip' }} - JTG_PRODUCTION_PROMOTION_MANIFEST_FILE: ${{ github.event.inputs.production_promotion_manifest_file || 'promotion/opencl/backend-source-promotion-manifest.properties' }} - JTG_PRODUCTION_PROMOTION_CANDIDATE_GIT_SHA: ${{ github.event.inputs.production_promotion_candidate_git_sha || '' }} + JTG_WORKFLOW_EVENT: ${{ github.event_name }} + JTG_SOURCE_PROMOTION_BASELINE_MODE: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.source_promotion_baseline_mode || 'advisory' }} + JTG_LAUNCH_ADVISORY_NEGATIVE_FIXTURE: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.launch_advisory_negative_fixture || 'false' }} + JTG_PRODUCTION_PROMOTION_MANIFEST_MODE: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.production_promotion_manifest_mode || 'skip' }} + JTG_PRODUCTION_PROMOTION_MANIFEST_FILE: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.production_promotion_manifest_file || 'promotion/opencl/backend-source-promotion-manifest.properties' }} + JTG_PRODUCTION_PROMOTION_CANDIDATE_GIT_SHA: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.production_promotion_candidate_git_sha || '' }} GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.console=plain steps: @@ -143,37 +151,53 @@ jobs: run: .\gradlew.bat ${{ matrix.routine_task }} --console=plain --no-daemon - name: Generate production-promotion manifest template - if: steps.opencl_validation.outcome == 'success' && env.JTG_PRODUCTION_PROMOTION_MANIFEST_MODE == 'template' + if: github.event_name == 'workflow_dispatch' && steps.opencl_validation.outcome == 'success' && env.JTG_PRODUCTION_PROMOTION_MANIFEST_MODE == 'template' id: production_promotion_manifest_template continue-on-error: true shell: cmd run: .\gradlew.bat :processor:writeOpenClBackendSourcePromotionManifestTemplate "-PopenClPromotionGitSha=%GITHUB_SHA%" --console=plain --no-daemon - - name: Validate production-promotion manifest + - name: Validate manual production-promotion inputs if: >- + github.event_name == 'workflow_dispatch' && steps.opencl_validation.outcome == 'success' && (env.JTG_PRODUCTION_PROMOTION_MANIFEST_MODE == 'validate' || env.JTG_PRODUCTION_PROMOTION_MANIFEST_MODE == 'activate') + id: production_promotion_inputs + continue-on-error: true + shell: cmd + run: | + if "%JTG_PRODUCTION_PROMOTION_CANDIDATE_GIT_SHA%"=="" ( + echo production_promotion_candidate_git_sha is required for manual validate/activate modes. + exit /b 1 + ) + echo Manual production-promotion inputs look complete. + + - name: Validate production-promotion manifest + if: >- + github.event_name == 'workflow_dispatch' && + steps.production_promotion_inputs.outcome == 'success' && + (env.JTG_PRODUCTION_PROMOTION_MANIFEST_MODE == 'validate' || env.JTG_PRODUCTION_PROMOTION_MANIFEST_MODE == 'activate') id: production_promotion_manifest_validation continue-on-error: true shell: cmd run: .\gradlew.bat :processor:validateOpenClBackendSourcePromotionManifest "-PopenClPromotionManifestFile=%JTG_PRODUCTION_PROMOTION_MANIFEST_FILE%" "-PopenClPromotionGitSha=%JTG_PRODUCTION_PROMOTION_CANDIDATE_GIT_SHA%" --console=plain --no-daemon - name: Validate controlled production activation gate - if: steps.production_promotion_manifest_validation.outcome == 'success' && env.JTG_PRODUCTION_PROMOTION_MANIFEST_MODE == 'activate' + if: github.event_name == 'workflow_dispatch' && steps.production_promotion_manifest_validation.outcome == 'success' && env.JTG_PRODUCTION_PROMOTION_MANIFEST_MODE == 'activate' id: production_promotion_activation_gate continue-on-error: true shell: cmd run: .\gradlew.bat :processor:validateOpenClBackendSourcePromotionActivationGate "-PopenClPromotionManifestFile=%JTG_PRODUCTION_PROMOTION_MANIFEST_FILE%" "-PopenClPromotionGitSha=%JTG_PRODUCTION_PROMOTION_CANDIDATE_GIT_SHA%" --console=plain --no-daemon - name: Run controlled production activation-token smoke - if: steps.production_promotion_activation_gate.outcome == 'success' && env.JTG_PRODUCTION_PROMOTION_MANIFEST_MODE == 'activate' + if: github.event_name == 'workflow_dispatch' && steps.production_promotion_activation_gate.outcome == 'success' && env.JTG_PRODUCTION_PROMOTION_MANIFEST_MODE == 'activate' id: production_promotion_activation_token_smoke continue-on-error: true shell: cmd run: .\gradlew.bat :processor:openClProductionActivationTokenSmokeTest "-PopenClPromotionManifestFile=%JTG_PRODUCTION_PROMOTION_MANIFEST_FILE%" "-PopenClPromotionGitSha=%JTG_PRODUCTION_PROMOTION_CANDIDATE_GIT_SHA%" --console=plain --no-daemon - name: Run production activation-token negative controls - if: steps.production_promotion_activation_token_smoke.outcome == 'success' && env.JTG_PRODUCTION_PROMOTION_MANIFEST_MODE == 'activate' + if: github.event_name == 'workflow_dispatch' && steps.production_promotion_activation_token_smoke.outcome == 'success' && env.JTG_PRODUCTION_PROMOTION_MANIFEST_MODE == 'activate' id: production_promotion_activation_token_negative continue-on-error: true shell: cmd @@ -334,6 +358,7 @@ jobs: PRODUCTION_EXPLAINABILITY_SUMMARY_OUTCOME: ${{ steps.production_explainability_summary.outcome }} SOURCE_PROMOTION_BASELINE_OUTCOME: ${{ steps.source_promotion_baseline.outcome }} SOURCE_PROMOTION_SUMMARY_OUTCOME: ${{ steps.source_promotion_summary.outcome }} + PRODUCTION_PROMOTION_INPUTS_OUTCOME: ${{ steps.production_promotion_inputs.outcome }} PRODUCTION_PROMOTION_MANIFEST_TEMPLATE_OUTCOME: ${{ steps.production_promotion_manifest_template.outcome }} PRODUCTION_PROMOTION_MANIFEST_VALIDATION_OUTCOME: ${{ steps.production_promotion_manifest_validation.outcome }} PRODUCTION_PROMOTION_ACTIVATION_GATE_OUTCOME: ${{ steps.production_promotion_activation_gate.outcome }} @@ -358,6 +383,7 @@ jobs: echo ^| Production explainability summary ^| %PRODUCTION_EXPLAINABILITY_SUMMARY_OUTCOME% ^|>>"%GITHUB_STEP_SUMMARY%" echo ^| Source-promotion baseline ^| %SOURCE_PROMOTION_BASELINE_OUTCOME% ^|>>"%GITHUB_STEP_SUMMARY%" echo ^| Source-promotion summary ^| %SOURCE_PROMOTION_SUMMARY_OUTCOME% ^|>>"%GITHUB_STEP_SUMMARY%" + echo ^| Promotion input guard ^| %PRODUCTION_PROMOTION_INPUTS_OUTCOME% ^|>>"%GITHUB_STEP_SUMMARY%" echo ^| Promotion manifest template ^| %PRODUCTION_PROMOTION_MANIFEST_TEMPLATE_OUTCOME% ^|>>"%GITHUB_STEP_SUMMARY%" echo ^| Promotion manifest validation ^| %PRODUCTION_PROMOTION_MANIFEST_VALIDATION_OUTCOME% ^|>>"%GITHUB_STEP_SUMMARY%" echo ^| Controlled activation gate ^| %PRODUCTION_PROMOTION_ACTIVATION_GATE_OUTCOME% ^|>>"%GITHUB_STEP_SUMMARY%" @@ -399,16 +425,23 @@ jobs: steps.launch_advisory_negative_fixture_check.outcome != 'success' ) || ( + github.event_name == 'workflow_dispatch' && env.JTG_PRODUCTION_PROMOTION_MANIFEST_MODE == 'template' && steps.production_promotion_manifest_template.outcome != 'success' ) || ( + github.event_name == 'workflow_dispatch' && env.JTG_PRODUCTION_PROMOTION_MANIFEST_MODE == 'validate' && - steps.production_promotion_manifest_validation.outcome != 'success' + ( + steps.production_promotion_inputs.outcome != 'success' || + steps.production_promotion_manifest_validation.outcome != 'success' + ) ) || ( + github.event_name == 'workflow_dispatch' && env.JTG_PRODUCTION_PROMOTION_MANIFEST_MODE == 'activate' && ( + steps.production_promotion_inputs.outcome != 'success' || steps.production_promotion_manifest_validation.outcome != 'success' || steps.production_promotion_activation_gate.outcome != 'success' || steps.production_promotion_activation_token_smoke.outcome != 'success' || diff --git a/README.md b/README.md index 866c956e..e169b3cd 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ Run the optional IR optimizer journal example: .\gradlew.bat :examples-app:runOptimizationJournalExample --console=plain ``` -The example is documented in [examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md](examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md) and shows how to opt into the optimizer module while dumping original and optimized IR artifacts for review. +The example is documented in [examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md](examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md) and shows how to opt into the optimizer module while dumping original and optimized IR artifacts plus the optional ServiceLoader-backed runtime lifecycle event journal for review. It also includes a small custom `GpuRuntimeLifecycleService` example so downstream modules can add tracing or metrics without manual callback registration. ## Project Layout diff --git a/docs/API-Overview.md b/docs/API-Overview.md index fac0af90..e54dc8ba 100644 --- a/docs/API-Overview.md +++ b/docs/API-Overview.md @@ -122,7 +122,7 @@ The reporter compares current counts and kernel snapshots with the latest compat The vendor workflow exposes an opt-in `launch_advisory_negative_fixture` dispatch input. It mutates only the restored validation-history baseline, verifies the real per-kernel validator rejects the synthetic kernel-maximum regression, and prevents cache staging or save for the fixture run. -Manual dispatch also accepts `validation_lane=all|nvidia|amd` to construct only the requested self-hosted vendor matrix entries. +Manual dispatch also accepts `validation_lane=all|nvidia|nvidia-rtx5070|nvidia-rtx3060|amd`; `nvidia` runs both NVIDIA devices, while the device-specific selectors construct only the requested self-hosted runner entry. The vendor workflow persists validation history through a per-lane cache and keeps the restored baseline immutable for the duration of the run, preventing same-run report regeneration from masking cross-run drift. diff --git a/docs/OpenCL-Runner-Contract.md b/docs/OpenCL-Runner-Contract.md index d2b5041f..167bbe15 100644 --- a/docs/OpenCL-Runner-Contract.md +++ b/docs/OpenCL-Runner-Contract.md @@ -12,9 +12,16 @@ This page describes the expected contract for future Intel, NVIDIA, and AMD vali ## Expected Labels - `self-hosted` -- `windows` -- `opencl` +- `Windows` +- `X64` - vendor-specific label such as `intel`, `nvidia`, or `amd` +- device-specific label for CI matrix routing, for example `rtx3060`, `rtx5070`, or `rx7800xt` + +The current GitHub workflow routes NVIDIA RTX 3060 machines with: + +```text +self-hosted, Windows, X64, nvidia, rtx3060 +``` ## Expected Task Set diff --git a/docs/Public-API-And-Extension-Contract.md b/docs/Public-API-And-Extension-Contract.md index 21cf02a6..e65fdcd4 100644 --- a/docs/Public-API-And-Extension-Contract.md +++ b/docs/Public-API-And-Extension-Contract.md @@ -222,7 +222,9 @@ The shared metadata foundation is available through: - `GpuExtensionDescriptor` for validated immutable metadata; - `GpuExtensionRegistry` for duplicate-id rejection, deterministic audit ordering, and properties-compatible field export. -`GpuRuntimeIrOptimizationPass` declares the `RUNTIME_IR_OPTIMIZATION` phase, `IR_OPTIMIZATION_PROPOSAL` capability, and `MUTATION_PROPOSAL` permission. `GpuIrValidationProvider` declares the `IR_VALIDATION` phase and capability with `READ_ONLY` permission. `GpuBackendCompilerFeedbackProvider` declares the `BACKEND_COMPILER_FEEDBACK` phase and `COMPILER_FEEDBACK` capability with `READ_ONLY` permission. Explicitly supplied pass/provider lists retain caller order where execution order is semantic; ServiceLoader registries sort by extension order, id, and version before execution. +`GpuRuntimeIrOptimizationPass` declares the `RUNTIME_IR_OPTIMIZATION` phase, `IR_OPTIMIZATION_PROPOSAL` capability, and `MUTATION_PROPOSAL` permission. `GpuIrValidationProvider` declares the `IR_VALIDATION` phase and capability with `READ_ONLY` permission. `GpuBackendCompilerFeedbackProvider` declares the `BACKEND_COMPILER_FEEDBACK` phase and `COMPILER_FEEDBACK` capability with `READ_ONLY` permission. `GpuRuntimeLifecycleService` is the ServiceLoader-facing hook for runtime lifecycle journaling, metrics, tracing, and diagnostics; it declares the `RUNTIME_LIFECYCLE` phase, `RUNTIME_LIFECYCLE_EVENT_LISTENER` capability, and `READ_ONLY` permission through its listener contract. Explicitly supplied pass/provider lists retain caller order where execution order is semantic; ServiceLoader registries sort by extension order, id, and version before execution. + +Runtime lifecycle hooks should be added as services, not by application-side callback registration. A module implements `GpuRuntimeLifecycleService` and registers the implementation in `META-INF/services/net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleService`; putting that module on the runtime classpath is enough for the OpenCL backend to discover it. The lower-level `GpuRuntimeLifecycleEventListener` remains the event handling contract and a compatibility bridge for legacy ServiceLoader descriptors, but user code should not need a `Journal.listenerAdd(...)` style API. Extension ids must be unique inside a pipeline. Blank metadata, whitespace/control characters in ids or versions, missing capabilities, null entries, duplicate ids, wrong phases, excessive permissions, and missing required capabilities are rejected before execution. diff --git a/docs/Runtime-Guide.md b/docs/Runtime-Guide.md index 87cbc452..d6e3a2f8 100644 --- a/docs/Runtime-Guide.md +++ b/docs/Runtime-Guide.md @@ -295,7 +295,7 @@ GitHub Actions restores an immutable per-lane/per-branch validation-history cach Manual `workflow_dispatch` runs can enable `launch_advisory_negative_fixture`. This mode requires a baseline containing per-kernel snapshots, increases one kernel maximum only in the restored baseline, and expects the normal drift validator to reject the resulting current-vs-baseline reduction. History staging and cache save are disabled unconditionally in this mode. The workflow succeeds only when fixture preparation succeeds, the drift step fails, and both cache steps remain skipped. -The same dispatch form exposes `validation_lane=all|nvidia|amd`. Selecting one vendor builds the matrix with only that self-hosted runner, so an offline AMD or NVIDIA machine does not leave the unrelated validation run queued. +The same dispatch form exposes `validation_lane=all|nvidia|nvidia-rtx5070|nvidia-rtx3060|amd`. Selecting `nvidia` runs both NVIDIA devices, while a device-specific selector builds only that self-hosted runner entry so an offline unrelated GPU does not leave the validation run queued. ## Runtime Failures And Fallbacks diff --git a/docs/Validation-and-Operations.md b/docs/Validation-and-Operations.md index 9cee2624..69f2e568 100644 --- a/docs/Validation-and-Operations.md +++ b/docs/Validation-and-Operations.md @@ -101,7 +101,7 @@ For local pre/post optimizer inspection, set `-Djavatogpu.opencl.runtimeCompileA The candidate gate combines the real-workload gate with controlled source-switching acceptance for the same kernel resources and device identity. `review-ready` means the candidate evidence is complete; default production source switching and production mutation remain disabled. -Manual `workflow_dispatch` runs expose `production_promotion_manifest_mode=skip|template|validate|activate`. Use `template` to archive a pending device-specific manifest bound to that run's `github.sha`. After approving and committing the manifest, use `validate` with `production_promotion_manifest_file`, the original SHA in `production_promotion_candidate_git_sha`, and a single matching `validation_lane`. Candidate SHA-256 and identity bindings prevent reuse for another GPU, driver, candidate artifact, or source state. +Manual `workflow_dispatch` runs expose `production_promotion_manifest_mode=skip|template|validate|activate`. Use `template` to archive a pending device-specific manifest bound to that run's `github.sha`. After approving and committing the manifest, use `validate` with `production_promotion_manifest_file`, the original SHA in `production_promotion_candidate_git_sha`, and a single matching device `validation_lane` such as `nvidia-rtx5070`, `nvidia-rtx3060`, or `amd`. Candidate SHA-256 and identity bindings prevent reuse for another GPU, driver, candidate artifact, or source state. Use `activate` only after manifest validation succeeds. The workflow then writes and validates the controlled activation gate plus its SHA-256 sidecar, loads the exact artifact into a `GpuProductionActivationToken`, and runs every approved real workload kernel on the selected device. The positive hardware result is written to `production-activation-token-smoke.properties` with per-kernel status and workload coverage. A required negative lane then verifies that a mismatched SHA-256 is rejected and that an unapproved kernel is blocked before output mutation; its result is written to `production-activation-token-negative.properties`. The `activate` lane fails if either check fails. This mode does not enable default runtime activation, default production source switching, or production mutation. diff --git a/examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md b/examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md index 60bc95d9..423cb87a 100644 --- a/examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md +++ b/examples-app/IR_OPTIMIZER_JOURNAL_EXAMPLE.md @@ -7,6 +7,8 @@ The important source files are: ```text examples-app/build.gradle examples-app/src/main/java/net/sixik/ga_utils/examples/OptimizationJournalExample.java +examples-app/src/main/java/net/sixik/ga_utils/examples/ExampleLifecycleTraceService.java +examples-app/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleService ``` `examples-app/build.gradle` adds the optional optimizer module and registers a dedicated runnable task: @@ -36,6 +38,43 @@ It also enables the runtime artifact journal by setting: javatogpu.opencl.runtimeCompileArtifactDirectory= ``` +The example also enables the optional runtime lifecycle event journal: + +```text +javatogpu.runtime.lifecycleJournalFile=/runtime-lifecycle.jsonl +javatogpu.runtime.lifecycleJournalFormat=jsonl +``` + +This records the runtime path as events such as IR load, validation, optimizer discovery/pass, backend lowerer selection, +source selection, backend compilation, artifact dump, invocation, and shutdown. The built-in file journal is discovered +through the service descriptor `META-INF/services/net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleService`, but it +stays disabled unless `javatogpu.runtime.lifecycleJournalFile` is configured. + +Custom lifecycle hooks should follow the same service pattern: implement `GpuRuntimeLifecycleService`, put the +implementation class name into `META-INF/services/net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleService`, and +add that JAR/module to the runtime classpath. Applications should not need to call a manual `listenerAdd`/callback +registration API just to enable journaling, metrics, tracing, or diagnostics. + +The example app includes a tiny custom hook, `ExampleLifecycleTraceService`, to show the full pattern: + +```java +public final class ExampleLifecycleTraceService implements GpuRuntimeLifecycleService { + public void onRuntimeLifecycleEvent(GpuRuntimeLifecycleEvent event) { + // Write metrics, tracing, journal rows, or diagnostics here. + } +} +``` + +It is registered by this service descriptor: + +```text +net.sixik.ga_utils.examples.ExampleLifecycleTraceService +``` + +`ExampleLifecycleTraceService` stays disabled unless `javatogpu.examples.lifecycleTraceFile` is set. The runnable +example sets it automatically to `/example-lifecycle-service.trace`, so you can inspect both the built-in +JSONL lifecycle journal and a custom service output side by side. + ## Run The Example Run it from the repository root: @@ -50,6 +89,18 @@ Use a custom artifact directory: .\gradlew.bat :examples-app:runOptimizationJournalExample -Pjavatogpu.optimizerJournalDir=build\reports\my-ir-journal --console=plain ``` +Write lifecycle events as an indexed `.properties` file instead of JSONL: + +```powershell +.\gradlew.bat :examples-app:runOptimizationJournalExample -Pjavatogpu.lifecycleJournalFile=build\reports\my-ir-journal\runtime-lifecycle.properties -Pjavatogpu.lifecycleJournalFormat=properties --console=plain +``` + +Override the custom example service output path: + +```powershell +.\gradlew.bat :examples-app:runOptimizationJournalExample -Pjavatogpu.exampleLifecycleTraceFile=build\reports\my-ir-journal\example-lifecycle-service.trace --console=plain +``` + ## Journal Files Look for these files under the generated per-kernel subdirectory: @@ -63,6 +114,8 @@ optimized.irgpu.properties runtime-ir-handoff.properties optimizer-report.txt runtime-ir-optimizer-evidence.properties +runtime-lifecycle.jsonl +example-lifecycle-service.trace ``` `original.backend.opencl-c` and `optimized.backend.opencl-c` are the before/after backend source files for comparing generated OpenCL around the optimizer boundary. `optimized.backend.opencl-c` may come from an accumulated review-only materialized candidate, including backend-neutral source materialization metadata, the constrained integer constant-folding transform, safe-local-CSE materialization, the fast-math `mad/fma` peephole materializer, `clamp` materialization, `step` materialization, `mix` materialization, or fixed-width loop vectorization. Constant folding supports plain 32-bit integer literal unary `-`, binary `+`, `-`, `*`, exact `/`, nested safe literal patterns, and the current pure-symbolic identity slice (`x + 0`, `0 + x`, `x - 0`, `x * 1`, `1 * x`, `x / 1`, mixed chains such as `((x / 1) + 0)`, repeated identities across multiple roots, and multiple method bodies) over deterministic retained expressions made from named variables, plain int32 literal leaves, arithmetic binary nodes, and unary minus nodes. Safe-local-CSE materialization reuses existing local bindings in straight-line `ir-text-v1`, and can now introduce conservative local temporaries for repeated pure binary expressions such as `(value * 1.414F)` when all text/typed occurrences line up and no input variable is reassigned between uses. Mad/FMA materialization requires `@GPUOptimize(fastMath = true)` and rewrites reachable `a * b + c` / `c + a * b` text plus typed nodes to `intrinsic(mad template="" args=[a, b, c])`; it also rewrites reachable `a * b - c` to `intrinsic(mad template="" args=[a, b, (-c)])` in the optimized review artifact. `x * 0`, `1 / x`, broader division algebra, control-flow-spanning CSE, reordered clamp/minmax variants, non-mask ternaries, and mix forms outside the current canonical / expanded / MAD-expanded shapes remain excluded for now. `backend.opencl-c` is the selected backend source code that the OpenCL backend actually compiles. `original.irgpu.properties` and `optimized.irgpu.properties` are the matching before/after IR files. `runtime-ir-optimizer-evidence.properties` is the machine-readable guardrail summary that keeps the current alpha fail-closed; for materialized review candidates, check `backendNeutralSourceMaterialization.*`, `constantFoldingMaterialization.*`, `safeLocalCseMaterialization.*`, `madFmaMaterialization.*`, `clampMaterialization.*`, `stepMaterialization.*`, `mixMaterialization.*`, `loopVectorizationMaterialization.*`, `typedDeadCodeMaterialization.*`, `runtimeEquivalenceReview.*`, `approvalTemplate.runtimeEquivalencePayload*.count`, `pass.N.approvalTemplate.field.resourcePath`, `pass.N.approvalTemplate.field.approvalManifest.*`, `reviewPackage.approvalManifest.*`, and the durable `runtime-optimizer-family-equivalence-payload/` files to see source-ready counts, transformed-node counts, literal/identity rewrite counts, fixed-point-pass counts, existing-local and introduced-temporary CSE counts, mad/FMA fast-math policy, clamp/step/mix intrinsic counts, loop-vectorization counts, loop typed-body rebuild status via `loopVectorizationMaterialization.typedBody.rebuild.*`, body-text replacement counts, reachable-node scan / rewrite-granularity fields, per-case method/node identity, exact-int / symbolic-identity / local-CSE-reuse / fast-math-mad / intrinsic-materialization payload evidence, approval payload readiness, loaded/accepted manifest evidence, the review manifest location, and the remaining manual-review/approval blocker. @@ -77,4 +130,12 @@ Clamp, step, and mix materialization are review-only as well. In this example, ` To tell whether `backend.opencl-c` came from optimized IR or from the original/pass-through IR, open `runtime-ir-handoff.properties` and check `selectedStage` plus `optimizedDiffersFromOriginal`. +To inspect the full runtime lifecycle, open `runtime-lifecycle.jsonl`. Each line is one event with stable fields such as +`kind`, `kernelResource`, `optimizationProfile`, `status`, `module.resource`, `module.lowererVersion`, `decision`, +`cacheKey`, and work-size fields like `work.globalX`. + +To inspect the custom service example, open `example-lifecycle-service.trace`. It is deliberately simpler than the +built-in JSONL journal and shows the kind of application-owned trace, metric, or audit output an external module can +write from `GpuRuntimeLifecycleService` without any runtime callback registration code. + Current alpha behavior is intentionally fail-closed by default: optimizer evidence and proposed artifacts can be dumped for review, but production mutation and selected-IR replacement remain disabled unless explicit apply/proof/approval/production gates allow them. If you validate an experimental-apply journal with the raw evidence CLI, use `validateOpenClRuntimeIrOptimizerEvidence --allow-experimental-apply `; without that flag, the validator rejects applied optimized-artifact selection on purpose. diff --git a/examples-app/build.gradle b/examples-app/build.gradle index ed51e8e8..085d4dd8 100644 --- a/examples-app/build.gradle +++ b/examples-app/build.gradle @@ -29,7 +29,8 @@ application { tasks.register('rewriteGpuMethods', JavaExec) { dependsOn project(':processor').tasks.named('classes') dependsOn tasks.named('compileJava') - classpath = project(':processor').sourceSets.main.runtimeClasspath + dependsOn tasks.named('processResources') + classpath = project(':processor').sourceSets.main.runtimeClasspath + configurations.runtimeClasspath mainClass = 'net.sixik.ga_utils.javatogpu.runtime.GpuMethodBodyRewriter' args layout.buildDirectory.dir('classes/java/main').get().asFile.absolutePath } @@ -42,6 +43,18 @@ tasks.register('runOptimizationJournalExample', JavaExec) { if (journalDir.isPresent()) { args journalDir.get() } + def lifecycleJournalFile = providers.gradleProperty('javatogpu.lifecycleJournalFile') + if (lifecycleJournalFile.isPresent()) { + systemProperty 'javatogpu.runtime.lifecycleJournalFile', lifecycleJournalFile.get() + } + def lifecycleJournalFormat = providers.gradleProperty('javatogpu.lifecycleJournalFormat') + if (lifecycleJournalFormat.isPresent()) { + systemProperty 'javatogpu.runtime.lifecycleJournalFormat', lifecycleJournalFormat.get() + } + def exampleLifecycleTraceFile = providers.gradleProperty('javatogpu.exampleLifecycleTraceFile') + if (exampleLifecycleTraceFile.isPresent()) { + systemProperty 'javatogpu.examples.lifecycleTraceFile', exampleLifecycleTraceFile.get() + } } tasks.named('classes') { diff --git a/examples-app/src/main/java/net/sixik/ga_utils/examples/ExampleLifecycleTraceService.java b/examples-app/src/main/java/net/sixik/ga_utils/examples/ExampleLifecycleTraceService.java new file mode 100644 index 00000000..f6565d79 --- /dev/null +++ b/examples-app/src/main/java/net/sixik/ga_utils/examples/ExampleLifecycleTraceService.java @@ -0,0 +1,77 @@ +package net.sixik.ga_utils.examples; + +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleEvent; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleService; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + +/** + * Example runtime lifecycle hook discovered through ServiceLoader. + * + *

The service is intentionally a no-op until {@value #TRACE_FILE_PROPERTY} is set, so adding the example module to a + * classpath does not create files unless the application opts into this particular trace output.

+ */ +public final class ExampleLifecycleTraceService implements GpuRuntimeLifecycleService { + + public static final String TRACE_FILE_PROPERTY = "javatogpu.examples.lifecycleTraceFile"; + + private static final Object WRITE_LOCK = new Object(); + + @Override + public void onRuntimeLifecycleEvent(GpuRuntimeLifecycleEvent event) { + String configuredFile = System.getProperty(TRACE_FILE_PROPERTY); + if (configuredFile == null || configuredFile.isBlank()) { + return; + } + Path traceFile = Path.of(configuredFile.trim()); + synchronized (WRITE_LOCK) { + try { + Files.createDirectories(parentOrCurrent(traceFile)); + Files.writeString( + traceFile, + toTraceLine(event) + System.lineSeparator(), + StandardCharsets.UTF_8, + StandardOpenOption.CREATE, + StandardOpenOption.WRITE, + StandardOpenOption.APPEND + ); + } catch (IOException exception) { + throw new IllegalStateException("Failed to write example lifecycle trace: " + traceFile, exception); + } + } + } + + @Override + public String extensionId() { + return "examples.lifecycle.trace-service"; + } + + @Override + public String extensionVersion() { + return "1"; + } + + @Override + public int extensionOrder() { + return 20_000; + } + + static String toTraceLine(GpuRuntimeLifecycleEvent event) { + String status = event.fields().getOrDefault("status", "unknown"); + return event.kind().name() + + " | backend=" + event.backendTarget().name() + + " | kernel=" + event.kernelResource() + + " | profile=" + event.optimizationProfile() + + " | status=" + status + + " | message=" + event.message(); + } + + private static Path parentOrCurrent(Path path) { + Path parent = path.toAbsolutePath().normalize().getParent(); + return parent == null ? Path.of(".") : parent; + } +} diff --git a/examples-app/src/main/java/net/sixik/ga_utils/examples/OptimizationJournalExample.java b/examples-app/src/main/java/net/sixik/ga_utils/examples/OptimizationJournalExample.java index 3caa55b9..8c23746d 100644 --- a/examples-app/src/main/java/net/sixik/ga_utils/examples/OptimizationJournalExample.java +++ b/examples-app/src/main/java/net/sixik/ga_utils/examples/OptimizationJournalExample.java @@ -22,6 +22,9 @@ public final class OptimizationJournalExample { private static final String ARTIFACT_DIRECTORY_PROPERTY = "javatogpu.opencl.runtimeCompileArtifactDirectory"; + private static final String LIFECYCLE_JOURNAL_FILE_PROPERTY = "javatogpu.runtime.lifecycleJournalFile"; + private static final String LIFECYCLE_JOURNAL_FORMAT_PROPERTY = "javatogpu.runtime.lifecycleJournalFormat"; + private static final String EXAMPLE_LIFECYCLE_TRACE_FILE_PROPERTY = ExampleLifecycleTraceService.TRACE_FILE_PROPERTY; private static final Path DEFAULT_JOURNAL_ROOT = Path.of( "build", "reports", @@ -35,7 +38,10 @@ public final class OptimizationJournalExample { "optimized.irgpu.properties", "runtime-ir-handoff.properties", "optimizer-report.txt", - "runtime-ir-optimizer-evidence.properties" + "runtime-ir-optimizer-evidence.properties", + "runtime-lifecycle.jsonl", + "runtime-lifecycle.properties", + "example-lifecycle-service.trace" ); private static final List EXPECTED_OPTIMIZED_SOURCE_MARKERS = List.of( "mad(", @@ -59,7 +65,19 @@ private OptimizationJournalExample() { public static void main(String[] args) { Path journalRoot = resolveJournalRoot(args); String previousArtifactDirectory = System.getProperty(ARTIFACT_DIRECTORY_PROPERTY); + String previousLifecycleJournalFile = System.getProperty(LIFECYCLE_JOURNAL_FILE_PROPERTY); + String previousLifecycleJournalFormat = System.getProperty(LIFECYCLE_JOURNAL_FORMAT_PROPERTY); + String previousExampleLifecycleTraceFile = System.getProperty(EXAMPLE_LIFECYCLE_TRACE_FILE_PROPERTY); System.setProperty(ARTIFACT_DIRECTORY_PROPERTY, journalRoot.toString()); + if (previousLifecycleJournalFile == null || previousLifecycleJournalFile.isBlank()) { + System.setProperty(LIFECYCLE_JOURNAL_FILE_PROPERTY, resolveLifecycleJournalFile(journalRoot).toString()); + } + if (previousLifecycleJournalFormat == null || previousLifecycleJournalFormat.isBlank()) { + System.setProperty(LIFECYCLE_JOURNAL_FORMAT_PROPERTY, "jsonl"); + } + if (previousExampleLifecycleTraceFile == null || previousExampleLifecycleTraceFile.isBlank()) { + System.setProperty(EXAMPLE_LIFECYCLE_TRACE_FILE_PROPERTY, resolveExampleLifecycleTraceFile(journalRoot).toString()); + } int vectorRows = 4; float[] input = new float[vectorRows * 4]; @@ -76,6 +94,10 @@ public static void main(String[] args) { System.out.println("Optimization profile: " + compileOptions.optimizationProfile()); System.out.println("Artifact journal root: " + journalRoot.toAbsolutePath().normalize()); System.out.println("Runtime artifact property: -D" + ARTIFACT_DIRECTORY_PROPERTY + "=" + journalRoot); + System.out.println("Lifecycle journal property: -D" + LIFECYCLE_JOURNAL_FILE_PROPERTY + + "=" + System.getProperty(LIFECYCLE_JOURNAL_FILE_PROPERTY)); + System.out.println("Example lifecycle service property: -D" + EXAMPLE_LIFECYCLE_TRACE_FILE_PROPERTY + + "=" + System.getProperty(EXAMPLE_LIFECYCLE_TRACE_FILE_PROPERTY)); int sizeX = 16; @@ -114,7 +136,10 @@ public static void main(String[] args) { System.out.println("OpenCL execution failed: " + exception.getMessage()); System.out.println("The example still shows the switches to use; run it on a machine with OpenCL to write artifacts."); } finally { - restoreProperty(previousArtifactDirectory); + restoreProperty(ARTIFACT_DIRECTORY_PROPERTY, previousArtifactDirectory); + restoreProperty(LIFECYCLE_JOURNAL_FILE_PROPERTY, previousLifecycleJournalFile); + restoreProperty(LIFECYCLE_JOURNAL_FORMAT_PROPERTY, previousLifecycleJournalFormat); + restoreProperty(EXAMPLE_LIFECYCLE_TRACE_FILE_PROPERTY, previousExampleLifecycleTraceFile); GpuRuntime.shutdownOpenClSharedCache(); } } @@ -206,6 +231,16 @@ static Path resolveJournalRoot(String[] args) { return DEFAULT_JOURNAL_ROOT; } + static Path resolveLifecycleJournalFile(Path journalRoot) { + Path root = journalRoot == null ? DEFAULT_JOURNAL_ROOT : journalRoot; + return root.resolve("runtime-lifecycle.jsonl"); + } + + static Path resolveExampleLifecycleTraceFile(Path journalRoot) { + Path root = journalRoot == null ? DEFAULT_JOURNAL_ROOT : journalRoot; + return root.resolve("example-lifecycle-service.trace"); + } + static List expectedOptimizedSourceMarkers() { return EXPECTED_OPTIMIZED_SOURCE_MARKERS; } @@ -251,11 +286,11 @@ private static void printJournal(Path journalRoot) { } } - private static void restoreProperty(String previousArtifactDirectory) { - if (previousArtifactDirectory == null) { - System.clearProperty(ARTIFACT_DIRECTORY_PROPERTY); + private static void restoreProperty(String property, String previousValue) { + if (previousValue == null) { + System.clearProperty(property); } else { - System.setProperty(ARTIFACT_DIRECTORY_PROPERTY, previousArtifactDirectory); + System.setProperty(property, previousValue); } } } diff --git a/examples-app/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleService b/examples-app/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleService new file mode 100644 index 00000000..fe671f18 --- /dev/null +++ b/examples-app/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleService @@ -0,0 +1 @@ +net.sixik.ga_utils.examples.ExampleLifecycleTraceService diff --git a/examples-app/src/test/java/net/sixik/ga_utils/examples/ExampleLifecycleTraceServiceTest.java b/examples-app/src/test/java/net/sixik/ga_utils/examples/ExampleLifecycleTraceServiceTest.java new file mode 100644 index 00000000..f5e4bb7d --- /dev/null +++ b/examples-app/src/test/java/net/sixik/ga_utils/examples/ExampleLifecycleTraceServiceTest.java @@ -0,0 +1,111 @@ +package net.sixik.ga_utils.examples; + +import net.sixik.ga_utils.javatogpu.api.GpuBackendTarget; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleEvent; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleEventBus; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleEventKind; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleService; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Enumeration; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ExampleLifecycleTraceServiceTest { + + @TempDir + Path temporaryDirectory; + + @Test + void serviceIsNoOpWhenTraceFileIsNotConfigured() throws Exception { + Path traceFile = temporaryDirectory.resolve("example-lifecycle-service.trace"); + + withTraceFileProperty(null, () -> new ExampleLifecycleTraceService().onRuntimeLifecycleEvent(sampleEvent())); + + assertFalse(Files.exists(traceFile)); + } + + @Test + void writesHumanReadableLifecycleTraceWhenConfigured() throws Exception { + Path traceFile = temporaryDirectory.resolve("example-lifecycle-service.trace"); + + withTraceFileProperty(traceFile, () -> new ExampleLifecycleTraceService().onRuntimeLifecycleEvent(sampleEvent())); + + String trace = Files.readString(traceFile, StandardCharsets.UTF_8); + assertTrue(trace.contains("BACKEND_COMPILATION_STARTED"), trace); + assertTrue(trace.contains("backend=OPENCL"), trace); + assertTrue(trace.contains("kernel=javatogpu/sample/Kernel.cl"), trace); + assertTrue(trace.contains("profile=diagnostic"), trace); + assertTrue(trace.contains("status=started"), trace); + } + + @Test + void serviceLoaderBusCanWriteThroughExampleLifecycleTraceService() throws Exception { + Path traceFile = temporaryDirectory.resolve("service-loader-example-lifecycle.trace"); + + withTraceFileProperty(traceFile, () -> GpuRuntimeLifecycleEventBus.loadFromServiceLoader() + .publish(sampleEvent())); + + String trace = Files.readString(traceFile, StandardCharsets.UTF_8); + assertTrue(trace.contains("BACKEND_COMPILATION_STARTED"), trace); + assertTrue(trace.contains("backend=OPENCL"), trace); + } + + @Test + void serviceDescriptorRegistersExampleLifecycleTraceService() throws Exception { + String servicePath = "META-INF/services/" + GpuRuntimeLifecycleService.class.getName(); + Enumeration resources = Thread.currentThread().getContextClassLoader().getResources(servicePath); + StringBuilder descriptors = new StringBuilder(); + boolean found = false; + while (resources.hasMoreElements()) { + URL resource = resources.nextElement(); + try (var input = resource.openStream()) { + String descriptor = new String(input.readAllBytes(), StandardCharsets.UTF_8); + descriptors.append(resource).append(System.lineSeparator()).append(descriptor).append(System.lineSeparator()); + found |= descriptor.contains(ExampleLifecycleTraceService.class.getName()); + } + } + assertTrue(found, descriptors.toString()); + } + + private static GpuRuntimeLifecycleEvent sampleEvent() { + return new GpuRuntimeLifecycleEvent( + GpuRuntimeLifecycleEventKind.BACKEND_COMPILATION_STARTED, + GpuBackendTarget.OPENCL, + "javatogpu/sample/Kernel.cl", + "diagnostic", + "backend compile started", + Map.of("status", "started") + ); + } + + private static void withTraceFileProperty(Path traceFile, ThrowingRunnable action) throws Exception { + String previous = System.getProperty(ExampleLifecycleTraceService.TRACE_FILE_PROPERTY); + try { + if (traceFile == null) { + System.clearProperty(ExampleLifecycleTraceService.TRACE_FILE_PROPERTY); + } else { + System.setProperty(ExampleLifecycleTraceService.TRACE_FILE_PROPERTY, traceFile.toString()); + } + action.run(); + } finally { + if (previous == null) { + System.clearProperty(ExampleLifecycleTraceService.TRACE_FILE_PROPERTY); + } else { + System.setProperty(ExampleLifecycleTraceService.TRACE_FILE_PROPERTY, previous); + } + } + } + + @FunctionalInterface + private interface ThrowingRunnable { + void run() throws Exception; + } +} diff --git a/examples-app/src/test/java/net/sixik/ga_utils/examples/OptimizationJournalExampleTest.java b/examples-app/src/test/java/net/sixik/ga_utils/examples/OptimizationJournalExampleTest.java index 57468ff0..696cf48b 100644 --- a/examples-app/src/test/java/net/sixik/ga_utils/examples/OptimizationJournalExampleTest.java +++ b/examples-app/src/test/java/net/sixik/ga_utils/examples/OptimizationJournalExampleTest.java @@ -43,6 +43,20 @@ void resolvesCustomJournalRootFromFirstArgument() { assertEquals(custom, OptimizationJournalExample.resolveJournalRoot(new String[]{custom.toString()})); } + @Test + void resolvesLifecycleJournalFileUnderJournalRoot() { + Path custom = temporaryDirectory.resolve("custom-journal"); + + assertEquals(custom.resolve("runtime-lifecycle.jsonl"), OptimizationJournalExample.resolveLifecycleJournalFile(custom)); + } + + @Test + void resolvesExampleLifecycleTraceFileUnderJournalRoot() { + Path custom = temporaryDirectory.resolve("custom-journal"); + + assertEquals(custom.resolve("example-lifecycle-service.trace"), OptimizationJournalExample.resolveExampleLifecycleTraceFile(custom)); + } + @Test void listsOnlyRelevantJournalArtifacts() throws Exception { Path kernelDirectory = temporaryDirectory.resolve("kernel-a"); @@ -55,11 +69,13 @@ void listsOnlyRelevantJournalArtifacts() throws Exception { Files.writeString(kernelDirectory.resolve("runtime-ir-handoff.properties"), "handoff\n"); Files.writeString(kernelDirectory.resolve("optimizer-report.txt"), "report\n"); Files.writeString(kernelDirectory.resolve("runtime-ir-optimizer-evidence.properties"), "evidence\n"); + Files.writeString(temporaryDirectory.resolve("runtime-lifecycle.jsonl"), "{}\n"); + Files.writeString(temporaryDirectory.resolve("example-lifecycle-service.trace"), "trace\n"); Files.writeString(kernelDirectory.resolve("unrelated.properties"), "ignored\n"); List files = OptimizationJournalExample.interestingJournalFiles(temporaryDirectory); - assertEquals(8, files.size()); + assertEquals(10, files.size()); assertTrue(files.stream().anyMatch(path -> path.getFileName().toString().equals("backend.opencl-c"))); assertTrue(files.stream().anyMatch(path -> path.getFileName().toString().equals("original.backend.opencl-c"))); assertTrue(files.stream().anyMatch(path -> path.getFileName().toString().equals("optimized.backend.opencl-c"))); @@ -68,6 +84,8 @@ void listsOnlyRelevantJournalArtifacts() throws Exception { assertTrue(files.stream().anyMatch(path -> path.getFileName().toString().equals("runtime-ir-handoff.properties"))); assertTrue(files.stream().anyMatch(path -> path.getFileName().toString().equals("optimizer-report.txt"))); assertTrue(files.stream().anyMatch(path -> path.getFileName().toString().equals("runtime-ir-optimizer-evidence.properties"))); + assertTrue(files.stream().anyMatch(path -> path.getFileName().toString().equals("runtime-lifecycle.jsonl"))); + assertTrue(files.stream().anyMatch(path -> path.getFileName().toString().equals("example-lifecycle-service.trace"))); } @Test diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/extension/GpuExtensionCapability.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/extension/GpuExtensionCapability.java index d792f813..b4a5b544 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/extension/GpuExtensionCapability.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/extension/GpuExtensionCapability.java @@ -10,6 +10,7 @@ public enum GpuExtensionCapability { BACKEND_LOWERING, COMPILER_FEEDBACK, DEVICE_SELECTION_POLICY, + RUNTIME_LIFECYCLE_EVENT_LISTENER, RUNTIME_EQUIVALENCE_EXECUTOR, PROMOTION_GATE, ARTIFACT_WRITER diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/extension/GpuExtensionPhase.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/extension/GpuExtensionPhase.java index 0c41d62d..828e1c61 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/extension/GpuExtensionPhase.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/extension/GpuExtensionPhase.java @@ -10,6 +10,7 @@ public enum GpuExtensionPhase { BACKEND_LOWERING, BACKEND_COMPILER_FEEDBACK, DEVICE_SELECTION, + RUNTIME_LIFECYCLE, RUNTIME_EQUIVALENCE, PRODUCTION_PROMOTION, ARTIFACT_EMISSION diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleEvent.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleEvent.java new file mode 100644 index 00000000..cc1ea20d --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleEvent.java @@ -0,0 +1,92 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import net.sixik.ga_utils.javatogpu.api.GpuBackendTarget; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Immutable event passed to runtime lifecycle listeners. + * + *

Events are observational only. Listeners receive already-normalized context fields and must not rely on mutating + * this object to influence compilation or execution.

+ */ +public record GpuRuntimeLifecycleEvent( + GpuRuntimeLifecycleEventKind kind, + GpuBackendTarget backendTarget, + String kernelResource, + String optimizationProfile, + String message, + Map fields +) { + + public GpuRuntimeLifecycleEvent { + kind = Objects.requireNonNull(kind, "kind"); + backendTarget = backendTarget == null ? GpuBackendTarget.UNKNOWN : backendTarget; + kernelResource = normalize(kernelResource, "unknown"); + optimizationProfile = normalize(optimizationProfile, "off"); + message = normalize(message, kind.name()); + fields = normalizeFields(fields); + } + + public static GpuRuntimeLifecycleEvent of(GpuRuntimeLifecycleEventKind kind) { + return new GpuRuntimeLifecycleEvent(kind, GpuBackendTarget.UNKNOWN, "unknown", "off", kind.name(), Map.of()); + } + + public GpuRuntimeLifecycleEvent withField(String key, String value) { + LinkedHashMap nextFields = new LinkedHashMap<>(fields); + nextFields.put(requireFieldKey(key), normalize(value, "")); + return new GpuRuntimeLifecycleEvent(kind, backendTarget, kernelResource, optimizationProfile, message, nextFields); + } + + public GpuRuntimeLifecycleEvent withFields(Map additionalFields) { + if (additionalFields == null || additionalFields.isEmpty()) { + return this; + } + LinkedHashMap nextFields = new LinkedHashMap<>(fields); + additionalFields.forEach((key, value) -> nextFields.put(requireFieldKey(key), normalize(value, ""))); + return new GpuRuntimeLifecycleEvent(kind, backendTarget, kernelResource, optimizationProfile, message, nextFields); + } + + public Map artifactFields(String prefix) { + String safePrefix = prefix == null || prefix.isBlank() ? "runtimeLifecycleEvent" : prefix.trim(); + LinkedHashMap artifactFields = new LinkedHashMap<>(); + artifactFields.put(safePrefix + ".kind", kind.name()); + artifactFields.put(safePrefix + ".backendTarget", backendTarget.name()); + artifactFields.put(safePrefix + ".kernelResource", kernelResource); + artifactFields.put(safePrefix + ".optimizationProfile", optimizationProfile); + artifactFields.put(safePrefix + ".message", message); + artifactFields.put(safePrefix + ".field.count", Integer.toString(fields.size())); + int index = 0; + for (Map.Entry entry : fields.entrySet()) { + String entryPrefix = safePrefix + ".field." + index; + artifactFields.put(entryPrefix + ".key", entry.getKey()); + artifactFields.put(entryPrefix + ".value", entry.getValue()); + index++; + } + return Collections.unmodifiableMap(artifactFields); + } + + private static Map normalizeFields(Map fields) { + if (fields == null || fields.isEmpty()) { + return Map.of(); + } + LinkedHashMap normalizedFields = new LinkedHashMap<>(); + fields.forEach((key, value) -> normalizedFields.put(requireFieldKey(key), normalize(value, ""))); + return Collections.unmodifiableMap(normalizedFields); + } + + private static String requireFieldKey(String value) { + String key = normalize(value, ""); + if (key.isBlank()) { + throw new IllegalArgumentException("lifecycle event field key must not be blank"); + } + return key; + } + + private static String normalize(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value.trim(); + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleEventBus.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleEventBus.java new file mode 100644 index 00000000..f3d10af7 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleEventBus.java @@ -0,0 +1,107 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionCapability; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionExecutionReport; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionFailurePolicy; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionPermission; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionPhase; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionRegistry; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.ServiceLoader; + +/** + * Failure-isolated dispatcher for read-only runtime lifecycle listeners. + */ +public final class GpuRuntimeLifecycleEventBus { + + private static final GpuRuntimeLifecycleEventBus EMPTY = new GpuRuntimeLifecycleEventBus(List.of()); + + private final List listeners; + private final GpuExtensionRegistry extensionRegistry; + + private GpuRuntimeLifecycleEventBus(Collection listeners) { + List rawListeners = listeners == null ? List.of() : List.copyOf(listeners); + this.extensionRegistry = GpuExtensionRegistry.of(rawListeners); + this.extensionRegistry.requirePipelineContract( + "runtime lifecycle event bus", + GpuExtensionPhase.RUNTIME_LIFECYCLE, + GpuExtensionPermission.READ_ONLY, + GpuExtensionCapability.RUNTIME_LIFECYCLE_EVENT_LISTENER + ); + LinkedHashMap listenersById = new LinkedHashMap<>(); + for (GpuRuntimeLifecycleEventListener listener : rawListeners) { + listenersById.put(listener.extensionId().trim(), listener); + } + this.listeners = this.extensionRegistry.descriptors().stream() + .map(descriptor -> listenersById.get(descriptor.id())) + .toList(); + } + + public static GpuRuntimeLifecycleEventBus empty() { + return EMPTY; + } + + public static GpuRuntimeLifecycleEventBus of(Collection listeners) { + if (listeners == null || listeners.isEmpty()) { + return empty(); + } + return new GpuRuntimeLifecycleEventBus(listeners); + } + + public static GpuRuntimeLifecycleEventBus loadFromServiceLoader() { + LinkedHashMap listeners = new LinkedHashMap<>(); + ServiceLoader.load(GpuRuntimeLifecycleService.class) + .forEach(listener -> addLoadedListener(listeners, listener)); + ServiceLoader.load(GpuRuntimeLifecycleEventListener.class) + .forEach(listener -> addLoadedListener(listeners, listener)); + return of(listeners.values()); + } + + private static void addLoadedListener( + LinkedHashMap listeners, + GpuRuntimeLifecycleEventListener listener + ) { + Objects.requireNonNull(listeners, "listeners"); + Objects.requireNonNull(listener, "listener"); + listeners.putIfAbsent(listener.getClass().getName(), listener); + } + + public int listenerCount() { + return listeners.size(); + } + + public List listeners() { + return listeners; + } + + public Map extensionArtifactFields(String prefix) { + return extensionRegistry.artifactFields(prefix == null || prefix.isBlank() + ? "runtimeLifecycleExtension" + : prefix.trim()); + } + + public GpuRuntimeLifecycleEventReport publish(GpuRuntimeLifecycleEvent event) { + GpuRuntimeLifecycleEvent value = Objects.requireNonNull(event, "event"); + ArrayList reports = new ArrayList<>(); + for (GpuRuntimeLifecycleEventListener listener : listeners) { + try { + listener.onRuntimeLifecycleEvent(value); + reports.add(GpuExtensionExecutionReport.succeeded(listener, "runtime lifecycle event " + value.kind())); + } catch (RuntimeException failure) { + reports.add(GpuExtensionExecutionReport.failed( + listener, + "runtime lifecycle event " + value.kind(), + GpuExtensionFailurePolicy.CONTINUE, + failure + )); + } + } + return new GpuRuntimeLifecycleEventReport(value, reports); + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleEventKind.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleEventKind.java new file mode 100644 index 00000000..6dc01780 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleEventKind.java @@ -0,0 +1,33 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +/** + * Stable event identifiers for observable runtime and compile lifecycle steps. + */ +public enum GpuRuntimeLifecycleEventKind { + DESCRIPTOR_DISCOVERY_STARTED, + DESCRIPTOR_DISCOVERY_COMPLETED, + IRGPU_LOAD_STARTED, + IRGPU_LOAD_COMPLETED, + VALIDATION_STARTED, + VALIDATION_COMPLETED, + OPTIMIZER_DISCOVERY_STARTED, + OPTIMIZER_DISCOVERY_COMPLETED, + OPTIMIZER_PASS_STARTED, + OPTIMIZER_PASS_COMPLETED, + BACKEND_LOWERER_SELECTION_STARTED, + BACKEND_LOWERER_SELECTION_COMPLETED, + SOURCE_SELECTION_DECIDED, + BACKEND_COMPILATION_STARTED, + BACKEND_COMPILATION_COMPLETED, + COMPILER_FEEDBACK_PARSING_STARTED, + COMPILER_FEEDBACK_PARSING_COMPLETED, + MODULE_LOAD_STARTED, + MODULE_LOAD_COMPLETED, + INVOCATION_STARTED, + INVOCATION_COMPLETED, + FALLBACK_OR_ROLLBACK_SELECTED, + ARTIFACT_DUMP_STARTED, + ARTIFACT_DUMP_COMPLETED, + RUNTIME_SHUTDOWN_STARTED, + RUNTIME_SHUTDOWN_COMPLETED +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleEventListener.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleEventListener.java new file mode 100644 index 00000000..e027c3f6 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleEventListener.java @@ -0,0 +1,35 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import net.sixik.ga_utils.javatogpu.extension.GpuExtension; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionCapability; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionPermission; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionPhase; + +import java.util.Set; + +/** + * Read-only observer for compile/runtime lifecycle events. + * + *

Listeners are intended for diagnostics, journaling, metrics, and test instrumentation. They must not mutate + * runtime state or use exceptions as control flow; the lifecycle event bus isolates listener failures.

+ */ +@FunctionalInterface +public interface GpuRuntimeLifecycleEventListener extends GpuExtension { + + void onRuntimeLifecycleEvent(GpuRuntimeLifecycleEvent event); + + @Override + default Set extensionCapabilities() { + return Set.of(GpuExtensionCapability.RUNTIME_LIFECYCLE_EVENT_LISTENER); + } + + @Override + default GpuExtensionPhase extensionPhase() { + return GpuExtensionPhase.RUNTIME_LIFECYCLE; + } + + @Override + default GpuExtensionPermission extensionPermission() { + return GpuExtensionPermission.READ_ONLY; + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleEventReport.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleEventReport.java new file mode 100644 index 00000000..0cded565 --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleEventReport.java @@ -0,0 +1,54 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionExecutionOutcome; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionExecutionReport; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Dispatch report for one lifecycle event publication. + */ +public record GpuRuntimeLifecycleEventReport( + GpuRuntimeLifecycleEvent event, + List listenerReports +) { + + public GpuRuntimeLifecycleEventReport { + event = event == null ? GpuRuntimeLifecycleEvent.of(GpuRuntimeLifecycleEventKind.RUNTIME_SHUTDOWN_COMPLETED) : event; + listenerReports = listenerReports == null ? List.of() : List.copyOf(listenerReports); + } + + public boolean allListenersSucceeded() { + return listenerReports.stream() + .allMatch(report -> report.outcome() == GpuExtensionExecutionOutcome.SUCCEEDED); + } + + public Map artifactFields(String prefix) { + String safePrefix = prefix == null || prefix.isBlank() ? "runtimeLifecycle" : prefix.trim(); + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put(safePrefix + ".listener.count", Integer.toString(listenerReports.size())); + fields.put(safePrefix + ".listener.allSucceeded", Boolean.toString(allListenersSucceeded())); + fields.putAll(event.artifactFields(safePrefix + ".event")); + for (int index = 0; index < listenerReports.size(); index++) { + fields.putAll(listenerReports.get(index).artifactFields(safePrefix + ".listener." + index)); + } + return Collections.unmodifiableMap(fields); + } + + public String toPropertiesText() { + StringBuilder builder = new StringBuilder(); + artifactFields("runtimeLifecycle").forEach((key, value) -> builder + .append(key) + .append('=') + .append(safePropertyValue(value)) + .append('\n')); + return builder.toString(); + } + + private static String safePropertyValue(String value) { + return value == null ? "" : value.replace("\r", "\\r").replace("\n", "\\n"); + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleFileJournalListener.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleFileJournalListener.java new file mode 100644 index 00000000..5817e61e --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleFileJournalListener.java @@ -0,0 +1,198 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; + +/** + * Optional file-backed lifecycle journal listener for local diagnostics and examples. + * + *

The listener is safe to register by default because it is a no-op until + * {@value #JOURNAL_FILE_PROPERTY} is set. When enabled, it records every runtime lifecycle event as either JSONL or an + * indexed {@code .properties} file. Listener failures are isolated by {@link GpuRuntimeLifecycleEventBus}, so a broken + * journal path cannot control compilation or execution flow.

+ */ +public final class GpuRuntimeLifecycleFileJournalListener implements GpuRuntimeLifecycleService { + + public static final String JOURNAL_FILE_PROPERTY = "javatogpu.runtime.lifecycleJournalFile"; + public static final String JOURNAL_FORMAT_PROPERTY = "javatogpu.runtime.lifecycleJournalFormat"; + + private static final Object WRITE_LOCK = new Object(); + + @Override + public void onRuntimeLifecycleEvent(GpuRuntimeLifecycleEvent event) { + Optional journalFile = configuredJournalFile(); + if (journalFile.isEmpty()) { + return; + } + Path path = journalFile.orElseThrow(); + JournalFormat format = JournalFormat.from(path, System.getProperty(JOURNAL_FORMAT_PROPERTY)); + synchronized (WRITE_LOCK) { + try { + Files.createDirectories(parentOrCurrent(path)); + if (format == JournalFormat.PROPERTIES) { + writeProperties(path, event); + } else { + writeJsonl(path, event); + } + } catch (IOException exception) { + throw new IllegalStateException("Failed to write runtime lifecycle journal: " + path, exception); + } + } + } + + @Override + public String extensionId() { + return "runtime.lifecycle.file-journal"; + } + + @Override + public String extensionVersion() { + return "1"; + } + + @Override + public int extensionOrder() { + return 10_000; + } + + private static Optional configuredJournalFile() { + String value = System.getProperty(JOURNAL_FILE_PROPERTY); + if (value == null || value.isBlank()) { + return Optional.empty(); + } + return Optional.of(Path.of(value.trim())); + } + + private static void writeJsonl(Path path, GpuRuntimeLifecycleEvent event) throws IOException { + Files.writeString( + path, + toJson(event) + System.lineSeparator(), + StandardCharsets.UTF_8, + StandardOpenOption.CREATE, + StandardOpenOption.WRITE, + StandardOpenOption.APPEND + ); + } + + private static void writeProperties(Path path, GpuRuntimeLifecycleEvent event) throws IOException { + Properties properties = new Properties(); + if (Files.isRegularFile(path)) { + try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { + properties.load(reader); + } + } + int index = parseInt(properties.getProperty("event.count"), 0); + properties.setProperty("journal.format", "properties"); + properties.setProperty("journal.version", "1"); + properties.setProperty("event.count", Integer.toString(index + 1)); + event.artifactFields("event." + index).forEach(properties::setProperty); + try (Writer writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) { + properties.store(writer, "JavaToGpu runtime lifecycle journal"); + } + } + + private static String toJson(GpuRuntimeLifecycleEvent event) { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("kind", event.kind().name()); + fields.put("backendTarget", event.backendTarget().name()); + fields.put("kernelResource", event.kernelResource()); + fields.put("optimizationProfile", event.optimizationProfile()); + fields.put("message", event.message()); + fields.put("timestampEpochMillis", Long.toString(System.currentTimeMillis())); + + StringBuilder builder = new StringBuilder(); + builder.append('{'); + appendJsonStringFields(builder, fields); + builder.append(",\"fields\":{"); + appendJsonStringFields(builder, event.fields()); + builder.append("}}"); + return builder.toString(); + } + + private static void appendJsonStringFields(StringBuilder builder, Map fields) { + boolean first = true; + for (Map.Entry entry : fields.entrySet()) { + if (!first) { + builder.append(','); + } + builder.append('"') + .append(jsonEscape(entry.getKey())) + .append("\":\"") + .append(jsonEscape(entry.getValue())) + .append('"'); + first = false; + } + } + + private static String jsonEscape(String value) { + if (value == null) { + return ""; + } + StringBuilder builder = new StringBuilder(value.length() + 8); + for (int index = 0; index < value.length(); index++) { + char current = value.charAt(index); + switch (current) { + case '"' -> builder.append("\\\""); + case '\\' -> builder.append("\\\\"); + case '\b' -> builder.append("\\b"); + case '\f' -> builder.append("\\f"); + case '\n' -> builder.append("\\n"); + case '\r' -> builder.append("\\r"); + case '\t' -> builder.append("\\t"); + default -> { + if (current < 0x20) { + builder.append(String.format("\\u%04x", (int) current)); + } else { + builder.append(current); + } + } + } + } + return builder.toString(); + } + + private static Path parentOrCurrent(Path path) { + Path parent = path.toAbsolutePath().normalize().getParent(); + return parent == null ? Path.of(".") : parent; + } + + private static int parseInt(String value, int fallback) { + if (value == null || value.isBlank()) { + return fallback; + } + try { + return Integer.parseInt(value.trim()); + } catch (NumberFormatException ignored) { + return fallback; + } + } + + private enum JournalFormat { + JSONL, + PROPERTIES; + + private static JournalFormat from(Path path, String configuredFormat) { + String normalized = configuredFormat == null ? "" : configuredFormat.trim().toLowerCase(java.util.Locale.ROOT); + if (normalized.isBlank() || "auto".equals(normalized)) { + String fileName = path.getFileName() == null ? "" : path.getFileName().toString().toLowerCase(java.util.Locale.ROOT); + return fileName.endsWith(".properties") ? PROPERTIES : JSONL; + } + return switch (normalized) { + case "jsonl", "json" -> JSONL; + case "properties", "props" -> PROPERTIES; + default -> throw new IllegalArgumentException( + "Unsupported runtime lifecycle journal format '" + configuredFormat + "'; use jsonl or properties" + ); + }; + } + } +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleService.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleService.java new file mode 100644 index 00000000..69cebd9a --- /dev/null +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleService.java @@ -0,0 +1,12 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +/** + * ServiceLoader-facing runtime lifecycle hook. + * + *

External modules should implement this interface and register their implementation in + * {@code META-INF/services/net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleService}. Applications then enable + * the hook by putting that module on the runtime classpath; they do not need to call an ad-hoc listener registration + * API.

+ */ +public interface GpuRuntimeLifecycleService extends GpuRuntimeLifecycleEventListener { +} diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackend.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackend.java index bf88b1a7..1ac1b35f 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackend.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackend.java @@ -32,6 +32,9 @@ import net.sixik.ga_utils.javatogpu.runtime.GpuBackendSourceSwitchingPolicy; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeApiVersion; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeBackendReport; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleEvent; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleEventBus; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleEventKind; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeFeature; import net.sixik.ga_utils.javatogpu.runtime.GpuKernelParameterDescriptor; import net.sixik.ga_utils.javatogpu.runtime.GpuKernelDescriptor; @@ -47,6 +50,7 @@ import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileOptionsException; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCallSiteResolver; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeDiagnosticContext; +import net.sixik.ga_utils.javatogpu.runtime.GpuExecutionConfig; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeException; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeInvocationException; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeCompileInvalidationStamp; @@ -69,6 +73,7 @@ import net.sixik.ga_utils.javatogpu.runtime.GpuProductionPromotionDecision; import net.sixik.ga_utils.javatogpu.runtime.GpuProductionPromotionOperatorAcceptance; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrArtifactLoader; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationOutcome; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationPassReport; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationReport; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationRequest; @@ -148,6 +153,7 @@ public enum CacheMode { private final GpuRuntimeIrOptimizerRegistry irOptimizerRegistry; private final GpuOptimizationStrategy optimizationStrategy; private final GpuRuntimeDevicePolicyRegistry devicePolicyRegistry; + private final GpuRuntimeLifecycleEventBus lifecycleEventBus; private final ThreadLocal sessionSelectionRequest = new ThreadLocal<>(); private final Map nativeBuffers = new ConcurrentHashMap<>(); private final AtomicLong invocationCount = new AtomicLong(); @@ -178,6 +184,16 @@ protected OpenClGpuRuntimeBackend(CacheMode cacheMode) { this(cacheMode, GpuRuntimeIrOptimizerRegistry.loadFromServiceLoader()); } + protected OpenClGpuRuntimeBackend(CacheMode cacheMode, GpuRuntimeLifecycleEventBus lifecycleEventBus) { + this( + cacheMode, + GpuRuntimeIrOptimizerRegistry.loadFromServiceLoader(), + GpuOptimizationStrategy.advisoryDefault(), + GpuRuntimeDevicePolicyRegistry.loadWithBuiltIns(), + lifecycleEventBus + ); + } + protected OpenClGpuRuntimeBackend(CacheMode cacheMode, GpuRuntimeIrOptimizerRegistry irOptimizerRegistry) { this(cacheMode, irOptimizerRegistry, GpuOptimizationStrategy.advisoryDefault()); } @@ -200,11 +216,28 @@ protected OpenClGpuRuntimeBackend( GpuRuntimeIrOptimizerRegistry irOptimizerRegistry, GpuOptimizationStrategy optimizationStrategy, GpuRuntimeDevicePolicyRegistry devicePolicyRegistry + ) { + this( + cacheMode, + irOptimizerRegistry, + optimizationStrategy, + devicePolicyRegistry, + GpuRuntimeLifecycleEventBus.loadFromServiceLoader() + ); + } + + protected OpenClGpuRuntimeBackend( + CacheMode cacheMode, + GpuRuntimeIrOptimizerRegistry irOptimizerRegistry, + GpuOptimizationStrategy optimizationStrategy, + GpuRuntimeDevicePolicyRegistry devicePolicyRegistry, + GpuRuntimeLifecycleEventBus lifecycleEventBus ) { this.cacheMode = Objects.requireNonNull(cacheMode, "cacheMode"); this.irOptimizerRegistry = Objects.requireNonNull(irOptimizerRegistry, "irOptimizerRegistry"); this.optimizationStrategy = Objects.requireNonNull(optimizationStrategy, "optimizationStrategy"); this.devicePolicyRegistry = Objects.requireNonNull(devicePolicyRegistry, "devicePolicyRegistry"); + this.lifecycleEventBus = lifecycleEventBus == null ? GpuRuntimeLifecycleEventBus.empty() : lifecycleEventBus; } /** @@ -286,9 +319,11 @@ public final void invoke(GpuKernelInvocation invocation) { GpuRuntimeCompileOptions requestCompileOptions = invocation.compileOptions() == null ? GpuRuntimeCompileOptions.defaults(backendTarget()) : invocation.compileOptions(); - Optional primaryIrGpuArtifact = GpuRuntimeIrArtifactLoader.load( + Optional primaryIrGpuArtifact = loadRuntimeIrArtifact( invocation.descriptor(), - invocation.artifactClassLoader() + invocation.artifactClassLoader(), + requestCompileOptions, + "primary" ); GpuRuntimeDiagnosticContext primaryContext = diagnosticContext( invocation.descriptor(), @@ -299,9 +334,30 @@ public final void invoke(GpuKernelInvocation invocation) { invocation.artifactClassLoader(), primaryContext.sourceLocation() )); + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.VALIDATION_STARTED, + invocation.descriptor(), + requestCompileOptions, + "OpenCL compile option validation started", + validationFields("compile-options", "started", null) + ); try { validateCompileOptions(requestCompileOptions); + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.VALIDATION_COMPLETED, + invocation.descriptor(), + requestCompileOptions, + "OpenCL compile option validation completed", + validationFields("compile-options", "succeeded", null) + ); } catch (RuntimeException exception) { + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.VALIDATION_COMPLETED, + invocation.descriptor(), + requestCompileOptions, + "OpenCL compile option validation failed", + validationFields("compile-options", "failed", exception) + ); if (exception instanceof GpuRuntimeException runtimeException) { throw runtimeException; } @@ -311,20 +367,48 @@ public final void invoke(GpuKernelInvocation invocation) { exception ); } - Optional methodVariantSelection = selectMethodVariant( - invocation, - requestCompileOptions + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.DESCRIPTOR_DISCOVERY_STARTED, + invocation.descriptor(), + requestCompileOptions, + "OpenCL method variant descriptor selection started", + descriptorSelectionFields(invocation, Optional.empty(), invocation.descriptor(), "started", null) ); + Optional methodVariantSelection; + try { + methodVariantSelection = selectMethodVariant( + invocation, + requestCompileOptions + ); + } catch (RuntimeException exception) { + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.DESCRIPTOR_DISCOVERY_COMPLETED, + invocation.descriptor(), + requestCompileOptions, + "OpenCL method variant descriptor selection failed", + descriptorSelectionFields(invocation, Optional.empty(), invocation.descriptor(), "failed", exception) + ); + throw exception; + } GpuKernelInvocation selectedInvocation = methodVariantSelection .map(selection -> invocation.withSelectedDescriptor(selection.selectedDescriptor())) .orElse(invocation); + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.DESCRIPTOR_DISCOVERY_COMPLETED, + selectedInvocation.descriptor(), + requestCompileOptions, + "OpenCL method variant descriptor selection completed", + descriptorSelectionFields(invocation, methodVariantSelection, selectedInvocation.descriptor(), "succeeded", null) + ); Optional loadedIrGpuArtifact = methodVariantSelection .flatMap(GpuRuntimeMethodVariantSelection::selectedArtifact) .or(() -> sameDescriptor(invocation.descriptor(), selectedInvocation.descriptor()) ? primaryIrGpuArtifact - : GpuRuntimeIrArtifactLoader.load( + : loadRuntimeIrArtifact( selectedInvocation.descriptor(), - selectedInvocation.artifactClassLoader() + selectedInvocation.artifactClassLoader(), + requestCompileOptions, + "selected-variant" )); GpuRuntimeDiagnosticContext selectedContextBase = diagnosticContext( selectedInvocation.descriptor(), @@ -343,6 +427,13 @@ public final void invoke(GpuKernelInvocation invocation) { } OpenClKernelArguments arguments; OpenClExecutionPlan plan; + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.VALIDATION_STARTED, + selectedInvocation.descriptor(), + requestCompileOptions, + "OpenCL invocation precondition validation started", + validationFields("invocation-preconditions", "started", null) + ); try { arguments = OpenClArgumentMarshaller.marshall( selectedInvocation.descriptor(), @@ -350,7 +441,21 @@ public final void invoke(GpuKernelInvocation invocation) { ); plan = OpenClExecutionPlanner.plan(arguments); validateInvocationPreconditions(selectedInvocation, plan); + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.VALIDATION_COMPLETED, + selectedInvocation.descriptor(), + requestCompileOptions, + "OpenCL invocation precondition validation completed", + validationFields("invocation-preconditions", "succeeded", null) + ); } catch (RuntimeException exception) { + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.VALIDATION_COMPLETED, + selectedInvocation.descriptor(), + requestCompileOptions, + "OpenCL invocation precondition validation failed", + validationFields("invocation-preconditions", "failed", exception) + ); if (exception instanceof GpuRuntimeException runtimeException) { throw runtimeException; } @@ -369,10 +474,31 @@ public final void invoke(GpuKernelInvocation invocation) { selectedContext )); try { - validateActiveSessionSelection(selectedInvocation.descriptor(), requestCompileOptions, loadedIrGpuArtifact); + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.VALIDATION_STARTED, + selectedInvocation.descriptor(), + requestCompileOptions, + "OpenCL runtime capability validation started", + validationFields("runtime-capabilities", "started", null) + ); try { + validateActiveSessionSelection(selectedInvocation.descriptor(), requestCompileOptions, loadedIrGpuArtifact); validateCapabilitySupport(selectedInvocation.descriptor(), plan); + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.VALIDATION_COMPLETED, + selectedInvocation.descriptor(), + requestCompileOptions, + "OpenCL runtime capability validation completed", + validationFields("runtime-capabilities", "succeeded", null) + ); } catch (RuntimeException exception) { + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.VALIDATION_COMPLETED, + selectedInvocation.descriptor(), + requestCompileOptions, + "OpenCL runtime capability validation failed", + validationFields("runtime-capabilities", "failed", exception) + ); if (exception instanceof GpuRuntimeException runtimeException) { throw runtimeException; } @@ -395,12 +521,12 @@ public final void invoke(GpuKernelInvocation invocation) { GpuRuntimeIrOptimizationResult optimizationResult = optimizeRuntimeIrWithReport(compileRequest); GpuRuntimeCompileRequest optimizedCompileRequest = optimizationResult.compileRequest(); GpuBackendModuleArtifact originalModuleArtifact = runtimeCompileArtifactsConfigured() - ? lowerBackendModule(compileRequest) + ? lowerBackendModuleChecked(compileRequest, "original-artifact-dump") : null; GpuBackendModuleArtifact optimizedModuleArtifact = runtimeCompileArtifactsConfigured() || runtimeIrOptimizerExperimentalApplyRequested(optimizedCompileRequest) - ? lowerOptimizedReviewModule(optimizedCompileRequest, optimizationResult.report()) - : lowerBackendModule(optimizedCompileRequest); + ? lowerOptimizedReviewModuleChecked(optimizedCompileRequest, optimizationResult.report(), "optimized-review") + : lowerBackendModuleChecked(optimizedCompileRequest, "optimized-selected"); GpuRuntimeEquivalenceEvidence runtimeEquivalenceEvidence = executeRuntimeEquivalence(new GpuRuntimeEquivalenceRequest( compileRequest, optimizedCompileRequest, @@ -430,7 +556,7 @@ public final void invoke(GpuKernelInvocation invocation) { ? optimizedModuleArtifact : sameSelectedIr(compileRequest, selectedCompileRequest) && originalModuleArtifact != null ? originalModuleArtifact - : lowerBackendModule(selectedCompileRequest); + : lowerBackendModuleChecked(selectedCompileRequest, "runtime-ir-selection"); GpuRuntimeCompileInvalidationStamp invalidationStamp = GpuRuntimeCompileInvalidationStamp.from( selectedCompileRequest, moduleArtifact, @@ -451,14 +577,21 @@ public final void invoke(GpuKernelInvocation invocation) { runtimeEquivalenceEvidence, artifactSnapshotBase.fallbackEvidence() ); + GpuBackendSourceSwitchingDecision sourceSwitchingDecision = backendSourceSwitchingDecision( + selectedCompileRequest, + moduleArtifact, + sourcePromotionGate + ); + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.SOURCE_SELECTION_DECIDED, + selectedCompileRequest, + "OpenCL backend source selection decided", + sourceSelectionFields(moduleArtifact, sourcePromotionGate, sourceSwitchingDecision, runtimeIrSelection) + ); GpuRuntimeCompileArtifactSnapshot artifactSnapshot = withRuntimeDeviceSelection( artifactSnapshotBase.withBackendSourceState( sourcePromotionGate, - backendSourceSwitchingDecision( - selectedCompileRequest, - moduleArtifact, - sourcePromotionGate - ) + sourceSwitchingDecision ), methodVariantSelection ); @@ -514,6 +647,437 @@ private static boolean sameDescriptor(GpuKernelDescriptor left, GpuKernelDescrip && left.irGpuResource().equals(right.irGpuResource()); } + private Optional loadRuntimeIrArtifact( + GpuKernelDescriptor descriptor, + ClassLoader artifactClassLoader, + GpuRuntimeCompileOptions compileOptions, + String loadRole + ) { + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.IRGPU_LOAD_STARTED, + descriptor, + compileOptions, + "Runtime IrGpu artifact load started", + irGpuLoadFields(loadRole, Optional.empty(), "started", null) + ); + try { + Optional artifact = GpuRuntimeIrArtifactLoader.load(descriptor, artifactClassLoader); + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.IRGPU_LOAD_COMPLETED, + descriptor, + compileOptions, + "Runtime IrGpu artifact load completed", + irGpuLoadFields(loadRole, artifact, "succeeded", null) + ); + return artifact; + } catch (RuntimeException exception) { + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.IRGPU_LOAD_COMPLETED, + descriptor, + compileOptions, + "Runtime IrGpu artifact load failed", + irGpuLoadFields(loadRole, Optional.empty(), "failed", exception) + ); + throw exception; + } + } + + private GpuBackendModuleArtifact lowerBackendModuleChecked( + GpuRuntimeCompileRequest compileRequest, + String purpose + ) { + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.BACKEND_LOWERER_SELECTION_STARTED, + compileRequest, + "OpenCL backend lowerer selection started", + backendLowererFields(purpose, null, "started", null) + ); + try { + GpuBackendModuleArtifact moduleArtifact = lowerBackendModule(compileRequest); + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.BACKEND_LOWERER_SELECTION_COMPLETED, + compileRequest, + "OpenCL backend lowerer selection completed", + backendLowererFields(purpose, moduleArtifact, "succeeded", null) + ); + return moduleArtifact; + } catch (RuntimeException exception) { + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.BACKEND_LOWERER_SELECTION_COMPLETED, + compileRequest, + "OpenCL backend lowerer selection failed", + backendLowererFields(purpose, null, "failed", exception) + ); + throw exception; + } + } + + private GpuBackendModuleArtifact lowerOptimizedReviewModuleChecked( + GpuRuntimeCompileRequest compileRequest, + GpuRuntimeIrOptimizationReport optimizationReport, + String purpose + ) { + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.BACKEND_LOWERER_SELECTION_STARTED, + compileRequest, + "OpenCL optimized review lowerer selection started", + backendLowererFields(purpose, null, "started", null) + ); + try { + GpuBackendModuleArtifact moduleArtifact = lowerOptimizedReviewModule(compileRequest, optimizationReport); + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.BACKEND_LOWERER_SELECTION_COMPLETED, + compileRequest, + "OpenCL optimized review lowerer selection completed", + backendLowererFields(purpose, moduleArtifact, "succeeded", null) + ); + return moduleArtifact; + } catch (RuntimeException exception) { + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.BACKEND_LOWERER_SELECTION_COMPLETED, + compileRequest, + "OpenCL optimized review lowerer selection failed", + backendLowererFields(purpose, null, "failed", exception) + ); + throw exception; + } + } + + private void publishLifecycleEvent( + GpuRuntimeLifecycleEventKind kind, + String message, + Map fields + ) { + lifecycleEventBus.publish(new GpuRuntimeLifecycleEvent( + kind, + backendTarget(), + "runtime", + "off", + message, + fields + )); + } + + private void publishLifecycleEvent( + GpuRuntimeLifecycleEventKind kind, + GpuRuntimeCompileRequest compileRequest, + String message, + Map fields + ) { + publishLifecycleEvent(kind, compileRequest.descriptor(), compileRequest.options(), message, fields); + } + + private void publishLifecycleEvent( + GpuRuntimeLifecycleEventKind kind, + GpuRuntimeCompileArtifactSnapshot artifactSnapshot, + String message, + Map fields + ) { + GpuBackendModuleArtifact moduleArtifact = artifactSnapshot == null + ? GpuBackendModuleArtifact.unknown() + : artifactSnapshot.backendModuleArtifact(); + GpuRuntimeCompileProvenance provenance = artifactSnapshot == null + ? GpuRuntimeCompileProvenance.unknown() + : artifactSnapshot.compileProvenance(); + lifecycleEventBus.publish(new GpuRuntimeLifecycleEvent( + kind, + backendTarget(), + normalizeLifecycleValue(moduleArtifact.resource(), "unknown"), + provenance.optimizationProfile(), + message, + fields + )); + } + + private void publishLifecycleEvent( + GpuRuntimeLifecycleEventKind kind, + GpuKernelDescriptor descriptor, + GpuRuntimeCompileOptions compileOptions, + String message, + Map fields + ) { + publishLifecycleEvent( + kind, + descriptor, + compileOptions == null ? "off" : compileOptions.optimizationProfile(), + message, + fields + ); + } + + private void publishLifecycleEvent( + GpuRuntimeLifecycleEventKind kind, + GpuKernelDescriptor descriptor, + String optimizationProfile, + String message, + Map fields + ) { + lifecycleEventBus.publish(new GpuRuntimeLifecycleEvent( + kind, + backendTarget(), + descriptor == null ? "unknown" : descriptor.kernelResource(), + optimizationProfile, + message, + fields + )); + } + + private static Map irGpuLoadFields( + String loadRole, + Optional artifact, + String status, + RuntimeException failure + ) { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("status", normalizeLifecycleValue(status, "unknown")); + fields.put("loadRole", normalizeLifecycleValue(loadRole, "unknown")); + fields.put("irgpu.present", Boolean.toString(artifact != null && artifact.isPresent())); + fields.put("irgpu.identity", IrGpuArtifactIdentity.stableIdentity(artifact)); + putFailureFields(fields, failure); + return fields; + } + + private static Map validationFields( + String validationStage, + String status, + RuntimeException failure + ) { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("status", normalizeLifecycleValue(status, "unknown")); + fields.put("validation.stage", normalizeLifecycleValue(validationStage, "unknown")); + putFailureFields(fields, failure); + return fields; + } + + private static Map descriptorSelectionFields( + GpuKernelInvocation invocation, + Optional selection, + GpuKernelDescriptor selectedDescriptor, + String status, + RuntimeException failure + ) { + LinkedHashMap fields = new LinkedHashMap<>(); + Optional resolvedSelection = selection == null ? Optional.empty() : selection; + fields.put("status", normalizeLifecycleValue(status, "unknown")); + fields.put("fallbackDescriptor.count", Integer.toString(invocation.fallbackDescriptors().size())); + fields.put("descriptorVariant.count", Integer.toString(invocation.descriptorVariants().size())); + fields.put("methodVariant.selected", Boolean.toString(resolvedSelection.isPresent())); + fields.put("methodVariant.groupId", resolvedSelection.map(GpuRuntimeMethodVariantSelection::groupId).orElse("none")); + fields.put("methodVariant.selectedVariantId", resolvedSelection + .map(GpuRuntimeMethodVariantSelection::selectedVariantId) + .orElse("primary")); + if (selectedDescriptor != null) { + fields.put("selected.kernelName", selectedDescriptor.kernelName()); + fields.put("selected.kernelResource", selectedDescriptor.kernelResource()); + fields.put("selected.irgpuResource", selectedDescriptor.irGpuResource()); + } + resolvedSelection.ifPresent(value -> fields.putAll(value.artifactFields("methodVariantSelection"))); + putFailureFields(fields, failure); + return fields; + } + + private Map optimizerDiscoveryFields( + GpuRuntimeCompileRequest compileRequest, + String status, + boolean legacyHook, + RuntimeException failure + ) { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("status", normalizeLifecycleValue(status, "unknown")); + fields.put("optimizer.legacyHook", Boolean.toString(legacyHook)); + fields.put("optimizer.count", Integer.toString(legacyHook ? 1 : irOptimizerRegistry.optimizerCount())); + fields.put("optimizer.pipelineVersion", optimizerPipelineVersion()); + fields.put("irgpu.present", Boolean.toString(compileRequest.irGpuArtifact().isPresent())); + fields.put("irgpu.identity", IrGpuArtifactIdentity.stableIdentity(compileRequest.irGpuArtifact())); + putFailureFields(fields, failure); + return fields; + } + + private static Map optimizerPassFields( + GpuRuntimeIrOptimizationReport optimizationReport, + String status, + RuntimeException failure + ) { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("status", normalizeLifecycleValue(status, "unknown")); + if (optimizationReport != null) { + fields.put("pass.count", Integer.toString(optimizationReport.passReports().size())); + fields.put("pass.applied.count", Long.toString(optimizationReport.passReports().stream() + .filter(report -> report.outcome() == GpuRuntimeIrOptimizationOutcome.APPLIED) + .count())); + fields.put("pass.skipped.count", Long.toString(optimizationReport.passReports().stream() + .filter(report -> report.outcome() == GpuRuntimeIrOptimizationOutcome.SKIPPED) + .count())); + fields.put("pass.rolledBack.count", Long.toString(optimizationReport.passReports().stream() + .filter(report -> report.outcome() == GpuRuntimeIrOptimizationOutcome.ROLLED_BACK) + .count())); + fields.put("pass.failed.count", Long.toString(optimizationReport.passReports().stream() + .filter(report -> report.outcome() == GpuRuntimeIrOptimizationOutcome.FAILED) + .count())); + fields.put("rollback.required", Boolean.toString(optimizationReport.requiresRollback())); + fields.put("artifact.present", Boolean.toString(optimizationReport.artifact().isPresent())); + fields.put("candidateArtifact.present", Boolean.toString(optimizationReport.candidateArtifact().isPresent())); + fields.put("strategy.name", optimizationReport.strategyDecision().strategyName()); + fields.put("strategy.deviceFamily", optimizationReport.strategyDecision().deviceFamily()); + fields.put("strategy.selectedProfile", optimizationReport.strategyDecision().selectedProfile()); + fields.put("strategy.advisoryOnly", Boolean.toString(optimizationReport.strategyDecision().advisoryOnly())); + } + putFailureFields(fields, failure); + return fields; + } + + private static Map backendLowererFields( + String purpose, + GpuBackendModuleArtifact moduleArtifact, + String status, + RuntimeException failure + ) { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("status", normalizeLifecycleValue(status, "unknown")); + fields.put("lowerer.purpose", normalizeLifecycleValue(purpose, "unknown")); + putModuleArtifactFields(fields, moduleArtifact); + putFailureFields(fields, failure); + return fields; + } + + private static Map sourceSelectionFields( + GpuBackendModuleArtifact moduleArtifact, + GpuBackendSourcePromotionGate sourcePromotionGate, + GpuBackendSourceSwitchingDecision sourceSwitchingDecision, + GpuRuntimeIrSelection runtimeIrSelection + ) { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("status", sourceSwitchingDecision.status()); + fields.put("decision", sourceSwitchingDecision.decision()); + fields.put("sourceSelection", sourceSwitchingDecision.sourceSelection()); + fields.put("irgpuSourceRequested", Boolean.toString(sourceSwitchingDecision.irGpuSourceRequested())); + fields.put("sourceReady", Boolean.toString(sourceSwitchingDecision.sourceReady())); + fields.put("sourceReconstructed", Boolean.toString(sourceSwitchingDecision.sourceReconstructed())); + fields.put("sourceAvailable", Boolean.toString(sourceSwitchingDecision.sourceAvailable())); + fields.put("sourceParityChecked", Boolean.toString(sourceSwitchingDecision.sourceParityChecked())); + fields.put("sourceParityMatched", Boolean.toString(sourceSwitchingDecision.sourceParityMatched())); + fields.put("sourcePromotionStatus", sourceSwitchingDecision.sourcePromotionStatus()); + fields.put("sourcePromotionReviewReady", Boolean.toString(sourceSwitchingDecision.sourcePromotionReviewReady())); + fields.put("selectedSource", sourcePromotionGate.selectedSource()); + fields.put("runtimeLoadMode", sourceSwitchingDecision.runtimeLoadMode()); + fields.put("runtimeIr.selectedStage", runtimeIrSelection.selectedStage()); + fields.put("runtimeIr.transformed", Boolean.toString(runtimeIrSelection.transformed())); + fields.put("runtimeIr.optimizedRejected", Boolean.toString(runtimeIrSelection.optimizedRejected())); + putModuleArtifactFields(fields, moduleArtifact); + return fields; + } + + private Map runtimeStateFields(String status, RuntimeException failure) { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("status", normalizeLifecycleValue(status, "unknown")); + fields.put("cacheMode", cacheMode.name()); + fields.put("compiledKernel.count", Integer.toString(compiledKernelCache().size())); + fields.put("nativeBuffer.count", Integer.toString(nativeBuffers.size())); + fields.put("invocation.count", Long.toString(invocationCount.get())); + fields.put("compile.count", Long.toString(compileCount.get())); + putFailureFields(fields, failure); + return fields; + } + + private static Map backendCompilationFields( + GpuBackendModuleArtifact moduleArtifact, + GpuRuntimeCompileArtifactSnapshot artifactSnapshot, + String status, + String cacheKey, + RuntimeException failure + ) { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("status", normalizeLifecycleValue(status, "unknown")); + if (cacheKey != null && !cacheKey.isBlank()) { + fields.put("cacheKey", cacheKey); + } + putModuleArtifactFields(fields, moduleArtifact); + if (artifactSnapshot != null) { + fields.put("runtimeIr.selectedStage", artifactSnapshot.runtimeIrSelection().selectedStage()); + fields.put("runtimeIr.transformed", Boolean.toString(artifactSnapshot.runtimeIrSelection().transformed())); + fields.put("runtimeIr.optimizedRejected", Boolean.toString(artifactSnapshot.runtimeIrSelection().optimizedRejected())); + fields.put("runtimeIr.fallbackDecision", artifactSnapshot.runtimeIrSelection().fallbackDecision()); + fields.put("compileLog.present", Boolean.toString(!artifactSnapshot.compileLog().isBlank())); + fields.put("binaryArtifact.count", Integer.toString(artifactSnapshot.binaryArtifacts().size())); + } + putFailureFields(fields, failure); + return fields; + } + + private static Map invocationFields( + OpenClPreparedExecution execution, + GpuExecutionConfig executionConfig, + String status, + RuntimeException failure + ) { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("status", normalizeLifecycleValue(status, "unknown")); + fields.put("cacheKey", execution.compiledKernel().cacheKey()); + fields.put("bufferBinding.count", Integer.toString(execution.bufferBindings().size())); + fields.put("localBinding.count", Integer.toString(execution.localBindings().size())); + fields.put("scalarBinding.count", Integer.toString(execution.scalarBindings().size())); + fields.put("argumentBinding.count", Integer.toString(execution.argumentBindings().size())); + if (executionConfig != null) { + fields.put("work.dimensions", Integer.toString(executionConfig.dimensions())); + fields.put("work.globalX", Long.toString(executionConfig.globalX())); + fields.put("work.globalY", Long.toString(executionConfig.globalY())); + fields.put("work.globalZ", Long.toString(executionConfig.globalZ())); + fields.put("work.localX", Long.toString(executionConfig.localX())); + fields.put("work.localY", Long.toString(executionConfig.localY())); + fields.put("work.localZ", Long.toString(executionConfig.localZ())); + } + putFailureFields(fields, failure); + return fields; + } + + private static Map artifactDumpFields( + GpuRuntimeCompileArtifactSnapshot artifactSnapshot, + String status, + RuntimeException failure + ) { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("status", normalizeLifecycleValue(status, "unknown")); + if (artifactSnapshot != null) { + putModuleArtifactFields(fields, artifactSnapshot.backendModuleArtifact()); + fields.put("directory.count", Integer.toString(runtimeCompileArtifactDirectories(artifactSnapshot).size())); + fields.put("runtimeIr.selectedStage", artifactSnapshot.runtimeIrSelection().selectedStage()); + fields.put("runtimeIr.transformed", Boolean.toString(artifactSnapshot.runtimeIrSelection().transformed())); + fields.put("compileLog.present", Boolean.toString(!artifactSnapshot.compileLog().isBlank())); + } + putFailureFields(fields, failure); + return fields; + } + + private static void putModuleArtifactFields( + LinkedHashMap fields, + GpuBackendModuleArtifact moduleArtifact + ) { + if (moduleArtifact == null) { + return; + } + fields.put("module.kind", moduleArtifact.kind()); + fields.put("module.format", moduleArtifact.format()); + fields.put("module.resource", normalizeLifecycleValue(moduleArtifact.resource(), "unknown")); + fields.put("module.artifactVersion", moduleArtifact.artifactVersion()); + fields.put("module.lowererVersion", moduleArtifact.lowererVersion()); + fields.put("module.sourceOrigin", moduleArtifact.sourceOrigin()); + fields.put("module.runtimeLoadMode", moduleArtifact.runtimeLoadMode()); + fields.put("module.sourceAvailable", Boolean.toString(moduleArtifact.sourceAvailable())); + fields.put("module.binaryAvailable", Boolean.toString(moduleArtifact.binaryAvailable())); + } + + private static void putFailureFields(LinkedHashMap fields, RuntimeException failure) { + if (failure == null) { + return; + } + fields.put("failure.type", failure.getClass().getName()); + fields.put("failure.message", normalizeLifecycleValue(failure.getMessage(), "")); + } + + private static String normalizeLifecycleValue(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } + private Optional selectMethodVariant( GpuKernelInvocation invocation, GpuRuntimeCompileOptions compileOptions @@ -1173,24 +1737,63 @@ protected GpuRuntimeCompileRequest optimizeRuntimeIr(GpuRuntimeCompileRequest co } protected GpuRuntimeIrOptimizationResult optimizeRuntimeIrWithReport(GpuRuntimeCompileRequest compileRequest) { - if (overridesLegacyOptimizeRuntimeIr()) { - GpuRuntimeCompileRequest optimizedCompileRequest = optimizeRuntimeIr(compileRequest); - return new GpuRuntimeIrOptimizationResult( - optimizedCompileRequest, - legacyOptimizationReport(compileRequest, optimizedCompileRequest) - ); - } - GpuRuntimeIrOptimizationReport optimizationReport = irOptimizerRegistry.optimizeWithReport( - new GpuRuntimeIrOptimizationRequest( - compileRequest, - compileRequest.irGpuArtifact(), - selectOptimizationStrategy(compileRequest) - ) + boolean legacyHook = overridesLegacyOptimizeRuntimeIr(); + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.OPTIMIZER_DISCOVERY_STARTED, + compileRequest, + "Runtime IR optimizer discovery started", + optimizerDiscoveryFields(compileRequest, "started", legacyHook, null) ); - GpuRuntimeCompileRequest optimizedCompileRequest = compileRequest.withIrGpuArtifact( - optimizationReport.artifactForOptimizedReview() + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.OPTIMIZER_DISCOVERY_COMPLETED, + compileRequest, + "Runtime IR optimizer discovery completed", + optimizerDiscoveryFields(compileRequest, "succeeded", legacyHook, null) ); - return new GpuRuntimeIrOptimizationResult(optimizedCompileRequest, optimizationReport); + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.OPTIMIZER_PASS_STARTED, + compileRequest, + "Runtime IR optimizer pass pipeline started", + optimizerPassFields(null, "started", null) + ); + try { + GpuRuntimeIrOptimizationResult result; + if (legacyHook) { + GpuRuntimeCompileRequest optimizedCompileRequest = optimizeRuntimeIr(compileRequest); + result = new GpuRuntimeIrOptimizationResult( + optimizedCompileRequest, + legacyOptimizationReport(compileRequest, optimizedCompileRequest) + ); + } else { + GpuOptimizationStrategyDecision strategyDecision = selectOptimizationStrategy(compileRequest); + GpuRuntimeIrOptimizationReport optimizationReport = irOptimizerRegistry.optimizeWithReport( + new GpuRuntimeIrOptimizationRequest( + compileRequest, + compileRequest.irGpuArtifact(), + strategyDecision + ) + ); + GpuRuntimeCompileRequest optimizedCompileRequest = compileRequest.withIrGpuArtifact( + optimizationReport.artifactForOptimizedReview() + ); + result = new GpuRuntimeIrOptimizationResult(optimizedCompileRequest, optimizationReport); + } + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.OPTIMIZER_PASS_COMPLETED, + result.compileRequest(), + "Runtime IR optimizer pass pipeline completed", + optimizerPassFields(result.report(), "succeeded", null) + ); + return result; + } catch (RuntimeException exception) { + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.OPTIMIZER_PASS_COMPLETED, + compileRequest, + "Runtime IR optimizer pass pipeline failed", + optimizerPassFields(null, "failed", exception) + ); + throw exception; + } } protected String optimizerPipelineVersion() { @@ -2588,32 +3191,56 @@ protected void readBackFromDeviceBuffer(Object nativeBuffer, OpenClBufferBinding */ @Override public void close() { - if (cacheMode == CacheMode.INSTANCE) { - compiledKernels.values().forEach(OpenClCompiledKernel::close); - compiledKernels.clear(); - } + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.RUNTIME_SHUTDOWN_STARTED, + "OpenCL runtime shutdown started", + runtimeStateFields("started", null) + ); + try { + if (cacheMode == CacheMode.INSTANCE) { + compiledKernels.values().forEach(OpenClCompiledKernel::close); + compiledKernels.clear(); + } - nativeBuffers.values().forEach(value -> { - if (value instanceof AutoCloseable closeable) { - try { - closeable.close(); - } catch (Exception exception) { - throw new RuntimeException("Failed to close OpenCL device buffer", exception); + nativeBuffers.values().forEach(value -> { + if (value instanceof AutoCloseable closeable) { + try { + closeable.close(); + } catch (Exception exception) { + throw new RuntimeException("Failed to close OpenCL device buffer", exception); + } } - } - }); - nativeBuffers.clear(); - bufferRegistry.clear(); + }); + nativeBuffers.clear(); + bufferRegistry.clear(); - if (cacheMode == CacheMode.SHARED) { - return; - } + if (cacheMode == CacheMode.SHARED) { + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.RUNTIME_SHUTDOWN_COMPLETED, + "OpenCL runtime shutdown completed", + runtimeStateFields("succeeded", null) + ); + return; + } - OpenClRuntimeSession currentSession = session; - session = null; - capabilities = null; - if (currentSession != null) { - currentSession.close(); + OpenClRuntimeSession currentSession = session; + session = null; + capabilities = null; + if (currentSession != null) { + currentSession.close(); + } + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.RUNTIME_SHUTDOWN_COMPLETED, + "OpenCL runtime shutdown completed", + runtimeStateFields("succeeded", null) + ); + } catch (RuntimeException exception) { + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.RUNTIME_SHUTDOWN_COMPLETED, + "OpenCL runtime shutdown failed", + runtimeStateFields("failed", exception) + ); + throw exception; } } @@ -3024,6 +3651,12 @@ private OpenClCompiledKernel compileKernelChecked( GpuRuntimeDiagnosticContext diagnosticContext ) { GpuKernelDescriptor descriptor = compileRequest.descriptor(); + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.BACKEND_COMPILATION_STARTED, + compileRequest, + "OpenCL backend compilation started", + backendCompilationFields(moduleArtifact, artifactSnapshot, "started", null, null) + ); try { compileCount.incrementAndGet(); OpenClCompiledKernel compiledKernel; @@ -3032,11 +3665,24 @@ private OpenClCompiledKernel compileKernelChecked( } else { compiledKernel = compileKernel(compileRequest, moduleArtifact); } - return compiledKernel.withArtifactSnapshot(mergeCompilerLog( + OpenClCompiledKernel mergedKernel = compiledKernel.withArtifactSnapshot(mergeCompilerLog( artifactSnapshot, compiledKernel.artifactSnapshot() )); + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.BACKEND_COMPILATION_COMPLETED, + compileRequest, + "OpenCL backend compilation completed", + backendCompilationFields(moduleArtifact, mergedKernel.artifactSnapshot(), "succeeded", mergedKernel.cacheKey(), null) + ); + return mergedKernel; } catch (RuntimeException exception) { + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.BACKEND_COMPILATION_COMPLETED, + compileRequest, + "OpenCL backend compilation failed", + backendCompilationFields(moduleArtifact, artifactSnapshot, "failed", null, exception) + ); if (exception instanceof GpuRuntimeException runtimeException) { throw runtimeException; } @@ -3059,10 +3705,29 @@ private boolean overridesCompileRequestHook() { private OpenClCompiledKernel compileKernelChecked(GpuRuntimeCompileRequest compileRequest) { GpuKernelDescriptor descriptor = compileRequest.descriptor(); + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.BACKEND_COMPILATION_STARTED, + compileRequest, + "OpenCL backend compilation started", + backendCompilationFields(null, null, "started", null, null) + ); try { compileCount.incrementAndGet(); - return compileKernel(compileRequest); + OpenClCompiledKernel compiledKernel = compileKernel(compileRequest); + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.BACKEND_COMPILATION_COMPLETED, + compileRequest, + "OpenCL backend compilation completed", + backendCompilationFields(null, compiledKernel.artifactSnapshot(), "succeeded", compiledKernel.cacheKey(), null) + ); + return compiledKernel; } catch (RuntimeException exception) { + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.BACKEND_COMPILATION_COMPLETED, + compileRequest, + "OpenCL backend compilation failed", + backendCompilationFields(null, null, "failed", null, exception) + ); if (exception instanceof GpuRuntimeException runtimeException) { throw runtimeException; } @@ -3170,15 +3835,38 @@ private void executeKernelChecked( OpenClPreparedExecution execution, GpuRuntimeDiagnosticContext diagnosticContext ) { + GpuExecutionConfig executionConfig = null; try { + executionConfig = resolveExecutionConfig(execution); + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.INVOCATION_STARTED, + execution.compiledKernel().descriptor(), + execution.compiledKernel().artifactSnapshot().compileProvenance().optimizationProfile(), + "OpenCL kernel invocation started", + invocationFields(execution, executionConfig, "started", null) + ); OpenClKernelLaunchAdvisory launchAdvisory = OpenClKernelLaunchAdvisory.evaluate( execution.compiledKernel(), - resolveExecutionConfig(execution) + executionConfig ); validateKernelWorkGroupSize(execution, diagnosticContext, launchAdvisory); dumpRuntimeLaunchAdvisory(execution.compiledKernel().artifactSnapshot(), launchAdvisory); executeKernel(execution); + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.INVOCATION_COMPLETED, + execution.compiledKernel().descriptor(), + execution.compiledKernel().artifactSnapshot().compileProvenance().optimizationProfile(), + "OpenCL kernel invocation completed", + invocationFields(execution, executionConfig, "succeeded", null) + ); } catch (RuntimeException exception) { + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.INVOCATION_COMPLETED, + execution.compiledKernel().descriptor(), + execution.compiledKernel().artifactSnapshot().compileProvenance().optimizationProfile(), + "OpenCL kernel invocation failed", + invocationFields(execution, executionConfig, "failed", exception) + ); if (exception instanceof GpuRuntimeException runtimeException) { throw runtimeException; } @@ -3302,7 +3990,32 @@ private void clearRuntimeLaunchAdvisory(GpuRuntimeCompileArtifactSnapshot artifa } private void dumpRuntimeCompileArtifacts(GpuRuntimeCompileArtifactSnapshot artifactSnapshot) { - writeRuntimeCompileArtifactsIfConfigured(artifactSnapshot); + if (!runtimeCompileArtifactsConfigured()) { + return; + } + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.ARTIFACT_DUMP_STARTED, + artifactSnapshot, + "OpenCL runtime artifact dump started", + artifactDumpFields(artifactSnapshot, "started", null) + ); + try { + writeRuntimeCompileArtifactsIfConfigured(artifactSnapshot); + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.ARTIFACT_DUMP_COMPLETED, + artifactSnapshot, + "OpenCL runtime artifact dump completed", + artifactDumpFields(artifactSnapshot, "succeeded", null) + ); + } catch (RuntimeException exception) { + publishLifecycleEvent( + GpuRuntimeLifecycleEventKind.ARTIFACT_DUMP_COMPLETED, + artifactSnapshot, + "OpenCL runtime artifact dump failed", + artifactDumpFields(artifactSnapshot, "failed", exception) + ); + throw exception; + } } private static boolean runtimeCompileArtifactsConfigured() { diff --git a/processor/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleService b/processor/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleService new file mode 100644 index 00000000..a10c10e9 --- /dev/null +++ b/processor/src/main/resources/META-INF/services/net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleService @@ -0,0 +1 @@ +net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleFileJournalListener diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleEventBusTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleEventBusTest.java new file mode 100644 index 00000000..62ff6ade --- /dev/null +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleEventBusTest.java @@ -0,0 +1,157 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import net.sixik.ga_utils.javatogpu.api.GpuBackendTarget; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionCapability; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionExecutionOutcome; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionPermission; +import net.sixik.ga_utils.javatogpu.extension.GpuExtensionPhase; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuRuntimeLifecycleEventBusTest { + + @Test + void dispatchesListenersInExtensionOrder() { + ArrayList events = new ArrayList<>(); + GpuRuntimeLifecycleEventListener later = listener("test.lifecycle.zeta", 20, events); + GpuRuntimeLifecycleEventListener earlier = listener("test.lifecycle.alpha", 10, events); + GpuRuntimeLifecycleEvent event = new GpuRuntimeLifecycleEvent( + GpuRuntimeLifecycleEventKind.BACKEND_COMPILATION_STARTED, + GpuBackendTarget.OPENCL, + "kernels/test.cl", + "diagnostic", + "backend compile started", + Map.of("source", "irgpu") + ); + + GpuRuntimeLifecycleEventReport report = GpuRuntimeLifecycleEventBus.of(List.of(later, earlier)) + .publish(event); + + assertEquals(List.of("test.lifecycle.alpha:BACKEND_COMPILATION_STARTED", "test.lifecycle.zeta:BACKEND_COMPILATION_STARTED"), events); + assertEquals(2, report.listenerReports().size()); + assertTrue(report.allListenersSucceeded()); + assertEquals("BACKEND_COMPILATION_STARTED", report.artifactFields("lifecycle").get("lifecycle.event.kind")); + assertEquals("OPENCL", report.artifactFields("lifecycle").get("lifecycle.event.backendTarget")); + assertEquals("source", report.artifactFields("lifecycle").get("lifecycle.event.field.0.key")); + assertEquals("irgpu", report.artifactFields("lifecycle").get("lifecycle.event.field.0.value")); + } + + @Test + void listenerFailureIsIsolatedAndFollowingListenersStillRun() { + AtomicBoolean followingInvoked = new AtomicBoolean(); + GpuRuntimeLifecycleEventListener failing = new OrderedLifecycleListener("test.lifecycle.failing", 0) { + @Override + public void onRuntimeLifecycleEvent(GpuRuntimeLifecycleEvent event) { + throw new IllegalStateException("listener exploded"); + } + }; + GpuRuntimeLifecycleEventListener following = new OrderedLifecycleListener("test.lifecycle.following", 10) { + @Override + public void onRuntimeLifecycleEvent(GpuRuntimeLifecycleEvent event) { + followingInvoked.set(true); + } + }; + + GpuRuntimeLifecycleEventReport report = GpuRuntimeLifecycleEventBus.of(List.of(failing, following)) + .publish(GpuRuntimeLifecycleEvent.of(GpuRuntimeLifecycleEventKind.INVOCATION_STARTED)); + + assertTrue(followingInvoked.get()); + assertFalse(report.allListenersSucceeded()); + assertEquals(GpuExtensionExecutionOutcome.FAILED_CONTINUED, report.listenerReports().get(0).outcome()); + assertEquals(GpuExtensionExecutionOutcome.SUCCEEDED, report.listenerReports().get(1).outcome()); + assertTrue(report.listenerReports().get(0).pipelineContinued()); + assertTrue(report.toPropertiesText().contains("listener exploded")); + } + + @Test + void busRejectsWrongExtensionPhaseCapabilityOrPermission() { + GpuRuntimeLifecycleEventListener wrongPhase = new OrderedLifecycleListener("test.lifecycle.wrong-phase", 0) { + @Override + public GpuExtensionPhase extensionPhase() { + return GpuExtensionPhase.DIAGNOSTICS; + } + }; + GpuRuntimeLifecycleEventListener wrongCapability = new OrderedLifecycleListener("test.lifecycle.wrong-capability", 0) { + @Override + public Set extensionCapabilities() { + return Set.of(GpuExtensionCapability.DIAGNOSTIC_REPORTING); + } + }; + GpuRuntimeLifecycleEventListener wrongPermission = new OrderedLifecycleListener("test.lifecycle.wrong-permission", 0) { + @Override + public GpuExtensionPermission extensionPermission() { + return GpuExtensionPermission.MUTATION_PROPOSAL; + } + }; + + assertThrows(IllegalArgumentException.class, () -> GpuRuntimeLifecycleEventBus.of(List.of(wrongPhase))); + assertThrows(IllegalArgumentException.class, () -> GpuRuntimeLifecycleEventBus.of(List.of(wrongCapability))); + assertThrows(IllegalArgumentException.class, () -> GpuRuntimeLifecycleEventBus.of(List.of(wrongPermission))); + } + + @Test + void eventFieldsAreImmutableAndPropertiesSafe() { + GpuRuntimeLifecycleEvent event = GpuRuntimeLifecycleEvent + .of(GpuRuntimeLifecycleEventKind.ARTIFACT_DUMP_COMPLETED) + .withField("path", "build/out\nnext-line"); + + assertThrows(UnsupportedOperationException.class, () -> event.fields().put("other", "value")); + + String properties = new GpuRuntimeLifecycleEventReport(event, List.of()).toPropertiesText(); + + assertTrue(properties.contains("runtimeLifecycle.event.kind=ARTIFACT_DUMP_COMPLETED")); + assertTrue(properties.contains("runtimeLifecycle.event.field.0.value=build/out\\nnext-line")); + } + + private static GpuRuntimeLifecycleEventListener listener( + String id, + int order, + List events + ) { + return new OrderedLifecycleListener(id, order) { + @Override + public void onRuntimeLifecycleEvent(GpuRuntimeLifecycleEvent event) { + events.add(extensionId() + ":" + event.kind().name()); + } + }; + } + + private static class OrderedLifecycleListener implements GpuRuntimeLifecycleEventListener { + private final String id; + private final int order; + + private OrderedLifecycleListener(String id, int order) { + this.id = id; + this.order = order; + } + + @Override + public void onRuntimeLifecycleEvent(GpuRuntimeLifecycleEvent event) { + } + + @Override + public String extensionId() { + return id; + } + + @Override + public String extensionVersion() { + return "1"; + } + + @Override + public int extensionOrder() { + return order; + } + } +} diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleFileJournalListenerTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleFileJournalListenerTest.java new file mode 100644 index 00000000..f12c90b6 --- /dev/null +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/GpuRuntimeLifecycleFileJournalListenerTest.java @@ -0,0 +1,144 @@ +package net.sixik.ga_utils.javatogpu.runtime; + +import net.sixik.ga_utils.javatogpu.api.GpuBackendTarget; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.util.Properties; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GpuRuntimeLifecycleFileJournalListenerTest { + + @TempDir + Path temporaryDirectory; + + @Test + void listenerIsNoOpWhenJournalFileIsNotConfigured() throws Exception { + withJournalProperties(null, null, () -> { + new GpuRuntimeLifecycleFileJournalListener().onRuntimeLifecycleEvent(sampleEvent( + GpuRuntimeLifecycleEventKind.INVOCATION_STARTED + )); + assertFalse(Files.exists(temporaryDirectory.resolve("runtime-lifecycle.jsonl"))); + }); + } + + @Test + void writesJsonlLifecycleEventsWhenConfigured() throws Exception { + Path journal = temporaryDirectory.resolve("runtime-lifecycle.jsonl"); + + withJournalProperties(journal, "jsonl", () -> new GpuRuntimeLifecycleFileJournalListener() + .onRuntimeLifecycleEvent(sampleEvent(GpuRuntimeLifecycleEventKind.INVOCATION_STARTED))); + + String jsonl = Files.readString(journal, StandardCharsets.UTF_8); + assertTrue(jsonl.contains("\"kind\":\"INVOCATION_STARTED\""), jsonl); + assertTrue(jsonl.contains("\"backendTarget\":\"OPENCL\""), jsonl); + assertTrue(jsonl.contains("\"kernelResource\":\"javatogpu/sample/Demo/kernel.cl\""), jsonl); + assertTrue(jsonl.contains("\"status\":\"started\""), jsonl); + assertTrue(jsonl.contains("\"work.globalX\":\"4\""), jsonl); + } + + @Test + void writesIndexedPropertiesLifecycleEventsWhenConfigured() throws Exception { + Path journal = temporaryDirectory.resolve("runtime-lifecycle.properties"); + + withJournalProperties(journal, "properties", () -> { + GpuRuntimeLifecycleFileJournalListener listener = new GpuRuntimeLifecycleFileJournalListener(); + listener.onRuntimeLifecycleEvent(sampleEvent(GpuRuntimeLifecycleEventKind.BACKEND_COMPILATION_STARTED)); + listener.onRuntimeLifecycleEvent(sampleEvent(GpuRuntimeLifecycleEventKind.BACKEND_COMPILATION_COMPLETED)); + }); + + Properties properties = new Properties(); + try (Reader reader = Files.newBufferedReader(journal, StandardCharsets.UTF_8)) { + properties.load(reader); + } + assertEquals("properties", properties.getProperty("journal.format")); + assertEquals("2", properties.getProperty("event.count")); + assertEquals("BACKEND_COMPILATION_STARTED", properties.getProperty("event.0.kind")); + assertEquals("BACKEND_COMPILATION_COMPLETED", properties.getProperty("event.1.kind")); + assertEquals("OPENCL", properties.getProperty("event.0.backendTarget")); + assertEquals("status", properties.getProperty("event.0.field.0.key")); + assertEquals("started", properties.getProperty("event.0.field.0.value")); + } + + @Test + void serviceLoaderBusCanWriteThroughBuiltInFileJournalListener() throws Exception { + Path journal = temporaryDirectory.resolve("service-loader-lifecycle.jsonl"); + + withJournalProperties(journal, "jsonl", () -> GpuRuntimeLifecycleEventBus.loadFromServiceLoader() + .publish(sampleEvent(GpuRuntimeLifecycleEventKind.ARTIFACT_DUMP_COMPLETED))); + + String jsonl = Files.readString(journal, StandardCharsets.UTF_8); + assertTrue(jsonl.contains("\"kind\":\"ARTIFACT_DUMP_COMPLETED\""), jsonl); + } + + @Test + void builtInJournalIsRegisteredAsLifecycleService() throws Exception { + String servicePath = "META-INF/services/" + GpuRuntimeLifecycleService.class.getName(); + try (var input = Thread.currentThread().getContextClassLoader().getResourceAsStream(servicePath)) { + assertTrue(input != null, servicePath); + String descriptor = new String(input.readAllBytes(), StandardCharsets.UTF_8); + assertTrue(descriptor.contains(GpuRuntimeLifecycleFileJournalListener.class.getName()), descriptor); + } + assertTrue(new GpuRuntimeLifecycleFileJournalListener() instanceof GpuRuntimeLifecycleService); + } + + private static GpuRuntimeLifecycleEvent sampleEvent(GpuRuntimeLifecycleEventKind kind) { + LinkedHashMap fields = new LinkedHashMap<>(); + fields.put("status", "started"); + fields.put("work.globalX", "4"); + return new GpuRuntimeLifecycleEvent( + kind, + GpuBackendTarget.OPENCL, + "javatogpu/sample/Demo/kernel.cl", + "diagnostic", + "sample lifecycle event", + fields + ); + } + + private static void withJournalProperties( + Path journal, + String format, + ThrowingRunnable action + ) throws Exception { + String previousFile = System.getProperty(GpuRuntimeLifecycleFileJournalListener.JOURNAL_FILE_PROPERTY); + String previousFormat = System.getProperty(GpuRuntimeLifecycleFileJournalListener.JOURNAL_FORMAT_PROPERTY); + try { + if (journal == null) { + System.clearProperty(GpuRuntimeLifecycleFileJournalListener.JOURNAL_FILE_PROPERTY); + } else { + System.setProperty(GpuRuntimeLifecycleFileJournalListener.JOURNAL_FILE_PROPERTY, journal.toString()); + } + if (format == null) { + System.clearProperty(GpuRuntimeLifecycleFileJournalListener.JOURNAL_FORMAT_PROPERTY); + } else { + System.setProperty(GpuRuntimeLifecycleFileJournalListener.JOURNAL_FORMAT_PROPERTY, format); + } + action.run(); + } finally { + restoreProperty(GpuRuntimeLifecycleFileJournalListener.JOURNAL_FILE_PROPERTY, previousFile); + restoreProperty(GpuRuntimeLifecycleFileJournalListener.JOURNAL_FORMAT_PROPERTY, previousFormat); + } + } + + private static void restoreProperty(String property, String value) { + if (value == null) { + System.clearProperty(property); + } else { + System.setProperty(property, value); + } + } + + @FunctionalInterface + private interface ThrowingRunnable { + void run() throws Exception; + } +} diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackendTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackendTest.java index 1360e6c5..b39c4834 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackendTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClGpuRuntimeBackendTest.java @@ -57,6 +57,10 @@ import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeEquivalenceCaseEvidence; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeEquivalenceEvidence; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeEquivalenceRequest; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleEvent; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleEventBus; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleEventKind; +import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeLifecycleEventListener; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationPassReport; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationProofArtifact; import net.sixik.ga_utils.javatogpu.runtime.GpuRuntimeIrOptimizationReport; @@ -757,6 +761,116 @@ protected void executeKernel(OpenClPreparedExecution execution) { assertEquals("OpenCL 3.0 Mock", request.deviceProfile().apiVersionText()); } + @Test + void publishesLifecycleEventsAroundCompileArtifactDumpInvocationAndClose() throws java.io.IOException { + String property = "javatogpu.opencl.runtimeCompileArtifactDirectory"; + String previousArtifactRoot = System.getProperty(property); + Path artifactRoot = Files.createTempDirectory("opencl-lifecycle-events"); + ArrayList events = new ArrayList<>(); + GpuRuntimeLifecycleEventListener listener = new GpuRuntimeLifecycleEventListener() { + @Override + public void onRuntimeLifecycleEvent(GpuRuntimeLifecycleEvent event) { + events.add(event); + } + + @Override + public String extensionId() { + return "test.opencl.lifecycle-listener"; + } + + @Override + public String extensionVersion() { + return "1"; + } + }; + GpuKernelDescriptor descriptor = intOutputDescriptor(); + OpenClGpuRuntimeBackend backend = new OpenClGpuRuntimeBackend( + OpenClGpuRuntimeBackend.CacheMode.INSTANCE, + GpuRuntimeLifecycleEventBus.of(List.of(listener)) + ) { + @Override + protected OpenClRuntimeCapabilities runtimeCapabilities() { + return new OpenClRuntimeCapabilities("Mock GPU", "OpenCL 3.0 Mock", true, true, true, 32_768L, 256L); + } + + @Override + protected GpuBackendModuleArtifact lowerBackendModule(GpuRuntimeCompileRequest compileRequest) { + return GpuBackendModuleArtifact.openClSource( + compileRequest.descriptor().kernelSource(), + compileRequest.descriptor().kernelResource(), + "test-lifecycle-lowerer" + ); + } + + @Override + protected OpenClCompiledKernel compileKernel( + GpuRuntimeCompileRequest compileRequest, + GpuBackendModuleArtifact moduleArtifact + ) { + return new OpenClCompiledKernel(compileRequest.descriptor(), "compiled:lifecycle"); + } + + @Override + protected void executeKernel(OpenClPreparedExecution execution) { + // no-op: this test covers lifecycle dispatch, not native OpenCL execution. + } + }; + + try { + System.setProperty(property, artifactRoot.toString()); + backend.invoke(new GpuKernelInvocation(descriptor, new Object[]{new int[4]})); + backend.close(); + } finally { + if (previousArtifactRoot == null) { + System.clearProperty(property); + } else { + System.setProperty(property, previousArtifactRoot); + } + } + + List kinds = events.stream().map(GpuRuntimeLifecycleEvent::kind).toList(); + assertEquals(List.of( + GpuRuntimeLifecycleEventKind.IRGPU_LOAD_STARTED, + GpuRuntimeLifecycleEventKind.IRGPU_LOAD_COMPLETED, + GpuRuntimeLifecycleEventKind.VALIDATION_STARTED, + GpuRuntimeLifecycleEventKind.VALIDATION_COMPLETED, + GpuRuntimeLifecycleEventKind.DESCRIPTOR_DISCOVERY_STARTED, + GpuRuntimeLifecycleEventKind.DESCRIPTOR_DISCOVERY_COMPLETED, + GpuRuntimeLifecycleEventKind.VALIDATION_STARTED, + GpuRuntimeLifecycleEventKind.VALIDATION_COMPLETED, + GpuRuntimeLifecycleEventKind.VALIDATION_STARTED, + GpuRuntimeLifecycleEventKind.VALIDATION_COMPLETED, + GpuRuntimeLifecycleEventKind.OPTIMIZER_DISCOVERY_STARTED, + GpuRuntimeLifecycleEventKind.OPTIMIZER_DISCOVERY_COMPLETED, + GpuRuntimeLifecycleEventKind.OPTIMIZER_PASS_STARTED, + GpuRuntimeLifecycleEventKind.OPTIMIZER_PASS_COMPLETED, + GpuRuntimeLifecycleEventKind.BACKEND_LOWERER_SELECTION_STARTED, + GpuRuntimeLifecycleEventKind.BACKEND_LOWERER_SELECTION_COMPLETED, + GpuRuntimeLifecycleEventKind.BACKEND_LOWERER_SELECTION_STARTED, + GpuRuntimeLifecycleEventKind.BACKEND_LOWERER_SELECTION_COMPLETED, + GpuRuntimeLifecycleEventKind.SOURCE_SELECTION_DECIDED, + GpuRuntimeLifecycleEventKind.BACKEND_COMPILATION_STARTED, + GpuRuntimeLifecycleEventKind.BACKEND_COMPILATION_COMPLETED, + GpuRuntimeLifecycleEventKind.ARTIFACT_DUMP_STARTED, + GpuRuntimeLifecycleEventKind.ARTIFACT_DUMP_COMPLETED, + GpuRuntimeLifecycleEventKind.INVOCATION_STARTED, + GpuRuntimeLifecycleEventKind.INVOCATION_COMPLETED, + GpuRuntimeLifecycleEventKind.RUNTIME_SHUTDOWN_STARTED, + GpuRuntimeLifecycleEventKind.RUNTIME_SHUTDOWN_COMPLETED + ), kinds); + assertEquals("javatogpu/sample/Demo/kernel.cl", events.get(0).kernelResource()); + assertEquals("primary", events.get(1).fields().get("loadRole")); + assertEquals("runtime-capabilities", events.get(8).fields().get("validation.stage")); + assertEquals("succeeded", events.get(13).fields().get("status")); + assertEquals("test-lifecycle-lowerer", events.get(15).fields().get("module.lowererVersion")); + assertEquals("descriptor-default", events.get(18).fields().get("status")); + assertEquals("succeeded", events.get(20).fields().get("status")); + assertEquals("compiled:lifecycle", events.get(20).fields().get("cacheKey")); + assertEquals("4", events.get(23).fields().get("work.globalX")); + assertEquals("succeeded", events.get(24).fields().get("status")); + assertEquals("INSTANCE", events.get(26).fields().get("cacheMode")); + } + @Test void compileCacheSeparatesVariantsByDeviceProfile() { GpuKernelDescriptor descriptor = new GpuKernelDescriptor( diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java index b6d8289e..0cf84081 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClValidationReportTest.java @@ -1564,7 +1564,19 @@ void validationReporterWritesKernelLaunchAdvisoryReportAndWorkflowFragment() thr assertTrue(workflow.contains("production_promotion_manifest_mode:")); assertTrue(workflow.contains("production_promotion_manifest_file:")); assertTrue(workflow.contains("production_promotion_candidate_git_sha:")); + assertTrue(workflow.contains("JTG_WORKFLOW_EVENT: ${{ github.event_name }}")); + assertTrue(workflow.contains("github.event_name == 'workflow_dispatch' && github.event.inputs.production_promotion_manifest_mode || 'skip'")); + assertTrue(workflow.contains("Validate manual production-promotion inputs")); + assertTrue(workflow.contains("production_promotion_candidate_git_sha is required for manual validate/activate modes.")); + assertTrue(workflow.contains("nvidia-rtx5070")); + assertTrue(workflow.contains("nvidia-rtx3060")); + assertTrue(workflow.contains("\"device_name\":\"RTX 3060\"")); + assertTrue(workflow.contains("\"runner_labels\":[\"self-hosted\",\"Windows\",\"X64\",\"nvidia\",\"rtx3060\"]")); + assertTrue(workflow.contains("github.event_name == 'workflow_dispatch' && github.event.inputs.validation_lane == 'nvidia'")); + assertTrue(workflow.contains("github.event_name == 'workflow_dispatch' && github.event.inputs.validation_lane == 'nvidia-rtx3060'")); + assertTrue(workflow.contains("github.event_name == 'workflow_dispatch' && github.event.inputs.validation_lane == 'amd'")); assertTrue(workflow.contains("github.event.inputs.validation_lane == 'nvidia'")); + assertTrue(workflow.contains("github.event.inputs.validation_lane == 'nvidia-rtx3060'")); assertTrue(workflow.contains("github.event.inputs.validation_lane == 'amd'")); assertTrue(workflow.contains(":processor:writeOpenClBackendSourcePromotionManifestTemplate")); assertTrue(workflow.contains(":processor:validateOpenClBackendSourcePromotionManifest")); From 3889c57201260e6d8687dbc8753251e4c09c4abb Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Wed, 15 Jul 2026 14:16:36 +0300 Subject: [PATCH 31/32] fix: Github workflow CI --- docs/Runtime-Guide.md | 2 +- docs/Validation-and-Operations.md | 2 +- processor/build.gradle | 5 ++-- ...untimeIrOptimizerEvidenceValidatorCli.java | 24 +++++++++++++++---- ...meIrOptimizerEvidenceValidatorCliTest.java | 7 ++++++ 5 files changed, 30 insertions(+), 10 deletions(-) diff --git a/docs/Runtime-Guide.md b/docs/Runtime-Guide.md index d6e3a2f8..2a68a2d8 100644 --- a/docs/Runtime-Guide.md +++ b/docs/Runtime-Guide.md @@ -235,7 +235,7 @@ OpenCL isolated runtime-equivalence checks write raw pipeline comparison cases i Non-analysis optimizer passes with runtime-equivalence payload evidence also produce `runtime-optimizer-family-equivalence-payload.properties` plus durable files under `runtime-optimizer-family-equivalence-payload/family--/pass-/`. Each pass directory contains `manifest.properties`, CPU/reference, pre-optimization, post-optimization, tolerance, failure-fixture, and diagnostics properties files. When proof fields contain structured case evidence, the component files include deterministic indexed fields for case names, inputs, raw outputs, tolerances, equivalence flags, and diagnostics. Missing components are materialized with `status=not-recorded`; they are never inferred from another pass. Artifact paths are normalized and must remain inside the kernel's `runtime-compile-artifacts` directory. These files are diagnostic evidence only and cannot enable source switching or production mutation. -When the optional IR optimizer bridge participates, each kernel directory may also receive `runtime-ir-optimizer-evidence.properties`. The artifact records only proposal/provider evidence from stable `javatogpu.ir-optimizer` sources: pass counts, proposal-only versus selected optimized counts, rollback counts, IR identities, proof fields, approval-template applicability, and diagnostics. Proposal-only materialized candidates are composed through a separate review-working artifact, so `optimized.irgpu.properties` and `optimized.backend.opencl-c` can accumulate safe review transforms from multiple providers while `backend.opencl-c` still remains the selected source compiled by OpenCL. The validation sandwich now also has an internal optimized-artifact candidate envelope for proposed artifacts; it records candidate identity and selection blockers as report-only evidence while final runtime IR selection remains separate and fail-closed. Candidate envelope proof fields are also lifted into top-level `optimizedArtifactCandidate.*` summary fields for status/counts, ready/blocked counts, first blockers, selection-ready/applied counts, selected-IR replacement counts, mutation-policy count, and disabled selection/replacement guardrails. Backend-neutral source materialization proof is aggregated into `backendNeutralSourceMaterialization.*` fields for pass count, candidate count, source-ready count, total source length, status, and first blocker; it is review-only source metadata evidence and does not require runtime-equivalence review by itself. Constant-folding preview proof is also aggregated into `constantFoldingPreview.*` fields for pass count, candidate count, skipped blockers, required runtime-equivalence/approval gates, and unresolved integer-overflow / floating-point-rounding safety flags. A separate constant-folding materialization provider can now emit a review-only transformed artifact for plain 32-bit integer literal unary `-` plus binary `+`, `-`, `*`, and exact `/` expressions where the divisor is non-zero and the division has no remainder, including nested safe folds materialized to a fixed point. It also supports a narrow pure-symbolic identity slice for `x + 0`, `0 + x`, `x - 0`, `x * 1`, `1 * x`, and `x / 1`, where the retained side can be a deterministic `GpuIrPureExpression` composed of named variables, plain int32 literal leaves, arithmetic binary nodes, and unary minus nodes; mixed identity chains such as `((x / 1) + 0)`, repeated identities across multiple roots, and multiple method bodies are recomputed one reachable rewrite per fixed-point pass so text and typed roots stay aligned. `x * 0`, `1 / x`, and broader division algebra remain excluded until stronger side-effect / unused-operand and equivalence proof exists. Top-level `constantFoldingMaterialization.*` fields record pass/transformed-node/literal-rewrite/identity-rewrite/fixed-point-pass counts, all-method-body rewrite scope, reachable-node-scan and rewrite-granularity metadata, body-text replacement scope, exact-int or symbolic-identity runtime-equivalence payload presence/pass counts, per-case method/node/rewrite-kind identity, status, skipped divide-by-zero/non-even-division counts, and first blocker. The same compact proof payload is routed into `runtime-optimizer-family-equivalence-payload.properties` plus durable component files, so this narrow materialized family can become runtime-equivalence `review-ready` while approval/manual review still blocks completion. Pending approval templates also export `pass.N.approvalTemplate.field.runtimeEquivalencePayload.*`, `pass.N.approvalTemplate.field.resourceDirectory`, and `pass.N.approvalTemplate.field.resourcePath` fields so the manual-review package is visibly bound to the same payload resource, comparison mode, completeness, case count, and manifest location; top-level `approvalTemplate.runtimeEquivalencePayloadRequired.count`, `approvalTemplate.runtimeEquivalencePayloadPresent.count`, `approvalTemplate.runtimeEquivalencePayloadPassed.count`, and `approvalTemplate.runtimeEquivalencePayloadComplete.count` summarize those bindings across pending templates. The bridge also probes the deterministic approval manifest classpath resource and emits `approvalManifest.*` fields for missing, blocked, or accepted manifest validation. `reviewPackage.approvalManifest.*` records the next package boundary over those templates, including manifest requirement, present/accepted counts, resource-path summary, first blocker, manual-review-only state, and disabled production mutation / selected-IR replacement. Safe-local-CSE preview proof is aggregated into `safeLocalCsePreview.*` fields for pass count, expression/candidate/duplicate/equivalence-class counts, unsupported-operator / impure-operand / control-flow-boundary blockers, required runtime-equivalence/approval gates, and unresolved dominance / side-effect-freedom proof flags. Safe-local-CSE materialization proof is aggregated into `safeLocalCseMaterialization.*` fields for pass count, existing-local binding count, introduced-temporary count, transformed-node count, body-text replacement count, fixed-point pass count, runtime-equivalence payload presence/pass counts, status, and first blocker. This materialized family rewrites repeated pure binary expressions to existing local references or conservative introduced local temporaries in straight-line `ir-text-v1` bodies, and changes only the optimized review artifact. Intrinsic materialization proof is aggregated into `madFmaMaterialization.*`, `clampMaterialization.*`, `stepMaterialization.*`, and `mixMaterialization.*` fields for pass/candidate/transformed-node/body-text-replacement counts, fast-math or strict-float safety policy, direct/inverted step accounting, canonical/expanded/MAD-expanded mix accounting, runtime-equivalence payload presence/pass counts, status, and first blocker. Loop-vectorization proof is aggregated into `loopVectorizationMaterialization.*` fields for transformed-loop/text-replacement/typed-body materialization counts and ordered-reduction safety evidence. Typed dead-code preview proof is aggregated into `typedDeadCodePreview.*` fields for pass count, typed/reachable/unreachable node counts, missing-root / missing-child-reference / side-effecting-unreachable blockers, required runtime-equivalence/approval gates, and unresolved side-effect-freedom proof flags. The artifact also records `previewReadiness.*` fields with aggregate status, recorded/candidate/blocked family counts, and a per-family status summary so CI can distinguish no-candidate previews from proof-blocked previews and review-ready runtime-equivalence candidates. `runtimeEquivalenceReview.*` records the next fail-closed review boundary: status, eligibility, requirement, first blocker, family summary, disabled production mutation, disabled selected-IR replacement, and manual-review-only state. `reviewPackage.*` records the manual-review package boundary above that review gate, including package status, requirement/completeness, first blocker, proposal-pass count, pending approval count, original/optimized IR and proof-summary requirements, manual-review-only state, and disabled production mutation / selected-IR replacement. `openClValidationReport` aggregates this into `Runtime IR Optimizer Evidence` for CI visibility, including optimized-artifact candidate status/blockers, backend-neutral source materialization readiness, approval-template payload readiness, approval-manifest package readiness, constant-folding preview/materialization, safe-local-CSE preview/materialization, mad/FMA, clamp, step, mix, loop-vectorization, typed dead-code preview/materialization totals, preview-readiness status, runtime-equivalence review eligibility, and review-package status. `validateOpenClRuntimeIrOptimizerEvidence` then validates the raw runtime evidence files and fails CI if those fail-closed guardrails are missing, completed early, or production-enabling. The section and validator are evidence-only and do not participate in history, source switching, selected-IR replacement, or production mutation gates. +When the optional IR optimizer bridge participates, each kernel directory may also receive `runtime-ir-optimizer-evidence.properties`. The artifact records only proposal/provider evidence from stable `javatogpu.ir-optimizer` sources: pass counts, proposal-only versus selected optimized counts, rollback counts, IR identities, proof fields, approval-template applicability, and diagnostics. Proposal-only materialized candidates are composed through a separate review-working artifact, so `optimized.irgpu.properties` and `optimized.backend.opencl-c` can accumulate safe review transforms from multiple providers while `backend.opencl-c` still remains the selected source compiled by OpenCL. The validation sandwich now also has an internal optimized-artifact candidate envelope for proposed artifacts; it records candidate identity and selection blockers as report-only evidence while final runtime IR selection remains separate and fail-closed. Candidate envelope proof fields are also lifted into top-level `optimizedArtifactCandidate.*` summary fields for status/counts, ready/blocked counts, first blockers, selection-ready/applied counts, selected-IR replacement counts, mutation-policy count, and disabled selection/replacement guardrails. Backend-neutral source materialization proof is aggregated into `backendNeutralSourceMaterialization.*` fields for pass count, candidate count, source-ready count, total source length, status, and first blocker; it is review-only source metadata evidence and does not require runtime-equivalence review by itself. Constant-folding preview proof is also aggregated into `constantFoldingPreview.*` fields for pass count, candidate count, skipped blockers, required runtime-equivalence/approval gates, and unresolved integer-overflow / floating-point-rounding safety flags. A separate constant-folding materialization provider can now emit a review-only transformed artifact for plain 32-bit integer literal unary `-` plus binary `+`, `-`, `*`, and exact `/` expressions where the divisor is non-zero and the division has no remainder, including nested safe folds materialized to a fixed point. It also supports a narrow pure-symbolic identity slice for `x + 0`, `0 + x`, `x - 0`, `x * 1`, `1 * x`, and `x / 1`, where the retained side can be a deterministic `GpuIrPureExpression` composed of named variables, plain int32 literal leaves, arithmetic binary nodes, and unary minus nodes; mixed identity chains such as `((x / 1) + 0)`, repeated identities across multiple roots, and multiple method bodies are recomputed one reachable rewrite per fixed-point pass so text and typed roots stay aligned. `x * 0`, `1 / x`, and broader division algebra remain excluded until stronger side-effect / unused-operand and equivalence proof exists. Top-level `constantFoldingMaterialization.*` fields record pass/transformed-node/literal-rewrite/identity-rewrite/fixed-point-pass counts, all-method-body rewrite scope, reachable-node-scan and rewrite-granularity metadata, body-text replacement scope, exact-int or symbolic-identity runtime-equivalence payload presence/pass counts, per-case method/node/rewrite-kind identity, status, skipped divide-by-zero/non-even-division counts, and first blocker. The same compact proof payload is routed into `runtime-optimizer-family-equivalence-payload.properties` plus durable component files, so this narrow materialized family can become runtime-equivalence `review-ready` while approval/manual review still blocks completion. Pending approval templates also export `pass.N.approvalTemplate.field.runtimeEquivalencePayload.*`, `pass.N.approvalTemplate.field.resourceDirectory`, and `pass.N.approvalTemplate.field.resourcePath` fields so the manual-review package is visibly bound to the same payload resource, comparison mode, completeness, case count, and manifest location; top-level `approvalTemplate.runtimeEquivalencePayloadRequired.count`, `approvalTemplate.runtimeEquivalencePayloadPresent.count`, `approvalTemplate.runtimeEquivalencePayloadPassed.count`, and `approvalTemplate.runtimeEquivalencePayloadComplete.count` summarize those bindings across pending templates. The bridge also probes the deterministic approval manifest classpath resource and emits `approvalManifest.*` fields for missing, blocked, or accepted manifest validation. `reviewPackage.approvalManifest.*` records the next package boundary over those templates, including manifest requirement, present/accepted counts, resource-path summary, first blocker, manual-review-only state, and disabled production mutation / selected-IR replacement. Safe-local-CSE preview proof is aggregated into `safeLocalCsePreview.*` fields for pass count, expression/candidate/duplicate/equivalence-class counts, unsupported-operator / impure-operand / control-flow-boundary blockers, required runtime-equivalence/approval gates, and unresolved dominance / side-effect-freedom proof flags. Safe-local-CSE materialization proof is aggregated into `safeLocalCseMaterialization.*` fields for pass count, existing-local binding count, introduced-temporary count, transformed-node count, body-text replacement count, fixed-point pass count, runtime-equivalence payload presence/pass counts, status, and first blocker. This materialized family rewrites repeated pure binary expressions to existing local references or conservative introduced local temporaries in straight-line `ir-text-v1` bodies, and changes only the optimized review artifact. Intrinsic materialization proof is aggregated into `madFmaMaterialization.*`, `clampMaterialization.*`, `stepMaterialization.*`, and `mixMaterialization.*` fields for pass/candidate/transformed-node/body-text-replacement counts, fast-math or strict-float safety policy, direct/inverted step accounting, canonical/expanded/MAD-expanded mix accounting, runtime-equivalence payload presence/pass counts, status, and first blocker. Loop-vectorization proof is aggregated into `loopVectorizationMaterialization.*` fields for transformed-loop/text-replacement/typed-body materialization counts and ordered-reduction safety evidence. Typed dead-code preview proof is aggregated into `typedDeadCodePreview.*` fields for pass count, typed/reachable/unreachable node counts, missing-root / missing-child-reference / side-effecting-unreachable blockers, required runtime-equivalence/approval gates, and unresolved side-effect-freedom proof flags. The artifact also records `previewReadiness.*` fields with aggregate status, recorded/candidate/blocked family counts, and a per-family status summary so CI can distinguish no-candidate previews from proof-blocked previews and review-ready runtime-equivalence candidates. `runtimeEquivalenceReview.*` records the next fail-closed review boundary: status, eligibility, requirement, first blocker, family summary, disabled production mutation, disabled selected-IR replacement, and manual-review-only state. `reviewPackage.*` records the manual-review package boundary above that review gate, including package status, requirement/completeness, first blocker, proposal-pass count, pending approval count, original/optimized IR and proof-summary requirements, manual-review-only state, and disabled production mutation / selected-IR replacement. `openClValidationReport` aggregates this into `Runtime IR Optimizer Evidence` for CI visibility, including optimized-artifact candidate status/blockers, backend-neutral source materialization readiness, approval-template payload readiness, approval-manifest package readiness, constant-folding preview/materialization, safe-local-CSE preview/materialization, mad/FMA, clamp, step, mix, loop-vectorization, typed dead-code preview/materialization totals, preview-readiness status, runtime-equivalence review eligibility, and review-package status. `validateOpenClRuntimeIrOptimizerEvidence` then validates the raw runtime evidence files when present and fails CI if those fail-closed guardrails are missing, completed early, or production-enabling; normal vendor routines pass `--allow-missing` so optional `not-recorded` optimizer evidence does not fail a non-optimizer run. The section and validator are evidence-only and do not participate in history, source switching, selected-IR replacement, or production mutation gates. For local optimized-artifact execution tests, use `GpuRuntimeCompileOptions.openClIrOptimizerExperimentalApply(...)`. That preset records `experimentalApply.*` fields in `runtime-ir-optimizer-evidence.properties` and allows `backend.opencl-c` to come from the selected optimized IR if runtime-equivalence and production gates allow it. The default validator still rejects applied optimized-artifact selection; pass `--allow-experimental-apply` only for intentionally local apply-mode artifacts. diff --git a/docs/Validation-and-Operations.md b/docs/Validation-and-Operations.md index 69f2e568..62bb8237 100644 --- a/docs/Validation-and-Operations.md +++ b/docs/Validation-and-Operations.md @@ -109,7 +109,7 @@ Use `activate` only after manifest validation succeeds. The workflow then writes The vendor workflow also runs `openClOptimizerFamilyPayloadFixtureTest` after the main validation bucket. It must produce `fixture-summary.properties` with two complete families and fourteen durable files. This fixture proves the nested artifact contract is uploadable and path-safe; it does not alter real-workload optimizer-family counts or production readiness. -`validateOpenClRuntimeIrOptimizerEvidence` checks `runtime-ir-optimizer-evidence.properties` under `runtime-compile-artifacts`. It fails if runtime-equivalence review, optimized-artifact candidate, or review-package guardrails stop being fail-closed, if production mutation or selected-IR replacement becomes enabled, if candidate selection is applied, if a required review package has no blocker, or if a package is marked complete before manual-review activation exists. When materialization-family evidence is present, it also verifies family-specific review-ready invariants: safe-local-CSE needs existing-local reuse or introduced-temp evidence, dominance/side-effect proof, matching text replacement, required runtime-equivalence/approval gates, and passed payloads; fast-math `mad/fma` needs matching text replacement, fast-math policy evidence, required runtime-equivalence/approval gates, and passed payloads; `clamp` needs matching text replacement, strict-float/no-fast-math safety evidence, required runtime-equivalence/approval gates, and passed payloads; `step` needs matching text replacement, direct/inverted step accounting, strict comparison/equality/NaN safety evidence, required runtime-equivalence/approval gates, and passed payloads; `mix` needs matching text replacement, canonical/expanded/MAD-expanded accounting, fast-math/reassociation evidence when required, required runtime-equivalence/approval gates, and passed payloads; loop vectorization needs matching text replacement, fixed trip-count proof, contiguous-load proof, ordered-reduction preservation, typed-body materialization/invalidation accounting, required runtime-equivalence/approval gates, and passed payloads; typed dead-code removal needs consistent node counts, side-effect proof, required runtime-equivalence/approval gates, and passed payloads. The operational and NVIDIA routines run this validator after `openClValidationReport`. +`validateOpenClRuntimeIrOptimizerEvidence` checks `runtime-ir-optimizer-evidence.properties` under `runtime-compile-artifacts` when optimizer evidence is recorded. The operational and NVIDIA routines invoke it with `--allow-missing`, because IR optimizer evidence is optional in normal vendor CI and may legitimately be `not-recorded`. When evidence is present, it fails if runtime-equivalence review, optimized-artifact candidate, or review-package guardrails stop being fail-closed, if production mutation or selected-IR replacement becomes enabled, if candidate selection is applied, if a required review package has no blocker, or if a package is marked complete before manual-review activation exists. When materialization-family evidence is present, it also verifies family-specific review-ready invariants: safe-local-CSE needs existing-local reuse or introduced-temp evidence, dominance/side-effect proof, matching text replacement, required runtime-equivalence/approval gates, and passed payloads; fast-math `mad/fma` needs matching text replacement, fast-math policy evidence, required runtime-equivalence/approval gates, and passed payloads; `clamp` needs matching text replacement, strict-float/no-fast-math safety evidence, required runtime-equivalence/approval gates, and passed payloads; `step` needs matching text replacement, direct/inverted step accounting, strict comparison/equality/NaN safety evidence, required runtime-equivalence/approval gates, and passed payloads; `mix` needs matching text replacement, canonical/expanded/MAD-expanded accounting, fast-math/reassociation evidence when required, required runtime-equivalence/approval gates, and passed payloads; loop vectorization needs matching text replacement, fixed trip-count proof, contiguous-load proof, ordered-reduction preservation, typed-body materialization/invalidation accounting, required runtime-equivalence/approval gates, and passed payloads; typed dead-code removal needs consistent node counts, side-effect proof, required runtime-equivalence/approval gates, and passed payloads. Run the CLI without `--allow-missing` when a local or release gate must require at least one optimizer evidence artifact. ## Optional IR Validation diff --git a/processor/build.gradle b/processor/build.gradle index 8452da88..bc2477af 100644 --- a/processor/build.gradle +++ b/processor/build.gradle @@ -653,12 +653,11 @@ tasks.register('validateOpenClCompilerResourceDrift', JavaExec) { tasks.register('validateOpenClRuntimeIrOptimizerEvidence', JavaExec) { group = 'verification' - description = 'Fails OpenCL CI when runtime IR optimizer evidence loses fail-closed review-package guardrails.' + description = 'Validates optional runtime IR optimizer evidence when recorded and fails if fail-closed guardrails regress.' dependsOn 'openClValidationReport' classpath = sourceSets.main.runtimeClasspath mainClass = 'net.sixik.ga_utils.javatogpu.runtime.opencl.OpenClRuntimeIrOptimizerEvidenceValidatorCli' - args openClRuntimeIrOptimizerEvidenceDirectory.absolutePath - inputs.dir(openClRuntimeIrOptimizerEvidenceDirectory) + args '--allow-missing', openClRuntimeIrOptimizerEvidenceDirectory.absolutePath } tasks.register('prepareOpenClKernelLaunchAdvisoryNegativeFixture', JavaExec) { diff --git a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCli.java b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCli.java index b25395d1..51e362d6 100644 --- a/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCli.java +++ b/processor/src/main/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCli.java @@ -28,6 +28,11 @@ public static void main(String[] args) throws IOException { Path artifactPath = cliArguments.artifactPath().orElseThrow(); List artifacts = evidenceArtifacts(artifactPath); if (artifacts.isEmpty()) { + if (cliArguments.allowMissing()) { + System.out.println("Runtime IR optimizer evidence not recorded under " + artifactPath + + "; optional optimizer evidence validation skipped."); + return; + } throw new IllegalStateException("Missing runtime IR optimizer evidence artifact under " + artifactPath); } @@ -58,11 +63,12 @@ public static void main(String[] args) throws IOException { } private static CliArguments parseArguments(String[] args) { - if (args == null || args.length == 0 || args.length > 2) { - return new CliArguments(java.util.Optional.empty(), false); + if (args == null || args.length == 0 || args.length > 3) { + return new CliArguments(java.util.Optional.empty(), false, false); } Path artifactPath = null; boolean allowExperimentalApply = false; + boolean allowMissing = false; for (String arg : args) { if (arg == null || arg.isBlank()) { continue; @@ -71,12 +77,16 @@ private static CliArguments parseArguments(String[] args) { allowExperimentalApply = true; continue; } + if ("--allow-missing".equals(arg)) { + allowMissing = true; + continue; + } if (artifactPath != null) { - return new CliArguments(java.util.Optional.empty(), allowExperimentalApply); + return new CliArguments(java.util.Optional.empty(), allowExperimentalApply, allowMissing); } artifactPath = Path.of(arg); } - return new CliArguments(java.util.Optional.ofNullable(artifactPath), allowExperimentalApply); + return new CliArguments(java.util.Optional.ofNullable(artifactPath), allowExperimentalApply, allowMissing); } private static List evidenceArtifacts(Path path) throws IOException { @@ -1179,7 +1189,11 @@ private enum IntrinsicMaterializationKind { MIX } - private record CliArguments(java.util.Optional artifactPath, boolean allowExperimentalApply) { + private record CliArguments( + java.util.Optional artifactPath, + boolean allowExperimentalApply, + boolean allowMissing + ) { } private static String requirePresent(Path artifact, Properties properties, String key) { diff --git a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCliTest.java b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCliTest.java index 2686476c..f43b8b73 100644 --- a/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCliTest.java +++ b/processor/src/test/java/net/sixik/ga_utils/javatogpu/runtime/opencl/OpenClRuntimeIrOptimizerEvidenceValidatorCliTest.java @@ -50,6 +50,13 @@ void rejectsMissingArtifact() { ); } + @Test + void acceptsMissingArtifactWhenExplicitlyAllowed() { + assertDoesNotThrow(() -> OpenClRuntimeIrOptimizerEvidenceValidatorCli.main( + new String[]{"--allow-missing", temporaryDirectory.resolve("missing").toString()} + )); + } + @Test void rejectsProductionMutationGuardrailRegression() throws Exception { Path artifact = temporaryDirectory.resolve(GpuRuntimeCompileArtifactDumper.RUNTIME_IR_OPTIMIZER_EVIDENCE_ARTIFACT); From 157b71e55a4452906064c95c6d8df1af0f6de8ca Mon Sep 17 00:00:00 2001 From: Bogdan <142807122+DeusSixik@users.noreply.github.com> Date: Wed, 15 Jul 2026 14:38:20 +0300 Subject: [PATCH 32/32] feat: Update PUBLISH & versions --- README.md | 6 +- build.gradle | 61 ++++++++++++- docs/Alpha-Release-Checklist.md | 10 +-- docs/Getting-Started.md | 6 +- docs/IR-Validation.md | 12 +-- docs/Publishing.md | 146 +++++++++++++++----------------- 6 files changed, 146 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index e169b3cd..f9c28cbc 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,8 @@ Add JavaToGpu as both a dependency and an annotation processor: ```groovy dependencies { - implementation 'io.github.deussixik:javatogpu:0.1.0-alpha.1' - annotationProcessor 'io.github.deussixik:javatogpu:0.1.0-alpha.1' + implementation 'io.github.deussixik:javatogpu:0.1.0-alpha.2' + annotationProcessor 'io.github.deussixik:javatogpu:0.1.0-alpha.2' } ``` @@ -30,7 +30,7 @@ Optional stricter IR validation: ```groovy dependencies { - annotationProcessor 'io.github.deussixik:javatogpu-ir-validation:0.1.0-alpha.1' + annotationProcessor 'io.github.deussixik:javatogpu-ir-validation:0.1.0-alpha.2' } tasks.withType(JavaCompile).configureEach { diff --git a/build.gradle b/build.gradle index 222ae457..a10a5daa 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,14 @@ plugins { } group = 'io.github.deussixik' -version = findProperty('javatogpu.version') ?: '0.1.0-alpha.1' +version = findProperty('javatogpu.version') ?: '0.1.0-alpha.2' + +ext.javatogpuPublishedArtifacts = [ + ':processor' : 'javatogpu', + ':ir-validation' : 'javatogpu-ir-validation', + ':ir-optimizer' : 'javatogpu-ir-optimizer', + ':ir-vendor-optimizer' : 'javatogpu-ir-vendor-optimizer' +] allprojects { group = rootProject.group @@ -18,4 +25,56 @@ test { useJUnitPlatform() } +tasks.register('printJavaToGpuPublicationCoordinates') { + group = 'publishing' + description = 'Prints the JavaToGpu Maven coordinates prepared for publication.' + doLast { + javatogpuPublishedArtifacts.each { projectPath, artifactId -> + println "${projectPath} -> ${rootProject.group}:${artifactId}:${rootProject.version}" + } + } +} + +tasks.register('validateJavaToGpuMavenCentralReleaseReadiness') { + group = 'publishing' + description = 'Runs Maven Central release-readiness guards for all published JavaToGpu artifacts.' + dependsOn javatogpuPublishedArtifacts.keySet().collect { projectPath -> + "${projectPath}:validateMavenCentralReleaseReadiness" + } +} + +tasks.register('publishJavaToGpuToLocalStaging') { + group = 'publishing' + description = 'Publishes all JavaToGpu Maven artifacts to their local staging repositories.' + dependsOn javatogpuPublishedArtifacts.keySet().collect { projectPath -> + "${projectPath}:publishMavenJavaPublicationToLocalStagingRepository" + } +} + +tasks.register('publishJavaToGpuSnapshotsToCentral') { + group = 'publishing' + description = 'Publishes all JavaToGpu Maven artifacts to the Maven Central snapshot repository.' + dependsOn javatogpuPublishedArtifacts.keySet().collect { projectPath -> + "${projectPath}:publishMavenJavaPublicationToCentralSnapshotsRepository" + } + doFirst { + if (!rootProject.version.toString().endsWith('-SNAPSHOT')) { + throw new GradleException('Snapshot publishing requires -Pjavatogpu.version=-SNAPSHOT') + } + } +} + +tasks.register('publishJavaToGpuReleasesToCentral') { + group = 'publishing' + description = 'Publishes all JavaToGpu Maven artifacts to the Maven Central release staging endpoint.' + dependsOn javatogpuPublishedArtifacts.keySet().collect { projectPath -> + "${projectPath}:publishMavenJavaPublicationToCentralReleasesRepository" + } + doFirst { + if (rootProject.version.toString().endsWith('-SNAPSHOT')) { + throw new GradleException('Release publishing requires a non-SNAPSHOT javatogpu.version') + } + } +} + apply from: 'gradle/ir-validation-dogfooding.gradle' diff --git a/docs/Alpha-Release-Checklist.md b/docs/Alpha-Release-Checklist.md index ce5e29fe..3bdaeb6f 100644 --- a/docs/Alpha-Release-Checklist.md +++ b/docs/Alpha-Release-Checklist.md @@ -4,10 +4,10 @@ Use this checklist before publishing a public alpha build. ## Release Label -Recommended first public version: +Recommended current public alpha version: ```text -v0.1.0-alpha.1 +v0.1.0-alpha.2 ``` Recommended positioning: @@ -27,7 +27,7 @@ Avoid calling the project stable or production-ready. NVIDIA and AMD OpenCL vali - The OpenCL operational routine is green on the currently available hardware. - The latest OpenCL validation report is generated and can be referenced in release notes. - `validateMavenCentralReleaseReadiness` passes for every artifact being published. -- Maven local staging has been generated for both `javatogpu` and `javatogpu-ir-validation` when releasing both artifacts. +- Maven local staging has been generated for every published artifact: `javatogpu`, `javatogpu-ir-validation`, `javatogpu-ir-optimizer`, and `javatogpu-ir-vendor-optimizer`. - Known unsupported features are documented instead of implied to work. - Public docs do not include private planning-only notes as user-facing promises. @@ -54,9 +54,9 @@ Key files: ## Release Notes Template ```markdown -# JavaToGpu v0.1.0-alpha.1 +# JavaToGpu v0.1.0-alpha.2 -This is the first public alpha of JavaToGpu, an experimental Java-to-OpenCL compiler and runtime for GPU-safe Java kernels. +This is a public alpha of JavaToGpu, an experimental Java-to-OpenCL compiler and runtime for GPU-safe Java kernels. ## Validated Environment diff --git a/docs/Getting-Started.md b/docs/Getting-Started.md index 8a306a69..4a74b5d6 100644 --- a/docs/Getting-Started.md +++ b/docs/Getting-Started.md @@ -8,8 +8,8 @@ Add JavaToGpu as both a dependency and an annotation processor: ```groovy dependencies { - implementation 'io.github.deussixik:javatogpu:0.1.0-alpha.1' - annotationProcessor 'io.github.deussixik:javatogpu:0.1.0-alpha.1' + implementation 'io.github.deussixik:javatogpu:0.1.0-alpha.2' + annotationProcessor 'io.github.deussixik:javatogpu:0.1.0-alpha.2' } ``` @@ -102,7 +102,7 @@ The optional IR validation module gives stricter compiler diagnostics and CI-fri ```groovy dependencies { - annotationProcessor 'io.github.deussixik:javatogpu-ir-validation:0.1.0-alpha.1' + annotationProcessor 'io.github.deussixik:javatogpu-ir-validation:0.1.0-alpha.2' } tasks.withType(JavaCompile).configureEach { diff --git a/docs/IR-Validation.md b/docs/IR-Validation.md index 0132890d..c940b9a3 100644 --- a/docs/IR-Validation.md +++ b/docs/IR-Validation.md @@ -22,10 +22,10 @@ Use the same version as the main JavaToGpu artifact: ```groovy dependencies { - implementation 'io.github.deussixik:javatogpu:0.1.0-alpha.1' - annotationProcessor 'io.github.deussixik:javatogpu:0.1.0-alpha.1' + implementation 'io.github.deussixik:javatogpu:0.1.0-alpha.2' + annotationProcessor 'io.github.deussixik:javatogpu:0.1.0-alpha.2' - annotationProcessor 'io.github.deussixik:javatogpu-ir-validation:0.1.0-alpha.1' + annotationProcessor 'io.github.deussixik:javatogpu-ir-validation:0.1.0-alpha.2' } ``` @@ -100,11 +100,11 @@ Use this flow when you are developing kernels and want actionable feedback witho ```groovy dependencies { - implementation 'io.github.deussixik:javatogpu:0.1.0-alpha.1' - annotationProcessor 'io.github.deussixik:javatogpu:0.1.0-alpha.1' + implementation 'io.github.deussixik:javatogpu:0.1.0-alpha.2' + annotationProcessor 'io.github.deussixik:javatogpu:0.1.0-alpha.2' // Optional: adds read-only IR validation providers. - annotationProcessor 'io.github.deussixik:javatogpu-ir-validation:0.1.0-alpha.1' + annotationProcessor 'io.github.deussixik:javatogpu-ir-validation:0.1.0-alpha.2' } ``` diff --git a/docs/Publishing.md b/docs/Publishing.md index 47db4cbe..35e52439 100644 --- a/docs/Publishing.md +++ b/docs/Publishing.md @@ -1,51 +1,38 @@ # Publishing Guide -This guide describes how to publish the JavaToGpu Maven artifacts: the main `processor` module and -the optional `ir-validation` strict-build add-on. +This guide describes how to publish JavaToGpu Maven artifacts to Maven Central. ## Artifact Coordinates +All published artifacts share the same release version: + ```text groupId: io.github.deussixik -artifactId: javatogpu -version: 0.1.0-alpha.1 +version: 0.1.0-alpha.2 ``` -The Gradle project that owns the main publication is: +Published modules: ```text -:processor +:processor -> io.github.deussixik:javatogpu +:ir-validation -> io.github.deussixik:javatogpu-ir-validation +:ir-optimizer -> io.github.deussixik:javatogpu-ir-optimizer +:ir-vendor-optimizer -> io.github.deussixik:javatogpu-ir-vendor-optimizer ``` +`examples-app` and `test-app` are consumer/demo applications and are not published. + ## What Is Published -The `mavenJava` publication includes: +Each published module uses the `mavenJava` publication and includes: - compiled jar - sources jar - javadoc jar -- generated POM metadata -- PGP signatures for release publishing - -Gradle module metadata is disabled for publication so a build-host-specific LWJGL native classifier is not published as universal metadata. Consumers should add their own LWJGL native classifier for their operating system when they need runtime OpenCL execution. - -## Optional IR Validation Artifact - -The `ir-validation` module is a separate strict-build add-on: - -```text -groupId: io.github.deussixik -artifactId: javatogpu-ir-validation -version: 0.1.0-alpha.1 -``` +- generated Maven POM metadata +- PGP signatures for release publishing when signing credentials are present -It contributes an optional compiler IR validation provider through Java `ServiceLoader`. Users add it to the annotation-processor path and enable `-Ajavatogpu.irValidation=diagnostic`, `strictSafety`, or `strictOptimizer` when they want extra lowered-IR checks before OpenCL emission. - -The Gradle project that owns the optional publication is: - -```text -:ir-validation -``` +Gradle module metadata is disabled for publication so build-host-specific LWJGL native classifiers are not published as universal metadata. Consumers should add their own LWJGL native classifier for their operating system when they need runtime OpenCL execution. ## Release Dependency Baseline @@ -55,7 +42,7 @@ The OpenCL runtime depends on the published Packager release: io.github.deussixik:packager:1.0.0-alpha.1 ``` -Release publishing is guarded by `validateMavenCentralReleaseReadiness`, which fails if the generated POM contains snapshot dependencies or JitPack branch dependencies. +Release publishing is guarded by `validateMavenCentralReleaseReadiness`, which fails if a generated POM contains snapshot dependencies or JitPack branch dependencies. ## Secrets @@ -87,69 +74,69 @@ SIGNING_IN_MEMORY_KEY_PASSWORD The local `gradle.properties` file in this repository root is ignored by Git as an extra safety net, but the preferred location is still `~/.gradle/gradle.properties`. -## Local Staging Check +## Aggregate Gradle Tasks -Before publishing remotely, build the local staging repository: +The root build exposes convenience tasks for the full artifact set: ```powershell -.\gradlew.bat :processor:publishMavenJavaPublicationToLocalStagingRepository --console=plain -.\gradlew.bat :ir-validation:publishMavenJavaPublicationToLocalStagingRepository --console=plain +.\gradlew.bat printJavaToGpuPublicationCoordinates --console=plain +.\gradlew.bat validateJavaToGpuMavenCentralReleaseReadiness --console=plain +.\gradlew.bat publishJavaToGpuToLocalStaging --console=plain +.\gradlew.bat publishJavaToGpuSnapshotsToCentral -Pjavatogpu.version=0.1.0-SNAPSHOT --console=plain +.\gradlew.bat publishJavaToGpuReleasesToCentral -Pjavatogpu.version=0.1.0-alpha.2 --console=plain ``` -Output is written under: - -```text -processor/build/maven-staging/ -ir-validation/build/maven-staging/ -``` - -Inspect the generated POMs before release: +PowerShell users can quote the Gradle property if the shell splits `-P` incorrectly: -```text -processor/build/publications/mavenJava/pom-default.xml -ir-validation/build/publications/mavenJava/pom-default.xml +```powershell +.\gradlew.bat "publishJavaToGpuReleasesToCentral" "-Pjavatogpu.version=0.1.0-alpha.2" --console=plain ``` -## Snapshot Publishing +## Per-Module Commands -Use a snapshot version when publishing to the Central snapshot repository: +Use per-module tasks when publishing or inspecting a single artifact: ```powershell -.\gradlew.bat :processor:publishMavenJavaPublicationToCentralSnapshotsRepository -Pjavatogpu.version=0.1.0-SNAPSHOT --console=plain -.\gradlew.bat :ir-validation:publishMavenJavaPublicationToCentralSnapshotsRepository -Pjavatogpu.version=0.1.0-SNAPSHOT --console=plain +.\gradlew.bat :processor:publishMavenJavaPublicationToLocalStagingRepository --console=plain +.\gradlew.bat :ir-validation:publishMavenJavaPublicationToLocalStagingRepository --console=plain +.\gradlew.bat :ir-optimizer:publishMavenJavaPublicationToLocalStagingRepository --console=plain +.\gradlew.bat :ir-vendor-optimizer:publishMavenJavaPublicationToLocalStagingRepository --console=plain ``` -PowerShell users can quote the Gradle property if the shell splits `-P` incorrectly: - -```powershell -.\gradlew.bat ":processor:publishMavenJavaPublicationToCentralSnapshotsRepository" "-Pjavatogpu.version=0.1.0-SNAPSHOT" --console=plain -.\gradlew.bat ":ir-validation:publishMavenJavaPublicationToCentralSnapshotsRepository" "-Pjavatogpu.version=0.1.0-SNAPSHOT" --console=plain -``` +Remote release publishing uses the matching `CentralReleasesRepository` task in each module. Snapshot publishing uses the matching `CentralSnapshotsRepository` task. -## Release Publishing +## Local Staging Check -For the first public alpha: +Before publishing remotely, build the local staging repositories: ```powershell -.\gradlew.bat :processor:publishMavenJavaPublicationToCentralReleasesRepository -Pjavatogpu.version=0.1.0-alpha.1 --console=plain -.\gradlew.bat :ir-validation:publishMavenJavaPublicationToCentralReleasesRepository -Pjavatogpu.version=0.1.0-alpha.1 --console=plain +.\gradlew.bat publishJavaToGpuToLocalStaging --console=plain ``` -PowerShell-safe form: +Output is written under each module's build directory: -```powershell -.\gradlew.bat ":processor:publishMavenJavaPublicationToCentralReleasesRepository" "-Pjavatogpu.version=0.1.0-alpha.1" --console=plain -.\gradlew.bat ":ir-validation:publishMavenJavaPublicationToCentralReleasesRepository" "-Pjavatogpu.version=0.1.0-alpha.1" --console=plain +```text +processor/build/maven-staging/ +ir-validation/build/maven-staging/ +ir-optimizer/build/maven-staging/ +ir-vendor-optimizer/build/maven-staging/ ``` -After upload, complete the release from the Maven Central / Sonatype portal if the deployment lands in a staging flow that requires manual close/release. Release the main artifact and the optional IR validation artifact with the same version. +Inspect the generated POMs before release: + +```text +processor/build/publications/mavenJava/pom-default.xml +ir-validation/build/publications/mavenJava/pom-default.xml +ir-optimizer/build/publications/mavenJava/pom-default.xml +ir-vendor-optimizer/build/publications/mavenJava/pom-default.xml +``` ## Recommended Release Flow -1. Run the normal tests for both published modules: +1. Run normal tests for published modules: ```powershell -.\gradlew.bat :processor:test :ir-validation:test --console=plain +.\gradlew.bat :processor:test :ir-validation:test :ir-optimizer:test :ir-vendor-optimizer:test --console=plain ``` 2. Run the OpenCL operational routine on the validated GPU machine: @@ -158,35 +145,40 @@ After upload, complete the release from the Maven Central / Sonatype portal if t .\gradlew.bat :processor:openClOperationalRoutine --rerun-tasks --console=plain ``` -3. Run Maven Central release-readiness guards for both publications: +3. Run Maven Central release-readiness guards: ```powershell -.\gradlew.bat :processor:validateMavenCentralReleaseReadiness :ir-validation:validateMavenCentralReleaseReadiness --console=plain +.\gradlew.bat validateJavaToGpuMavenCentralReleaseReadiness --console=plain ``` 4. Build local Maven staging: ```powershell -.\gradlew.bat :processor:publishMavenJavaPublicationToLocalStagingRepository --console=plain -.\gradlew.bat :ir-validation:publishMavenJavaPublicationToLocalStagingRepository --console=plain +.\gradlew.bat publishJavaToGpuToLocalStaging --console=plain ``` -5. Inspect both generated POM files: +5. Inspect generated POM files and staged artifacts. -```text -processor/build/publications/mavenJava/pom-default.xml -ir-validation/build/publications/mavenJava/pom-default.xml +6. Publish the release: + +```powershell +.\gradlew.bat publishJavaToGpuReleasesToCentral -Pjavatogpu.version=0.1.0-alpha.2 --console=plain ``` -6. Publish snapshot or release for both modules with the commands above. +After upload, complete the release from the Maven Central / Sonatype portal if the deployment lands in a staging flow that requires manual close/release. Release all JavaToGpu artifacts with the same version. ## Consumer Example ```groovy dependencies { - implementation 'io.github.deussixik:javatogpu:0.1.0-alpha.1' - annotationProcessor 'io.github.deussixik:javatogpu:0.1.0-alpha.1' - annotationProcessor 'io.github.deussixik:javatogpu-ir-validation:0.1.0-alpha.1' // optional strict IR checks + implementation 'io.github.deussixik:javatogpu:0.1.0-alpha.2' + annotationProcessor 'io.github.deussixik:javatogpu:0.1.0-alpha.2' + + annotationProcessor 'io.github.deussixik:javatogpu-ir-validation:0.1.0-alpha.2' // optional strict IR checks + + // Optional optimizer proposal providers. + implementation 'io.github.deussixik:javatogpu-ir-optimizer:0.1.0-alpha.2' + implementation 'io.github.deussixik:javatogpu-ir-vendor-optimizer:0.1.0-alpha.2' runtimeOnly 'org.lwjgl:lwjgl::natives-windows' }