Skip to content

Commit 0a9d8ff

Browse files
committed
implement _lru_cache_wrapper type for _functools
1 parent 8642f56 commit 0a9d8ff

File tree

13 files changed

+911
-186
lines changed

13 files changed

+911
-186
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@
159159
import com.oracle.graal.python.builtins.modules.ctypes.UnionTypeBuiltins;
160160
import com.oracle.graal.python.builtins.modules.functools.FunctoolsModuleBuiltins;
161161
import com.oracle.graal.python.builtins.modules.functools.KeyWrapperBuiltins;
162+
import com.oracle.graal.python.builtins.modules.functools.LruCacheWrapperBuiltins;
162163
import com.oracle.graal.python.builtins.modules.functools.PartialBuiltins;
163164
import com.oracle.graal.python.builtins.modules.hashlib.Blake2ModuleBuiltins;
164165
import com.oracle.graal.python.builtins.modules.hashlib.Blake2bObjectBuiltins;
@@ -581,6 +582,7 @@ private static PythonBuiltins[] initializeBuiltins(boolean nativeAccessAllowed)
581582
// _functools
582583
new KeyWrapperBuiltins(),
583584
new PartialBuiltins(),
585+
new LruCacheWrapperBuiltins(),
584586
new FunctoolsModuleBuiltins(),
585587

586588
new ErrnoModuleBuiltins(),

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
import static com.oracle.graal.python.nodes.BuiltinNames.J_DICT_VALUEITERATOR;
8080
import static com.oracle.graal.python.nodes.BuiltinNames.J_DICT_VALUES;
8181
import static com.oracle.graal.python.nodes.BuiltinNames.J_FOREIGN;
82+
import static com.oracle.graal.python.nodes.BuiltinNames.J_LRU_CACHE_WRAPPER;
8283
import static com.oracle.graal.python.nodes.BuiltinNames.J_MEMBER_DESCRIPTOR;
8384
import static com.oracle.graal.python.nodes.BuiltinNames.J_PARTIAL;
8485
import static com.oracle.graal.python.nodes.BuiltinNames.J_POSIX;
@@ -143,6 +144,8 @@ public enum PythonBuiltinClassType implements TruffleObject {
143144
PSimpleNamespace("SimpleNamespace", null, "types", Flags.PUBLIC_BASE_WDICT),
144145
PKeyWrapper("KeyWrapper", "_functools", "functools", Flags.PUBLIC_DERIVED_WODICT),
145146
PPartial(J_PARTIAL, "_functools", "functools", Flags.PUBLIC_BASE_WDICT),
147+
PLruListElem("_lru_list_elem", null, "functools", Flags.PUBLIC_DERIVED_WODICT),
148+
PLruCacheWrapper(J_LRU_CACHE_WRAPPER, "_functools", "functools", Flags.PUBLIC_BASE_WDICT),
146149
PDefaultDict(J_DEFAULTDICT, "_collections", "collections", Flags.PUBLIC_BASE_WODICT, DEFAULTDICT_M_FLAGS),
147150
PDeque(J_DEQUE, "_collections", Flags.PUBLIC_BASE_WODICT, DEQUE_M_FLAGS),
148151
PTupleGetter(J_TUPLE_GETTER, "_collections", Flags.PUBLIC_BASE_WODICT),

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/WeakRefModuleBuiltins.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,8 @@ public static int getBuiltinTypeWeaklistoffset(PythonBuiltinClassType cls) {
293293
case PStringIO -> 112; // _io.StringIO
294294
case PBufferedReader, // _io.BufferedReader
295295
PBufferedWriter, // _io.BufferedWriter
296-
PBufferedRandom // _io.BufferedRandom
296+
PBufferedRandom, // _io.BufferedRandom
297+
PLruCacheWrapper
297298
-> 144;
298299
case PTextIOWrapper -> 176; // _io.TextIOWrapper
299300

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/functools/FunctoolsModuleBuiltins.java

Lines changed: 27 additions & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -41,58 +41,68 @@
4141
package com.oracle.graal.python.builtins.modules.functools;
4242

4343
import static com.oracle.graal.python.builtins.PythonBuiltinClassType.PythonObject;
44-
import static com.oracle.graal.python.nodes.BuiltinNames.J_PARTIAL;
44+
import static com.oracle.graal.python.nodes.BuiltinNames.J_FUNCTOOLS;
4545
import static com.oracle.graal.python.nodes.ErrorMessages.REDUCE_EMPTY_SEQ;
46-
import static com.oracle.graal.python.nodes.ErrorMessages.S_ARG_MUST_BE_CALLABLE;
4746
import static com.oracle.graal.python.nodes.ErrorMessages.S_ARG_N_MUST_SUPPORT_ITERATION;
48-
import static com.oracle.graal.python.nodes.ErrorMessages.TYPE_S_TAKES_AT_LEAST_ONE_ARGUMENT;
49-
import static com.oracle.graal.python.nodes.SpecialMethodNames.J___INIT__;
5047
import static com.oracle.truffle.api.nodes.LoopNode.reportLoopCount;
5148

5249
import java.util.List;
5350

5451
import com.oracle.graal.python.builtins.Builtin;
5552
import com.oracle.graal.python.builtins.CoreFunctions;
53+
import com.oracle.graal.python.builtins.Python3Core;
5654
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
5755
import com.oracle.graal.python.builtins.PythonBuiltins;
58-
import com.oracle.graal.python.builtins.objects.PNone;
59-
import com.oracle.graal.python.builtins.objects.common.HashingStorage;
60-
import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageAddAllToOther;
61-
import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageCopy;
62-
import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageLen;
63-
import com.oracle.graal.python.builtins.objects.dict.PDict;
64-
import com.oracle.graal.python.builtins.objects.function.PKeyword;
6556
import com.oracle.graal.python.lib.GetNextNode;
66-
import com.oracle.graal.python.lib.PyCallableCheckNode;
6757
import com.oracle.graal.python.lib.PyObjectGetIter;
6858
import com.oracle.graal.python.nodes.PGuards;
59+
import com.oracle.graal.python.nodes.SpecialAttributeNames;
6960
import com.oracle.graal.python.nodes.call.CallNode;
7061
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
71-
import com.oracle.graal.python.nodes.function.PythonBuiltinNode;
7262
import com.oracle.graal.python.nodes.function.builtins.PythonTernaryBuiltinNode;
7363
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode;
7464
import com.oracle.graal.python.nodes.object.BuiltinClassProfiles.IsBuiltinObjectProfile;
75-
import com.oracle.graal.python.nodes.object.GetDictIfExistsNode;
7665
import com.oracle.graal.python.runtime.exception.PException;
77-
import com.oracle.graal.python.util.PythonUtils;
7866
import com.oracle.truffle.api.CompilerDirectives;
79-
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
8067
import com.oracle.truffle.api.dsl.Bind;
8168
import com.oracle.truffle.api.dsl.Cached;
8269
import com.oracle.truffle.api.dsl.GenerateNodeFactory;
8370
import com.oracle.truffle.api.dsl.NodeFactory;
8471
import com.oracle.truffle.api.dsl.Specialization;
8572
import com.oracle.truffle.api.frame.VirtualFrame;
8673
import com.oracle.truffle.api.nodes.Node;
74+
import com.oracle.truffle.api.object.HiddenKey;
8775
import com.oracle.truffle.api.profiles.InlinedConditionProfile;
8876

89-
@CoreFunctions(defineModule = "_functools")
77+
@CoreFunctions(defineModule = J_FUNCTOOLS)
9078
public final class FunctoolsModuleBuiltins extends PythonBuiltins {
9179
@Override
9280
protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFactories() {
9381
return FunctoolsModuleBuiltinsFactory.getFactories();
9482
}
9583

84+
protected static final HiddenKey KWD_MARK = new HiddenKey("kwd_mark");
85+
86+
@Override
87+
public void initialize(Python3Core core) {
88+
super.initialize(core);
89+
addBuiltinConstant(SpecialAttributeNames.T___DOC__,
90+
"Create a cached callable that wraps another function.\n" + //
91+
"\n" + //
92+
"user_function: the function being cached\n" + //
93+
"\n" + //
94+
"maxsize: 0 for no caching\n" + //
95+
" None for unlimited cache size\n" + //
96+
" n for a bounded cache\n" + //
97+
"\n" + //
98+
"typed: False cache f(3) and f(3.0) as identical calls\n" + //
99+
" True cache f(3) and f(3.0) as distinct calls\n" + //
100+
"\n" + //
101+
"cache_info_type: namedtuple class with the fields:\n" + //
102+
" hits misses currsize maxsize\n");
103+
addBuiltinConstant(KWD_MARK, core.factory().createPythonObject(PythonObject));
104+
}
105+
96106
// functools.reduce(function, iterable[, initializer])
97107
@Builtin(name = "reduce", minNumOfPositionalArgs = 2, maxNumOfPositionalArgs = 3, doc = "reduce(function, sequence[, initial]) -> value\n" +
98108
"\n" +
@@ -165,171 +175,4 @@ Object doConvert(Object myCmp) {
165175
}
166176
}
167177

168-
// functools.partial(func, /, *args, **keywords)
169-
@Builtin(name = J_PARTIAL, minNumOfPositionalArgs = 1, varArgsMarker = true, takesVarArgs = true, takesVarKeywordArgs = true, constructsClass = PythonBuiltinClassType.PPartial, doc = "partial(func, *args, **keywords) - new function with partial application\n" +
170-
"of the given arguments and keywords.\n")
171-
@GenerateNodeFactory
172-
public abstract static class PartialNode extends PythonBuiltinNode {
173-
protected boolean isPartialWithoutDict(GetDictIfExistsNode getDict, Object[] args, HashingStorageLen lenNode, boolean withKwDict) {
174-
return isPartialWithoutDict(getDict, args) && withKwDict == ((PPartial) args[0]).hasKw(lenNode);
175-
}
176-
177-
protected boolean isPartialWithoutDict(GetDictIfExistsNode getDict, Object[] args) {
178-
return getDict.execute(args[0]) == null && args[0] instanceof PPartial;
179-
}
180-
181-
protected boolean withKeywords(PKeyword[] keywords) {
182-
return keywords.length > 0;
183-
}
184-
185-
protected boolean atLeastOneArg(Object[] args) {
186-
return args.length >= 1;
187-
}
188-
189-
@Specialization(guards = {"atLeastOneArg(args)", "isPartialWithoutDict(getDict, args, lenNode, false)"}, limit = "1")
190-
@SuppressWarnings("truffle-static-method")
191-
Object createFromPartialWoDictWoKw(Object cls, Object[] args, PKeyword[] keywords,
192-
@Bind("this") Node inliningTarget,
193-
@SuppressWarnings("unused") @Cached GetDictIfExistsNode getDict,
194-
@Cached InlinedConditionProfile hasArgsProfile,
195-
@Cached InlinedConditionProfile hasKeywordsProfile,
196-
@SuppressWarnings("unused") @Cached HashingStorageLen lenNode) {
197-
assert args[0] instanceof PPartial;
198-
final PPartial function = (PPartial) args[0];
199-
Object[] funcArgs = getNewPartialArgs(function, args, inliningTarget, hasArgsProfile, 1);
200-
201-
PDict funcKwDict;
202-
if (hasKeywordsProfile.profile(inliningTarget, keywords.length > 0)) {
203-
funcKwDict = factory().createDict(keywords);
204-
} else {
205-
funcKwDict = factory().createDict();
206-
}
207-
208-
return factory().createPartial(cls, function.getFn(), funcArgs, funcKwDict);
209-
}
210-
211-
@Specialization(guards = {"atLeastOneArg(args)", "isPartialWithoutDict(getDict, args, lenNode, true)", "!withKeywords(keywords)"}, limit = "1")
212-
@SuppressWarnings("truffle-static-method")
213-
Object createFromPartialWoDictWKw(Object cls, Object[] args, @SuppressWarnings("unused") PKeyword[] keywords,
214-
@Bind("this") Node inliningTarget,
215-
@SuppressWarnings("unused") @Cached GetDictIfExistsNode getDict,
216-
@Cached InlinedConditionProfile hasArgsProfile,
217-
@SuppressWarnings("unused") @Cached HashingStorageLen lenNode,
218-
@Cached HashingStorageCopy copyNode) {
219-
assert args[0] instanceof PPartial;
220-
final PPartial function = (PPartial) args[0];
221-
Object[] funcArgs = getNewPartialArgs(function, args, inliningTarget, hasArgsProfile, 1);
222-
return factory().createPartial(cls, function.getFn(), funcArgs, function.getKwCopy(factory(), copyNode));
223-
}
224-
225-
@Specialization(guards = {"atLeastOneArg(args)", "isPartialWithoutDict(getDict, args, lenNode, true)", "withKeywords(keywords)"}, limit = "1")
226-
@SuppressWarnings("truffle-static-method")
227-
Object createFromPartialWoDictWKwKw(VirtualFrame frame, Object cls, Object[] args, PKeyword[] keywords,
228-
@Bind("this") Node inliningTarget,
229-
@SuppressWarnings("unused") @Cached GetDictIfExistsNode getDict,
230-
@Cached InlinedConditionProfile hasArgsProfile,
231-
@Cached HashingStorage.InitNode initNode,
232-
@SuppressWarnings("unused") @Cached HashingStorageLen lenNode,
233-
@Cached HashingStorageCopy copyHashingStorageNode,
234-
@Cached HashingStorageAddAllToOther addAllToOtherNode) {
235-
assert args[0] instanceof PPartial;
236-
final PPartial function = (PPartial) args[0];
237-
Object[] funcArgs = getNewPartialArgs(function, args, inliningTarget, hasArgsProfile, 1);
238-
239-
HashingStorage storage = copyHashingStorageNode.execute(function.getKw().getDictStorage());
240-
PDict result = factory().createDict(storage);
241-
addAllToOtherNode.execute(frame, initNode.execute(frame, PNone.NO_VALUE, keywords), result);
242-
243-
return factory().createPartial(cls, function.getFn(), funcArgs, result);
244-
}
245-
246-
@Specialization(guards = {"atLeastOneArg(args)", "!isPartialWithoutDict(getDict, args)"}, limit = "1")
247-
@SuppressWarnings("truffle-static-method")
248-
Object createGeneric(Object cls, Object[] args, PKeyword[] keywords,
249-
@Bind("this") Node inliningTarget,
250-
@SuppressWarnings("unused") @Cached GetDictIfExistsNode getDict,
251-
@Cached InlinedConditionProfile hasKeywordsProfile,
252-
@Cached PyCallableCheckNode callableCheckNode) {
253-
Object function = args[0];
254-
if (!callableCheckNode.execute(function)) {
255-
throw raise(PythonBuiltinClassType.TypeError, S_ARG_MUST_BE_CALLABLE, "the first");
256-
}
257-
258-
final Object[] funcArgs = PythonUtils.arrayCopyOfRange(args, 1, args.length);
259-
PDict funcKwDict;
260-
if (hasKeywordsProfile.profile(inliningTarget, keywords.length > 0)) {
261-
funcKwDict = factory().createDict(keywords);
262-
} else {
263-
funcKwDict = factory().createDict();
264-
}
265-
return factory().createPartial(cls, function, funcArgs, funcKwDict);
266-
}
267-
268-
@Specialization(guards = "!atLeastOneArg(args)")
269-
@SuppressWarnings("unused")
270-
Object noCallable(Object cls, Object[] args, PKeyword[] keywords) {
271-
throw raise(PythonBuiltinClassType.TypeError, TYPE_S_TAKES_AT_LEAST_ONE_ARGUMENT, "partial");
272-
}
273-
}
274-
}
275-
276-
@CoreFunctions(extendClasses = PythonBuiltinClassType.LsprofProfiler)
277-
class ProfilerBuiltins extends PythonBuiltins {
278-
@Override
279-
protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFactories() {
280-
return ProfilerBuiltinsFactory.getFactories();
281-
}
282-
283-
@Builtin(name = J___INIT__, minNumOfPositionalArgs = 1, parameterNames = {"$self", "timer", "timeunit", "subcalls", "builtins"})
284-
@GenerateNodeFactory
285-
abstract static class Init extends PythonBuiltinNode {
286-
@Specialization
287-
PNone doit(Profiler self, Object timer, double timeunit, long subcalls, long builtins) {
288-
self.subcalls = subcalls > 0;
289-
self.builtins = builtins > 0;
290-
self.timeunit = timeunit;
291-
self.externalTimer = timer;
292-
return PNone.NONE;
293-
}
294-
295-
@Specialization
296-
@SuppressWarnings("unused")
297-
PNone doit(Profiler self, Object timer, PNone timeunit, PNone subcalls, PNone builtins) {
298-
self.subcalls = true;
299-
self.builtins = true;
300-
self.timeunit = -1;
301-
self.externalTimer = timer;
302-
return PNone.NONE;
303-
}
304-
}
305-
306-
@Builtin(name = "enable", minNumOfPositionalArgs = 1, parameterNames = {"$self", "subcalls", "builtins"})
307-
@GenerateNodeFactory
308-
abstract static class Enable extends PythonBuiltinNode {
309-
@Specialization
310-
@TruffleBoundary
311-
PNone doit(Profiler self, long subcalls, long builtins) {
312-
self.subcalls = subcalls > 0;
313-
self.builtins = builtins > 0;
314-
// TODO: deal with any arguments
315-
self.time = System.currentTimeMillis();
316-
self.sampler.setCollecting(true);
317-
return PNone.NONE;
318-
}
319-
320-
@Specialization
321-
PNone doit(Profiler self, long subcalls, @SuppressWarnings("unused") PNone builtins) {
322-
return doit(self, subcalls, self.builtins ? 1 : 0);
323-
}
324-
325-
@Specialization
326-
PNone doit(Profiler self, @SuppressWarnings("unused") PNone subcalls, long builtins) {
327-
return doit(self, self.subcalls ? 1 : 0, builtins);
328-
}
329-
330-
@Specialization
331-
PNone doit(Profiler self, @SuppressWarnings("unused") PNone subcalls, @SuppressWarnings("unused") PNone builtins) {
332-
return doit(self, self.subcalls ? 1 : 0, self.builtins ? 1 : 0);
333-
}
334-
}
335178
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package com.oracle.graal.python.builtins.modules.functools;
42+
43+
import com.oracle.graal.python.builtins.objects.common.ObjectHashMap;
44+
import com.oracle.truffle.api.object.Shape;
45+
46+
public class LruCacheObject extends LruListElemObject {
47+
48+
enum WrapperType {
49+
INFINITE,
50+
UNCACHED,
51+
BOUNDED,
52+
}
53+
54+
WrapperType wrapper;
55+
int typed;
56+
final ObjectHashMap cache;
57+
int hits;
58+
Object func;
59+
int maxsize;
60+
int misses;
61+
/* the kwd_mark is used delimit args and keywords in the cache keys */
62+
Object kwdMark;
63+
// Object lru_list_elem_type; PyTypeObject * /* not needed */
64+
Object cacheInfoType; // cache_info_type
65+
// Object dict; /* mq: enable when needed */
66+
// Object weakreflist; /* mq: enable when needed */
67+
68+
public LruCacheObject(Object cls, Shape instanceShape) {
69+
super(cls, instanceShape);
70+
this.cache = new ObjectHashMap();
71+
}
72+
73+
public boolean isInfinite() {
74+
return wrapper == WrapperType.INFINITE;
75+
}
76+
77+
public boolean isUncached() {
78+
return wrapper == WrapperType.UNCACHED;
79+
}
80+
81+
public boolean isBounded() {
82+
return wrapper == WrapperType.BOUNDED;
83+
}
84+
}

0 commit comments

Comments
 (0)