1 /* 2 * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package hat; 26 27 28 import hat.backend.Backend; 29 import hat.buffer.Buffer; 30 import hat.buffer.BufferAllocator; 31 import hat.buffer.BufferTracker; 32 import hat.ifacemapper.BoundSchema; 33 import hat.ifacemapper.SegmentMapper; 34 import hat.optools.OpTk; 35 36 import java.lang.invoke.MethodHandles; 37 import java.lang.reflect.Method; 38 39 import jdk.incubator.code.Op; 40 import jdk.incubator.code.Quotable; 41 import jdk.incubator.code.Quoted; 42 import jdk.incubator.code.dialect.java.JavaOp; 43 44 import java.util.HashMap; 45 import java.util.Map; 46 import java.util.ServiceLoader; 47 import java.util.function.Consumer; 48 import java.util.function.Predicate; 49 50 import static hat.backend.Backend.FIRST; 51 52 /** 53 * This class provides the developer facing view of HAT, and wraps a <a href="backend/Backend.html">Backend</a> capable of 54 * executing <b>NDRange</b> style execution. 55 * <p/> 56 * An Accelerator is provided a <a href="java/lang/invoke/MethodHandles.Lookup.html">MethodHandles.Lookup</a> with visibility to the 57 * compute to be performed. 58 * <p/> 59 * As well we either a <a href="backend/Backend.html">Backend</a> directly 60 * <pre> 61 * Accelerator accelerator = 62 * new Accelerator(MethodHandles.lookup(), 63 * new JavaMultiThreadedBackend()); 64 * </pre> 65 * or a {@code java.util.function.Predicate<Backend>} which can be used to select the required {@code Backend} 66 * loaded via Javas ServiceLoader mechanism 67 * {@code} 68 * <pre> 69 * Accelerator accelerator = 70 * new Accelerator(MethodHandles.lookup(), 71 * be -> be.name().startsWith("OpenCL)); 72 * </pre>} 73 * 74 * @author Gary Frost 75 */ 76 public class Accelerator implements BufferAllocator, BufferTracker { 77 public MethodHandles.Lookup lookup; 78 public final Backend backend; 79 80 private final Map<Method, hat.ComputeContext> cache = new HashMap<>(); 81 82 public NDRange range(int max) { 83 NDRange ndRange = new NDRange(this); 84 ndRange.kid = new KernelContext(ndRange, max); 85 return ndRange; 86 } 87 88 public NDRange range(int maxX, int maxY) { 89 NDRange ndRange = new NDRange(this); 90 ndRange.kid = new KernelContext(ndRange, maxX, maxY); 91 return ndRange; 92 } 93 94 public NDRange range(int maxX, int maxY, int maxZ) { 95 NDRange ndRange = new NDRange(this); 96 ndRange.kid = new KernelContext(ndRange, maxX, maxY, maxZ); 97 return ndRange; 98 } 99 100 public NDRange range(ComputeRange computeRange) { 101 NDRange ndRange = new NDRange(this); 102 ndRange.kid = new KernelContext(ndRange, computeRange); 103 return ndRange; 104 } 105 106 protected Accelerator(MethodHandles.Lookup lookup, ServiceLoader.Provider<Backend> provider) { 107 this(lookup, provider.get()); 108 } 109 public Accelerator(MethodHandles.Lookup lookup) { 110 this(lookup, FIRST); 111 } 112 113 /** 114 * @param lookup 115 * @param backend 116 */ 117 public Accelerator(MethodHandles.Lookup lookup, Backend backend) { 118 this.lookup = lookup; 119 this.backend = backend; 120 } 121 122 /** 123 * @param lookup 124 * @param backendPredicate 125 */ 126 public Accelerator(MethodHandles.Lookup lookup, Predicate<Backend> backendPredicate) { 127 this(lookup, Backend.getBackend(backendPredicate)); 128 } 129 130 @Override 131 public <T extends Buffer> T allocate(SegmentMapper<T> segmentMapper, BoundSchema<T> boundShema) { 132 return backend.allocate(segmentMapper, boundShema); 133 } 134 135 @Override 136 public void preMutate(Buffer b) { 137 if (backend instanceof BufferTracker) { 138 ((BufferTracker) backend).preMutate(b); 139 } 140 } 141 142 @Override 143 public void postMutate(Buffer b) { 144 if (backend instanceof BufferTracker) { 145 ((BufferTracker) backend).postMutate(b); 146 } 147 } 148 149 @Override 150 public void preAccess(Buffer b) { 151 if (backend instanceof BufferTracker) { 152 ((BufferTracker) backend).preAccess(b); 153 } 154 } 155 156 @Override 157 public void postAccess(Buffer b) { 158 if (backend instanceof BufferTracker) { 159 ((BufferTracker) backend).postAccess(b); 160 } 161 } 162 163 /** 164 * An interface used for wrapping the compute entrypoint of work to be performed by the Accelerator. 165 * <p/> 166 * So given a ComputeClass such as... 167 * <pre> 168 * public class MyComputeClass { 169 * @ CodeReflection 170 * public static void addDeltaKernel(KernelContext kc, S32Array arrayOfInt, int delta) { 171 * arrayOfInt.array(kc.x, arrayOfInt.array(kc.x)+delta); 172 * } 173 * 174 * @ CodeReflection 175 * static public void doSomeWork(final ComputeContext cc, S32Array arrayOfInt) { 176 * } 177 * } 178 * </pre> 179 * The accelerator will be passed the doSomeWork entrypoint, wrapped in a {@code QuotableComputeContextConsumer} 180 * <pre> 181 * accelerator.compute(cc -> 182 * MyCompute.doSomeWork(cc, arrayOfInt) 183 * ); 184 * </pre> 185 */ 186 public interface QuotableComputeContextConsumer extends Quotable, Consumer<ComputeContext> { 187 } 188 189 /** 190 * This method provides the Accelerator with the {@code Compute Entrypoint} from a Compute class. 191 * <p> 192 * The entrypoint is wrapped in a <a href="QuotableComputeContextConsumer.html">QuotableComputeContextConsumer</a> lambda. 193 * 194 * <pre> 195 * accelerator.compute(cc -> 196 * MyCompute.doSomeWork(cc, intArray) 197 * ) 198 * </pre> 199 */ 200 public void compute(QuotableComputeContextConsumer quotableComputeContextConsumer) { 201 Quoted quoted = Op.ofQuotable(quotableComputeContextConsumer).orElseThrow(); 202 JavaOp.LambdaOp lambda = (JavaOp.LambdaOp) quoted.op(); 203 Method method = OpTk.methodOrThrow(lookup,OpTk.getQuotableTargetInvokeOpWrapper(lambda)); 204 // Create (or get cached) a compute context which closes over compute entryppint and reachable kernels. 205 // The models of all compute and kernel methods are passed to the backend during creation 206 // The backend may well mutate the models. 207 // It will also use this opportunity to generate ISA specific code for the kernels. 208 ComputeContext computeContext = cache.computeIfAbsent(method, (_) -> new ComputeContext(this, method)); 209 // Here we get the captured values from the Quotable 210 Object[] args = OpTk.getQuotableCapturedValues(lambda,quoted, method); 211 args[0] = computeContext; 212 // now ask the backend to execute 213 backend.dispatchCompute(computeContext, args); 214 } 215 }