Skip to content

Commit

Permalink
Make cache sharable between type descriptors
Browse files Browse the repository at this point in the history
  • Loading branch information
heshanpadmasiri committed Oct 6, 2024
1 parent 8f70ddf commit dca84d0
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 58 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.ballerina.runtime.internal.types;
package io.ballerina.runtime.api.types.semtype;

import io.ballerina.runtime.api.types.Type;

Expand All @@ -13,7 +13,7 @@ public interface CacheableTypeDescriptor extends Type {

boolean shouldCache();

Optional<Boolean> cachedTypeCheckResult(CacheableTypeDescriptor other);
Optional<Boolean> cachedTypeCheckResult(Context cx, CacheableTypeDescriptor other);

void cacheTypeCheckResult(CacheableTypeDescriptor other, boolean result);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import io.ballerina.runtime.internal.types.semtype.MappingAtomicType;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand All @@ -43,9 +44,22 @@ public final class Context {
public final Map<Bdd, BddMemo> listMemo = new WeakHashMap<>();
public final Map<Bdd, BddMemo> mappingMemo = new WeakHashMap<>();
public final Map<Bdd, BddMemo> functionMemo = new WeakHashMap<>();
private static final int MAX_CACHE_SIZE = 100;
private final Map<CacheableTypeDescriptor, TypeCheckCache<CacheableTypeDescriptor>> typeCheckCacheMemo;

private Context(Env env) {
this.env = env;
this.typeCheckCacheMemo = createTypeCheckCacheMemo();
}

private static Map<CacheableTypeDescriptor, TypeCheckCache<CacheableTypeDescriptor>> createTypeCheckCacheMemo() {
return new LinkedHashMap<>(MAX_CACHE_SIZE, 1f, true) {
@Override
protected boolean removeEldestEntry(
Map.Entry<CacheableTypeDescriptor, TypeCheckCache<CacheableTypeDescriptor>> eldest) {
return size() > MAX_CACHE_SIZE;
}
};
}

public static Context from(Env env) {
Expand Down Expand Up @@ -128,4 +142,8 @@ public FunctionAtomicType functionAtomicType(Atom atom) {
return (FunctionAtomicType) ((TypeAtom) atom).atomicType();
}
}

public TypeCheckCache<CacheableTypeDescriptor> getTypeCheckCache(CacheableTypeDescriptor typeDescriptor) {
return typeCheckCacheMemo.computeIfAbsent(typeDescriptor, TypeCheckCache::new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.ballerina.runtime.api.types.semtype;

import io.ballerina.runtime.api.types.Type;

import java.util.Map;
import java.util.Optional;
import java.util.WeakHashMap;

public class TypeCheckCache<T extends Type> {

private final Map<T, Boolean> cachedResults = new WeakHashMap<>();
private final T owner;

public TypeCheckCache(T owner) {
this.owner = owner;
}

public Optional<Boolean> cachedTypeCheckResult(T other) {
if (other.equals(owner)) {
return Optional.of(true);
}
return Optional.ofNullable(cachedResults.get(other));
}

public void cacheTypeCheckResult(T other, boolean result) {
cachedResults.put(other, result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import io.ballerina.runtime.api.types.Type;
import io.ballerina.runtime.api.types.XmlNodeType;
import io.ballerina.runtime.api.types.semtype.Builder;
import io.ballerina.runtime.api.types.semtype.CacheableTypeDescriptor;
import io.ballerina.runtime.api.types.semtype.Context;
import io.ballerina.runtime.api.types.semtype.Core;
import io.ballerina.runtime.api.types.semtype.Env;
Expand All @@ -53,7 +54,6 @@
import io.ballerina.runtime.internal.types.BType;
import io.ballerina.runtime.internal.types.BTypeReferenceType;
import io.ballerina.runtime.internal.types.BUnionType;
import io.ballerina.runtime.internal.types.CacheableTypeDescriptor;
import io.ballerina.runtime.internal.types.TypeWithShape;
import io.ballerina.runtime.internal.values.DecimalValue;
import io.ballerina.runtime.internal.values.HandleValue;
Expand Down Expand Up @@ -608,26 +608,26 @@ private static boolean isSubType(Type source, Type target) {
}
// This is really a workaround for Standard libraries that create record types that are not the "same". But
// with the same name and expect them to be same.
return isSubTypeInner(source, target);
return isSubTypeInner(context(), source, target);
}

private static boolean isSubTypeInner(Type source, Type target) {
Context cx = context();
private static boolean isSubTypeInner(Context cx, Type source, Type target) {
SemType sourceSemType = SemType.tryInto(source);
SemType targetSemType = SemType.tryInto(target);
return Core.isSubType(cx, sourceSemType, targetSemType);
}

private static boolean isSubTypeWithCache(CacheableTypeDescriptor source, CacheableTypeDescriptor target) {
Context cx = context();
if (!source.shouldCache() || !target.shouldCache()) {
return isSubTypeInner(source, target);
return isSubTypeInner(cx, source, target);
}
Optional<Boolean> cachedResult = source.cachedTypeCheckResult(target);
Optional<Boolean> cachedResult = source.cachedTypeCheckResult(cx, target);
if (cachedResult.isPresent()) {
assert cachedResult.get() == isSubTypeInner(source, target);
assert cachedResult.get() == isSubTypeInner(cx, source, target);
return cachedResult.get();
}
boolean result = isSubTypeInner(source, target);
boolean result = isSubTypeInner(cx, source, target);
source.cacheTypeCheckResult(target, result);
return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@

package io.ballerina.runtime.internal.scheduling;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
Expand All @@ -39,7 +39,7 @@ public class ItemGroup {
* Keep the list of items that should run on same thread.
* Using a stack to get advantage of the locality.
*/
Deque<SchedulerItem> items = new ArrayDeque<>();
Deque<SchedulerItem> items = new ConcurrentLinkedDeque<>();

/**
* Indicates this item is already in runnable list/executing or not.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,10 @@ private SemType valueShape(Context cx, ShapeSupplier shapeSupplier, BTable<?, ?>
}
return createSemTypeWithConstraint(constraintType);
}

@Override
public boolean shouldCache() {
// TODO: remove this once we have fixed equals
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,16 @@
import io.ballerina.runtime.api.creators.TypeCreator;
import io.ballerina.runtime.api.types.IntersectionType;
import io.ballerina.runtime.api.types.Type;
import io.ballerina.runtime.api.types.semtype.CacheableTypeDescriptor;
import io.ballerina.runtime.api.types.semtype.Context;
import io.ballerina.runtime.api.types.semtype.SemType;
import io.ballerina.runtime.api.types.semtype.TypeCheckCache;
import io.ballerina.runtime.api.utils.StringUtils;
import io.ballerina.runtime.internal.TypeChecker;
import io.ballerina.runtime.internal.types.semtype.MutableSemType;

import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.WeakHashMap;

/**
* {@code BType} represents a type in Ballerina.
Expand All @@ -54,7 +55,7 @@ public abstract non-sealed class BType extends SemType
private Type cachedImpliedType = null;
private volatile SemType cachedSemType = null;
private TypeCreator.TypeMemoKey lookupKey = null;
private volatile Map<CacheableTypeDescriptor, Boolean> cachedResults;
private volatile TypeCheckCache<CacheableTypeDescriptor> typeCheckCache;

protected BType(String typeName, Module pkg, Class<? extends Object> valueClass) {
this.typeName = typeName;
Expand Down Expand Up @@ -297,23 +298,21 @@ public boolean shouldCache() {
}

@Override
public final Optional<Boolean> cachedTypeCheckResult(CacheableTypeDescriptor other) {
if (other.equals(this)) {
return Optional.of(true);
}
if (cachedResults == null) {
public final Optional<Boolean> cachedTypeCheckResult(Context cx, CacheableTypeDescriptor other) {
if (typeCheckCache == null) {
synchronized (this) {
if (cachedResults == null) {
cachedResults = new WeakHashMap<>();
if (typeCheckCache == null) {
typeCheckCache = cx.getTypeCheckCache(this);
}
}
return Optional.empty();
}
return Optional.ofNullable(cachedResults.get(other));
return typeCheckCache.cachedTypeCheckResult(other);
}

@Override
public final void cacheTypeCheckResult(CacheableTypeDescriptor other, boolean result) {
cachedResults.put(other, result);
// This happening after checking the cache so it must be initialized by now
typeCheckCache.cacheTypeCheckResult(other, result);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ public void testNullJsonToBoolean() {

@Test(description = "Test casting nil to a record",
expectedExceptions = {BLangTestException.class},
expectedExceptionsMessageRegExp = ".*incompatible types: '\\(\\)' cannot be cast to 'Student'.*")
expectedExceptionsMessageRegExp = ".*incompatible types: '\\(\\)' cannot be cast to 'StudentTC'.*")
public void testNullStructToStruct() {
BRunUtil.invoke(result, "testNullStructToStruct");
}
Expand Down Expand Up @@ -317,7 +317,7 @@ public void testAnyMapToJson() {

@Test(description = "Test casting a struct as any type to json",
expectedExceptions = {BLangTestException.class},
expectedExceptionsMessageRegExp = ".*incompatible types: 'Address' cannot be cast to 'json'.*")
expectedExceptionsMessageRegExp = ".*incompatible types: 'AddressTC' cannot be cast to 'json'.*")
public void testAnyStructToJson() {
BRunUtil.invoke(result, "testAnyStructToJson");
}
Expand Down Expand Up @@ -368,14 +368,14 @@ public void testStructAsAnyToStruct() {

@Test(description = "Test casting any to struct",
expectedExceptions = {BLangTestException.class},
expectedExceptionsMessageRegExp = ".*incompatible types: 'map' cannot be cast to 'Person'.*")
expectedExceptionsMessageRegExp = ".*incompatible types: 'map' cannot be cast to 'PersonTC'.*")
public void testAnyToStruct() {
BRunUtil.invoke(result, "testAnyToStruct");
}

@Test(description = "Test casting a null stored as any to struct",
expectedExceptions = {BLangTestException.class},
expectedExceptionsMessageRegExp = ".*incompatible types: '\\(\\)' cannot be cast to 'Person'.*")
expectedExceptionsMessageRegExp = ".*incompatible types: '\\(\\)' cannot be cast to 'PersonTC'.*")
public void testAnyNullToStruct() {
Object returns = BRunUtil.invoke(result, "testAnyNullToStruct");
Assert.assertNull(returns);
Expand Down
Loading

0 comments on commit dca84d0

Please sign in to comment.