Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Implement multi-dispatch cache.
While selftest is dominated by startup time, this does shave around 8%
off the time to run it.
  • Loading branch information
jnthn committed Apr 14, 2013
1 parent f073947 commit 1dbb570
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 4 deletions.
5 changes: 5 additions & 0 deletions src/org/perl6/nqp/runtime/GlobalContext.java
Expand Up @@ -85,6 +85,11 @@ public class GlobalContext {
public SixModelObject BOOTNumArray;
public SixModelObject BOOTStrArray;

/**
* Multi-dispatch cache type.
*/
public SixModelObject MultiCache;

/**
* The main, startup thread's ThreadContext.
*/
Expand Down
13 changes: 9 additions & 4 deletions src/org/perl6/nqp/runtime/Ops.java
Expand Up @@ -38,6 +38,7 @@
import org.perl6.nqp.sixmodel.reprs.ContextRef;
import org.perl6.nqp.sixmodel.reprs.ContextRefInstance;
import org.perl6.nqp.sixmodel.reprs.IOHandleInstance;
import org.perl6.nqp.sixmodel.reprs.MultiCacheInstance;
import org.perl6.nqp.sixmodel.reprs.NFA;
import org.perl6.nqp.sixmodel.reprs.NFAInstance;
import org.perl6.nqp.sixmodel.reprs.NFAStateInfo;
Expand Down Expand Up @@ -1297,12 +1298,16 @@ public static SixModelObject lexotic(long target) {

/* Multi-dispatch cache. */
public static SixModelObject multicacheadd(SixModelObject cache, SixModelObject capture, SixModelObject result, ThreadContext tc) {
// TODO
return null;
if (!(cache instanceof MultiCacheInstance))
cache = tc.gc.MultiCache.st.REPR.allocate(tc, tc.gc.MultiCache.st);
((MultiCacheInstance)cache).add((CallCaptureInstance)capture, result);
return cache;
}
public static SixModelObject multicachefind(SixModelObject cache, SixModelObject capture, ThreadContext tc) {
// TODO
return null;
if (cache instanceof MultiCacheInstance)
return ((MultiCacheInstance)cache).lookup((CallCaptureInstance)capture);
else
return null;
}

/* Basic 6model operations. */
Expand Down
2 changes: 2 additions & 0 deletions src/org/perl6/nqp/sixmodel/KnowHOWBootstrapper.java
Expand Up @@ -30,6 +30,8 @@ public static void bootstrap(ThreadContext tc)
tc.gc.BOOTNumArray = bootTypedArray(tc, "BOOTNumArray", tc.gc.BOOTNum);
tc.gc.BOOTStrArray = bootTypedArray(tc, "BOOTStrArray", tc.gc.BOOTStr);

tc.gc.MultiCache = bootType(tc, "MultiCache", "MultiCache");

Ops.setboolspec(tc.gc.BOOTIter, BoolificationSpec.MODE_ITER, null, tc);
Ops.setboolspec(tc.gc.BOOTInt, BoolificationSpec.MODE_UNBOX_INT, null, tc);
Ops.setboolspec(tc.gc.BOOTNum, BoolificationSpec.MODE_UNBOX_NUM, null, tc);
Expand Down
2 changes: 2 additions & 0 deletions src/org/perl6/nqp/sixmodel/REPRRegistry.java
Expand Up @@ -9,6 +9,7 @@
import org.perl6.nqp.sixmodel.reprs.IOHandle;
import org.perl6.nqp.sixmodel.reprs.KnowHOWAttribute;
import org.perl6.nqp.sixmodel.reprs.KnowHOWREPR;
import org.perl6.nqp.sixmodel.reprs.MultiCache;
import org.perl6.nqp.sixmodel.reprs.NFA;
import org.perl6.nqp.sixmodel.reprs.P6Opaque;
import org.perl6.nqp.sixmodel.reprs.P6bigint;
Expand Down Expand Up @@ -66,5 +67,6 @@ public static void setup() {
addREPR("VMException", new VMException());
addREPR("IOHandle", new IOHandle());
addREPR("P6bigint", new P6bigint());
addREPR("MultiCache", new MultiCache());
}
}
33 changes: 33 additions & 0 deletions src/org/perl6/nqp/sixmodel/reprs/MultiCache.java
@@ -0,0 +1,33 @@
package org.perl6.nqp.sixmodel.reprs;

import org.perl6.nqp.runtime.ThreadContext;
import org.perl6.nqp.sixmodel.REPR;
import org.perl6.nqp.sixmodel.STable;
import org.perl6.nqp.sixmodel.SerializationReader;
import org.perl6.nqp.sixmodel.SixModelObject;
import org.perl6.nqp.sixmodel.TypeObject;

public class MultiCache extends REPR {
public SixModelObject type_object_for(ThreadContext tc, SixModelObject HOW) {
STable st = new STable(this, HOW);
SixModelObject obj = new TypeObject();
obj.st = st;
st.WHAT = obj;
return st.WHAT;
}

public SixModelObject allocate(ThreadContext tc, STable st) {
MultiCacheInstance obj = new MultiCacheInstance();
obj.st = st;
return obj;
}

public SixModelObject deserialize_stub(ThreadContext tc, STable st) {
throw new RuntimeException("MultiCache does not participate in serialization");
}

public void deserialize_finish(ThreadContext tc, STable st,
SerializationReader reader, SixModelObject obj) {
throw new RuntimeException("MultiCache does not participate in serialization");
}
}
179 changes: 179 additions & 0 deletions src/org/perl6/nqp/sixmodel/reprs/MultiCacheInstance.java
@@ -0,0 +1,179 @@
package org.perl6.nqp.sixmodel.reprs;

import org.perl6.nqp.runtime.CallSiteDescriptor;
import org.perl6.nqp.sixmodel.SixModelObject;
import org.perl6.nqp.sixmodel.TypeObject;

public class MultiCacheInstance extends SixModelObject {
private static final int MD_CACHE_MAX_ARITY = 4;
private static final int MD_CACHE_MAX_ENTRIES = 16;
private static final int MD_CACHE_INT = 1;
private static final int MD_CACHE_NUM = 2;
private static final int MD_CACHE_STR = 3;

private SixModelObject zeroArity;
private ArityCache[] arityCaches = new ArityCache[MD_CACHE_MAX_ARITY];

private class ArityCache
{
/* The number of entries we have in the cache. */
public int numEntries;

/* This is a bunch of ST hashes, with natives special-cased. We allocate
* it arity * MAX_ENTRIES big and go through it in arity sized chunks. */
public long typeIds[];

/* Whether the entry is allowed to have named arguments. Doesn't say
* anything about which ones, though. Something that is ambivalent
* about named arguments to the degree it doesn't care about them
* even tie-breaking (like NQP) can just throw such entries into the
* cache. Things that do care should not make such cache entries. */
public boolean namedOK[];

/* The results we return from the cache. */
public SixModelObject[] results;
}

public void add(CallCaptureInstance capture, SixModelObject result) {
/* If there's flattenings, we can't cache. */
if (capture.descriptor.hasFlattening)
return;

/* If it's zero arity, just stick it in that slot. */
Object[] args = capture.args;
if (args.length == 0) {
this.zeroArity = result;
return;
}

/* Count number of positional args and build type tuple. */
int numArgs = 0;
byte[] argFlags = capture.descriptor.argFlags;
long argTup[] = new long[MD_CACHE_MAX_ARITY];
boolean hasNamed = false;
for (int i = 0; i < argFlags.length; i++) {
switch (argFlags[i]) {
case CallSiteDescriptor.ARG_INT:
if (numArgs >= MD_CACHE_MAX_ARITY)
return;
argTup[numArgs++] = MD_CACHE_INT;
break;
case CallSiteDescriptor.ARG_NUM:
if (numArgs >= MD_CACHE_MAX_ARITY)
return;
argTup[numArgs++] = MD_CACHE_NUM;
break;
case CallSiteDescriptor.ARG_STR:
if (numArgs >= MD_CACHE_MAX_ARITY)
return;
argTup[numArgs++] = MD_CACHE_STR;
break;
case CallSiteDescriptor.ARG_OBJ:
if (numArgs >= MD_CACHE_MAX_ARITY)
return;
long flag = ((long)((SixModelObject)args[i]).st.hashCode()) << 1;
if (!(args[i] instanceof TypeObject))
flag |= 1;
argTup[numArgs++] = flag;
break;
default:
if ((argFlags[i] & CallSiteDescriptor.ARG_FLAT) != 0)
return;
hasNamed = true;
}
}

/* If the cache is saturated, don't do anything (we could instead do a random
* replacement). */
ArityCache ac = this.arityCaches[numArgs - 1];
if (ac != null && ac.numEntries == MD_CACHE_MAX_ENTRIES)
return;

/* If there's no entries yet, need to do some allocation. */
if (ac == null) {
ac = new ArityCache();
ac.typeIds = new long[numArgs * MD_CACHE_MAX_ENTRIES];
ac.namedOK = new boolean[MD_CACHE_MAX_ENTRIES];
ac.results = new SixModelObject[MD_CACHE_MAX_ENTRIES];
this.arityCaches[numArgs - 1] = ac;
}

/* Add entry. */
int insType = ac.numEntries * numArgs;
for (int i = 0; i < numArgs; i++)
ac.typeIds[insType + i] = argTup[i];
ac.results[ac.numEntries] = result;
ac.namedOK[ac.numEntries] = hasNamed;
ac.numEntries++;
}

public SixModelObject lookup(CallCaptureInstance capture) {
/* If there's flattenings, we can't use the cache. */
if (capture.descriptor.hasFlattening)
return null;

/* Count number of positional args and build type tuple. */
int numArgs = 0;
Object[] args = capture.args;
byte[] argFlags = capture.descriptor.argFlags;
long argTup[] = new long[MD_CACHE_MAX_ARITY];
boolean hasNamed = false;
for (int i = 0; i < argFlags.length; i++) {
switch (argFlags[i]) {
case CallSiteDescriptor.ARG_INT:
if (numArgs >= MD_CACHE_MAX_ARITY)
return null;
argTup[numArgs++] = MD_CACHE_INT;
break;
case CallSiteDescriptor.ARG_NUM:
if (numArgs >= MD_CACHE_MAX_ARITY)
return null;
argTup[numArgs++] = MD_CACHE_NUM;
break;
case CallSiteDescriptor.ARG_STR:
if (numArgs >= MD_CACHE_MAX_ARITY)
return null;
argTup[numArgs++] = MD_CACHE_STR;
break;
case CallSiteDescriptor.ARG_OBJ:
if (numArgs >= MD_CACHE_MAX_ARITY)
return null;
long flag = ((long)((SixModelObject)args[i]).st.hashCode()) << 1;
if (!(args[i] instanceof TypeObject))
flag |= 1;
argTup[numArgs++] = flag;
break;
default:
if ((argFlags[i] & CallSiteDescriptor.ARG_FLAT) != 0)
return null;
hasNamed = true;
}
}

/* If it's zero-arity, return result right off. */
if (numArgs == 0)
return hasNamed ? null : this.zeroArity;

/* Look through entries. */
ArityCache ac = this.arityCaches[numArgs - 1];
if (ac == null)
return null;
int tPos = 0;
for (int i = 0; i < ac.numEntries; i++) {
boolean match = true;
for (int j = 0; j < numArgs; j++) {
if (ac.typeIds[tPos + j] != argTup[j]) {
match = false;
break;
}
}
if (match) {
if (hasNamed == ac.namedOK[i])
return ac.results[i];
}
tPos += numArgs;
}

return null;
}
}

0 comments on commit 1dbb570

Please sign in to comment.