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 /**
54 * This class provides the developer facing view of HAT, and wraps a <a href="backend/Backend.html">Backend</a> capable of
55 * executing <b>NDRange</b> style execution.
56 * <p/>
57 * An Accelerator is provided a <a href="java/lang/invoke/MethodHandles.Lookup.html">MethodHandles.Lookup</a> with visibility to the
58 * compute to be performed.
59 * <p/>
60 * As well we either a <a href="backend/Backend.html">Backend</a> directly
61 * <pre>
62 * Accelerator accelerator =
63 * new Accelerator(MethodHandles.lookup(),
64 * new JavaMultiThreadedBackend());
65 * </pre>
66 * or a {@code java.util.function.Predicate<Backend>} which can be used to select the required {@code Backend}
67 * loaded via Javas ServiceLoader mechanism
68 * {@code}
69 * <pre>
70 * Accelerator accelerator =
71 * new Accelerator(MethodHandles.lookup(),
72 * be -> be.name().startsWith("OpenCL));
73 * </pre>}
74 *
75 * @author Gary Frost
76 */
77 public class Accelerator implements BufferAllocator, BufferTracker {
78 public MethodHandles.Lookup lookup;
79 public final Backend backend;
80
81 private final Map<Method, hat.ComputeContext> cache = new HashMap<>();
82
83 public NDRange range(int max) {
84 NDRange ndRange = new NDRange(this);
85 ndRange.kid = new KernelContext(ndRange, max);
86 return ndRange;
87 }
88
89 public NDRange range(int maxX, int maxY) {
90 NDRange ndRange = new NDRange(this);
91 ndRange.kid = new KernelContext(ndRange, maxX, maxY);
92 return ndRange;
93 }
94
95 public NDRange range(int maxX, int maxY, int maxZ) {
96 NDRange ndRange = new NDRange(this);
97 ndRange.kid = new KernelContext(ndRange, maxX, maxY, maxZ);
98 return ndRange;
99 }
100
101 public NDRange range(ComputeRange computeRange) {
102 NDRange ndRange = new NDRange(this);
103 ndRange.kid = new KernelContext(ndRange, computeRange);
104 return ndRange;
105 }
106
107 protected Accelerator(MethodHandles.Lookup lookup, ServiceLoader.Provider<Backend> provider) {
108 this(lookup, provider.get());
109 }
110 public Accelerator(MethodHandles.Lookup lookup) {
111 this(lookup, FIRST);
112 }
113
114 /**
115 * @param lookup
116 * @param backend
117 */
118 public Accelerator(MethodHandles.Lookup lookup, Backend backend) {
119 this.lookup = lookup;
120 this.backend = backend;
121 }
122
123 /**
124 * @param lookup
125 * @param backendPredicate
126 */
127 public Accelerator(MethodHandles.Lookup lookup, Predicate<Backend> backendPredicate) {
128 this(lookup, Backend.getBackend(backendPredicate));
129 }
130
131 @Override
132 public <T extends Buffer> T allocate(SegmentMapper<T> segmentMapper, BoundSchema<T> boundShema) {
133 return backend.allocate(segmentMapper, boundShema);
134 }
135
136 @Override
137 public void preMutate(Buffer b) {
138 if (backend instanceof BufferTracker) {
139 ((BufferTracker) backend).preMutate(b);
140 }
141 }
142
143 @Override
144 public void postMutate(Buffer b) {
145 if (backend instanceof BufferTracker) {
146 ((BufferTracker) backend).postMutate(b);
147 }
148 }
149
150 @Override
151 public void preAccess(Buffer b) {
152 if (backend instanceof BufferTracker) {
153 ((BufferTracker) backend).preAccess(b);
154 }
155 }
156
157 @Override
158 public void postAccess(Buffer b) {
159 if (backend instanceof BufferTracker) {
160 ((BufferTracker) backend).postAccess(b);
161 }
162 }
163
164 /**
165 * An interface used for wrapping the compute entrypoint of work to be performed by the Accelerator.
166 * <p/>
167 * So given a ComputeClass such as...
168 * <pre>
169 * public class MyComputeClass {
170 * @ CodeReflection
171 * public static void addDeltaKernel(KernelContext kc, S32Array arrayOfInt, int delta) {
172 * arrayOfInt.array(kc.x, arrayOfInt.array(kc.x)+delta);
173 * }
174 *
175 * @ CodeReflection
176 * static public void doSomeWork(final ComputeContext cc, S32Array arrayOfInt) {
177 * }
178 * }
179 * </pre>
180 * The accelerator will be passed the doSomeWork entrypoint, wrapped in a {@code QuotableComputeContextConsumer}
181 * <pre>
182 * accelerator.compute(cc ->
183 * MyCompute.doSomeWork(cc, arrayOfInt)
184 * );
185 * </pre>
186 */
187 public interface QuotableComputeContextConsumer extends Quotable, Consumer<ComputeContext> {
188 }
189 // convenience
190 public Config config(){
191 return backend.config();
192 }
193
194 /**
195 * This method provides the Accelerator with the {@code Compute Entrypoint} from a Compute class.
196 * <p>
197 * The entrypoint is wrapped in a <a href="QuotableComputeContextConsumer.html">QuotableComputeContextConsumer</a> lambda.
198 *
199 * <pre>
200 * accelerator.compute(cc ->
201 * MyCompute.doSomeWork(cc, intArray)
202 * )
203 * </pre>
204 */
205 public void compute(QuotableComputeContextConsumer quotableComputeContextConsumer) {
206 Quoted quoted = Op.ofQuotable(quotableComputeContextConsumer).orElseThrow();
207 JavaOp.LambdaOp lambda = (JavaOp.LambdaOp) quoted.op();
208 Method method = OpTk.methodOrThrow(lookup,OpTk.getQuotableTargetInvokeOpWrapper(lambda));
209 // Create (or get cached) a compute context which closes over compute entryppint and reachable kernels.
210 // The models of all compute and kernel methods are passed to the backend during creation
211 // The backend may well mutate the models.
212 // It will also use this opportunity to generate ISA specific code for the kernels.
213 ComputeContext computeContext = cache.computeIfAbsent(method, (_) -> new ComputeContext(this, method));
214 // Here we get the captured values from the Quotable
215 Object[] args = OpTk.getQuotableCapturedValues(lambda,quoted, method);
216 args[0] = computeContext;
217 // now ask the backend to execute
218 backend.dispatchCompute(computeContext, args);
219 }
220 }