diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Builder.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Builder.java index 4c4f1149387b..38278a30fdfe 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Builder.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Builder.java @@ -52,6 +52,7 @@ import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_TYPEDESC; import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.BT_XML; import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.CODE_UNDEF; +import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.VT_INHERENTLY_IMMUTABLE; import static io.ballerina.runtime.api.types.semtype.BasicTypeCode.VT_MASK; import static io.ballerina.runtime.api.types.semtype.BddNode.bddAtom; import static io.ballerina.runtime.api.types.semtype.Core.union; @@ -68,6 +69,7 @@ public final class Builder { private static final String[] EMPTY_STRING_ARR = new String[0]; private static final SemType VAL = SemType.from(VT_MASK); private static final SemType OBJECT = from(BT_OBJECT); + private static final SemType INHERENTLY_IMMUTABLE = SemType.from(VT_INHERENTLY_IMMUTABLE); private static final SemType INNER = getBasicTypeUnion(VAL.all() | from(BasicTypeCode.BT_UNDEF).all()); private static final SemType ANY = getBasicTypeUnion(BasicTypeCode.VT_MASK & ~(1 << BasicTypeCode.BT_ERROR.code())); @@ -357,6 +359,10 @@ public static SemType getObjectType() { return OBJECT; } + public static SemType getInherentlyImmutable() { + return INHERENTLY_IMMUTABLE; + } + static SemType mappingRO() { return MAPPING_RO.get(); } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Context.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Context.java index 4d6634f89f76..2cff96f94c5c 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Context.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Context.java @@ -22,6 +22,7 @@ import io.ballerina.runtime.internal.types.semtype.FunctionAtomicType; import io.ballerina.runtime.internal.types.semtype.ListAtomicType; import io.ballerina.runtime.internal.types.semtype.MappingAtomicType; +import io.ballerina.runtime.internal.types.semtype.MutableSemType; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -48,10 +49,17 @@ public final class Context { public final Map functionMemo = new WeakHashMap<>(); private static final int MAX_CACHE_SIZE = 100; private final Map> typeCheckCacheMemo; + private Phase phase = Phase.INIT; + List typeResolutionPhases = new ArrayList<>(); + List typeCheckPhases = new ArrayList<>(); + private final boolean collectDiagnostic; + private int typeCheckDepth = 0; + private int typeResolutionDepth = 0; private Context(Env env) { this.env = env; this.typeCheckCacheMemo = createTypeCheckCacheMemo(); + this.collectDiagnostic = "true".equalsIgnoreCase(System.getenv("BAL_TYPE_CHECK_DIAGNOSTIC_ENABLE")); } private static Map> createTypeCheckCacheMemo() { @@ -90,6 +98,111 @@ public boolean memoSubtypeIsEmpty(Map memoTable, BddIsEmptyPredica return m.isEmpty().orElseGet(() -> memoSubTypeIsEmptyInner(isEmptyPredicate, bdd, m)); } + public void enterTypeResolutionPhase(MutableSemType type) throws InterruptedException { + switch (phase) { + case INIT -> { + typeResolutionDepth++; + env.enterTypeResolutionPhase(this, type); + phase = Phase.TYPE_RESOLUTION; + if (collectDiagnostic) { + typeResolutionPhases.add(new PhaseData()); + } + } + case TYPE_RESOLUTION -> { + typeResolutionDepth++; + } + case TYPE_CHECKING -> { + StringBuilder sb = new StringBuilder(); + sb.append("Cannot enter type resolution phase while in type checking phase\n"); + if (collectDiagnostic) { + appendPhaseDataToError(sb); + } + throw new IllegalStateException(sb.toString()); + } + } + } + + public void exitTypeResolutionPhaseAbruptly(Exception ex) { + env.exitTypeResolutionPhaseAbruptly(this, ex); + } + + public void enterTypeCheckingPhase(SemType t1, SemType t2) { + typeCheckDepth++; + switch (phase) { + case INIT -> { + env.enterTypeCheckingPhase(this, t1, t2); + if (collectDiagnostic) { + typeCheckPhases.add(new PhaseData()); + } + phase = Phase.TYPE_CHECKING; + } + case TYPE_RESOLUTION -> { + StringBuilder sb = new StringBuilder(); + sb.append("Cannot enter type checking phase while in type resolution phase\n"); + if (collectDiagnostic) { + appendPhaseDataToError(sb); + } + throw new IllegalStateException(sb.toString()); + } + case TYPE_CHECKING -> { + } + } + } + + public void exitTypeResolutionPhase() { + if (phase == Phase.TYPE_RESOLUTION) { + typeResolutionDepth--; + if (typeResolutionDepth == 0) { + env.exitTypeResolutionPhase(this); + phase = Phase.INIT; + if (collectDiagnostic) { + typeResolutionPhases.removeLast(); + } + } + } else { + throw new IllegalStateException("Cannot exit type resolution phase without entering it"); + } + } + + public void exitTypeCheckingPhase() { + switch (phase) { + case INIT -> { + StringBuilder sb = new StringBuilder(); + sb.append("Cannot exit type checking phase without entering it"); + if (collectDiagnostic) { + appendPhaseDataToError(sb); + } + throw new IllegalStateException(sb.toString()); + } + case TYPE_RESOLUTION -> { + StringBuilder sb = new StringBuilder(); + sb.append("Cannot exit type checking phase while in type resolution phase\n"); + if (collectDiagnostic) { + appendPhaseDataToError(sb); + } + throw new IllegalStateException(sb.toString()); + } + case TYPE_CHECKING -> { + env.exitTypeCheckingPhase(this); + typeCheckDepth--; + if (typeCheckDepth == 0) { + phase = Phase.INIT; + } + } + } + } + + private void appendPhaseDataToError(StringBuilder sb) { + sb.append("Type resolution phases:\n"); + for (PhaseData phaseData : typeResolutionPhases) { + sb.append(phaseData).append("\n"); + } + sb.append("Type checking phases:\n"); + for (PhaseData phaseData : typeCheckPhases) { + sb.append(phaseData).append("\n"); + } + } + private boolean memoSubTypeIsEmptyInner(BddIsEmptyPredicate isEmptyPredicate, Bdd bdd, BddMemo m) { // We are staring the type check with the assumption our type is empty (see: inductive type) m.isEmpty = BddMemo.Status.PROVISIONAL; @@ -131,6 +244,7 @@ private void resetMemoizedValues(int initStackDepth, boolean isEmpty, boolean is public ListAtomicType listAtomType(Atom atom) { if (atom instanceof RecAtom recAtom) { + assert this.env.getRecListAtomType(recAtom) != null; return this.env.getRecListAtomType(recAtom); } else { return (ListAtomicType) ((TypeAtom) atom).atomicType(); @@ -139,6 +253,7 @@ public ListAtomicType listAtomType(Atom atom) { public MappingAtomicType mappingAtomType(Atom atom) { if (atom instanceof RecAtom recAtom) { + assert this.env.getRecMappingAtomType(recAtom) != null; return this.env.getRecMappingAtomType(recAtom); } else { return (MappingAtomicType) ((TypeAtom) atom).atomicType(); @@ -147,6 +262,7 @@ public MappingAtomicType mappingAtomType(Atom atom) { public FunctionAtomicType functionAtomicType(Atom atom) { if (atom instanceof RecAtom recAtom) { + assert this.env.getRecFunctionAtomType(recAtom) != null; return this.env.getRecFunctionAtomType(recAtom); } else { return (FunctionAtomicType) ((TypeAtom) atom).atomicType(); @@ -156,4 +272,28 @@ public FunctionAtomicType functionAtomicType(Atom atom) { public TypeCheckCache getTypeCheckCache(CacheableTypeDescriptor typeDescriptor) { return typeCheckCacheMemo.computeIfAbsent(typeDescriptor, TypeCheckCache::new); } + + public void registerAbruptTypeCheckEnd(Exception ex) { + env.registerAbruptTypeCheckEnd(this, ex); + } + + enum Phase { + INIT, TYPE_RESOLUTION, TYPE_CHECKING + } + + record PhaseData(StackTraceElement[] stackTrace) { + + PhaseData() { + this(Thread.currentThread().getStackTrace()); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + for (StackTraceElement element : stackTrace) { + builder.append("\tat ").append(element).append("\n"); + } + return builder.toString(); + } + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Core.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Core.java index 3eefea1b58e4..370d01c3f66c 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Core.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Core.java @@ -295,7 +295,15 @@ public static boolean isNever(SemType t) { } public static boolean isSubType(Context cx, SemType t1, SemType t2) { - return isEmpty(cx, diff(t1, t2)); + try { + cx.enterTypeCheckingPhase(t1, t2); + boolean res = isEmpty(cx, diff(t1, t2)); + cx.exitTypeCheckingPhase(); + return res; + } catch (Exception e) { + cx.registerAbruptTypeCheckEnd(e); + throw e; + } } public static boolean isSubtypeSimple(SemType t1, SemType t2) { diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/DebugSelfDiagnosticRunner.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/DebugSelfDiagnosticRunner.java new file mode 100644 index 000000000000..35c567203be2 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/DebugSelfDiagnosticRunner.java @@ -0,0 +1,268 @@ +package io.ballerina.runtime.api.types.semtype; + +import io.ballerina.runtime.internal.types.semtype.FunctionAtomicType; +import io.ballerina.runtime.internal.types.semtype.ListAtomicType; +import io.ballerina.runtime.internal.types.semtype.MappingAtomicType; +import io.ballerina.runtime.internal.types.semtype.MutableSemType; + +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; + +public class DebugSelfDiagnosticRunner implements TypeCheckSelfDiagnosticsRunner { + + private static final int MAX_TIMEOUT = 1000; + private static final String LOG_FILE_PATH = "/tmp/type_check_diagnostics.log"; + private final Queue pendingTypeResolutions = new ArrayDeque<>(); + private final Queue pendingTypeChecks = new ArrayDeque<>(); + private final Map uncaughtExceptions = new ConcurrentHashMap<>(); + private final Env env; + + DebugSelfDiagnosticRunner(Env env) { + try { + Files.write(Paths.get(LOG_FILE_PATH), "Type Check Diagnostics \n\n".getBytes(), StandardOpenOption.APPEND); + } catch (IOException ignored) { + } + this.env = env; + Thread houseKeeperThread = new Thread(new HouseKeeper()); + houseKeeperThread.setDaemon(true); + houseKeeperThread.start(); + } + + @Override + public void registerTypeResolutionStart(Context cx, MutableSemType type) { + synchronized (pendingTypeResolutions) { + pendingTypeResolutions.add(new TypeResolutionData(cx, type)); + } + Thread t = Thread.currentThread(); + t.setUncaughtExceptionHandler((thread, throwable) -> uncaughtExceptions.put(thread, throwable.getMessage())); + } + + private void validateRecAtomState() { + + List recMappingAtomsCopy = env.getRecMappingAtomsCopy(); + List recFunctionAtomsCopy = env.getRecFunctionAtomsCopy(); + List recListAtomsCopy = env.getRecListAtomsCopy(); + Thread validateThread = new Thread(() -> { + StringBuilder logBuilder = new StringBuilder(); + for (int i = 0; i < recMappingAtomsCopy.size(); i++) { + if (recMappingAtomsCopy.get(i) == null) { + logBuilder.append("Rec mapping atoms contain null values at ").append(i).append("\n"); + } + } + for (int i = 0; i < recFunctionAtomsCopy.size(); i++) { + if (recFunctionAtomsCopy.get(i) == null) { + logBuilder.append("Rec function atoms contain null values at ").append(i).append("\n"); + } + } + for (int i = 0; i < recListAtomsCopy.size(); i++) { + if (recListAtomsCopy.get(i) == null) { + logBuilder.append("Rec list atoms contain null values at ").append(i).append("\n"); + } + } + try { + Files.write(Paths.get(LOG_FILE_PATH), logBuilder.toString().getBytes(), StandardOpenOption.CREATE, + StandardOpenOption.APPEND); + } catch (IOException ignored) { + } + }); + validateThread.setDaemon(true); + validateThread.start(); + } + + @Override + public void registerTypeCheckStart(Context cx, SemType t1, SemType t2) { + synchronized (pendingTypeResolutions) { + pendingTypeResolutions.removeIf(data -> data.cx == cx); + } + synchronized (pendingTypeChecks) { + pendingTypeChecks.add(new TypeCheckData(cx, t1, t2)); + } + validateRecAtomState(); + Thread t = Thread.currentThread(); + t.setUncaughtExceptionHandler((thread, throwable) -> uncaughtExceptions.put(thread, throwable.getMessage())); + } + + @Override + public void registerTypeCheckEnd(Context cx) { + synchronized (pendingTypeChecks) { + pendingTypeChecks.removeIf(data -> data.cx == cx); + } + } + + @Override + public void registerAbruptTypeResolutionEnd(Context cx, Exception ex) { + StringBuilder logBuilder = new StringBuilder(); + logBuilder.append("Abrupt type resolution end:\n"); + logBuilder.append("Context: ").append(cx).append("\n"); + logBuilder.append("Exception: ").append(ex.getMessage()).append("\n"); + for (StackTraceElement element : ex.getStackTrace()) { + logBuilder.append("\tat ").append(element).append("\n"); + } + synchronized (pendingTypeChecks) { + logBuilder.append(pendingTypeChecksToString(pendingTypeChecks)); + } + synchronized (pendingTypeResolutions) { + logBuilder.append(pendingTypeResolutionsToString(pendingTypeResolutions)); + } + logBuilder.append("\n\n"); + try { + Files.write(Paths.get(LOG_FILE_PATH), logBuilder.toString().getBytes(), StandardOpenOption.CREATE, + StandardOpenOption.APPEND); + } catch (IOException ignored) { + } + } + + @Override + public void registerAbruptTypeCheckEnd(Context cx, Exception ex) { + StringBuilder logBuilder = new StringBuilder(); + logBuilder.append("Abrupt type check end:\n"); + logBuilder.append("Context: ").append(cx).append("\n"); + logBuilder.append("Exception: ").append(ex.getMessage()).append("\n"); + for (StackTraceElement element : ex.getStackTrace()) { + logBuilder.append("\tat ").append(element).append("\n"); + } + synchronized (pendingTypeChecks) { + logBuilder.append(pendingTypeChecksToString(pendingTypeChecks)); + } + synchronized (pendingTypeResolutions) { + logBuilder.append(pendingTypeResolutionsToString(pendingTypeResolutions)); + } + + logBuilder.append("\n\n"); + try { + Files.write(Paths.get(LOG_FILE_PATH), logBuilder.toString().getBytes(), StandardOpenOption.CREATE, + StandardOpenOption.APPEND); + } catch (IOException ignored) { + } + } + + @Override + public void registerTypeResolutionExit(Context cx) { + synchronized (pendingTypeResolutions) { + pendingTypeResolutions.removeIf(data -> data.cx == cx); + } + } + + private static String withIdentity(Object o) { + return o + "[" + System.identityHashCode(o) + "]"; + } + + private static String pendingTypeChecksToString(Collection typeChecks) { + StringBuilder logBuilder = new StringBuilder(); + logBuilder.append("Pending type checks:\n"); + typeChecks.forEach(data -> { + logBuilder.append(data.cx).append(" - ").append(withIdentity(data.t1)).append(" - ") + .append(withIdentity(data.t2)).append("\n"); + logBuilder.append("Thread state: ").append(data.t.getState()).append("\n"); + for (StackTraceElement element : data.t.getStackTrace()) { + logBuilder.append("\tat ").append(element).append("\n"); + } + logBuilder.append("Entry point\n"); + var typeCheckPhases = new ArrayList<>(data.cx.typeCheckPhases); + typeCheckPhases.forEach(each -> logBuilder.append(each.toString()).append("\n")); + }); + return logBuilder.toString(); + } + + private static String pendingTypeResolutionsToString(Collection typeResolutions) { + StringBuilder logBuilder = new StringBuilder(); + logBuilder.append("Pending type resolutions:\n"); + typeResolutions.forEach(data -> { + logBuilder.append(data.cx).append(" - ").append(withIdentity(data.type)).append("\n"); + logBuilder.append("Thread state: ").append(data.t.getState()).append("\n"); + for (StackTraceElement element : data.t.getStackTrace()) { + logBuilder.append("\tat ").append(element).append("\n"); + } + logBuilder.append("Entry point\n"); + List typeResolutionPhases = new ArrayList<>(data.cx.typeResolutionPhases); + typeResolutionPhases.forEach(each -> logBuilder.append(each.toString()).append("\n")); + }); + return logBuilder.toString(); + } + + private record TypeResolutionData(Context cx, Thread t, MutableSemType type, long startTime) { + + TypeResolutionData(Context cx, MutableSemType type) { + this(cx, Thread.currentThread(), type, System.nanoTime()); + } + } + + private record TypeCheckData(Context cx, Thread t, SemType t1, SemType t2, long startTime) { + + TypeCheckData(Context cx, SemType t1, SemType t2) { + this(cx, Thread.currentThread(), t1, t2, System.nanoTime()); + } + } + + private class HouseKeeper implements Runnable { + + @Override + public void run() { + while (true) { + List hangedTypeResolutions; + List hangedTypeChecks; + synchronized (pendingTypeResolutions) { + hangedTypeResolutions = pendingTypeResolutions.stream().filter(data -> { + long elapsedTime = System.nanoTime() - data.startTime(); + return elapsedTime > MAX_TIMEOUT; + }).toList(); + } + synchronized (pendingTypeChecks) { + hangedTypeChecks = pendingTypeChecks.stream().filter(data -> { + long elapsedTime = System.nanoTime() - data.startTime(); + return elapsedTime > MAX_TIMEOUT; + }).toList(); + } + StringBuilder logBuilder = new StringBuilder(); + if (!hangedTypeResolutions.isEmpty()) { + logBuilder.append(pendingTypeResolutionsToString(hangedTypeResolutions)); + } + if (!hangedTypeChecks.isEmpty()) { + logBuilder.append(pendingTypeChecksToString(hangedTypeChecks)); + } + uncaughtExceptions.forEach((thread, message) -> { + logBuilder.append("Uncaught exception in thread: ").append(thread).append("\n"); + logBuilder.append("Exception message: ").append(message).append("\n"); + for (StackTraceElement element : thread.getStackTrace()) { + logBuilder.append("\tat ").append(element).append("\n"); + } + }); + uncaughtExceptions.clear(); + ThreadMXBean tmx = ManagementFactory.getThreadMXBean(); + long[] ids = tmx.findDeadlockedThreads(); + if (ids != null) { + ThreadInfo[] infos = tmx.getThreadInfo(ids, true, true); + logBuilder.append("The following threads are deadlocked:"); + for (ThreadInfo ti : infos) { + logBuilder.append(ti); + } + } + + logBuilder.append("\n\n"); + try { + Files.write(Paths.get(LOG_FILE_PATH), logBuilder.toString().getBytes(), + StandardOpenOption.CREATE, StandardOpenOption.APPEND); + } catch (IOException ignored) { + } + try { + Thread.sleep(5000); // Sleep for 5 seconds + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + } + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Definition.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Definition.java index 0b0017f05841..9edf4527b222 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Definition.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Definition.java @@ -36,19 +36,4 @@ public abstract class Definition { */ public abstract SemType getSemType(Env env); - /** - * Register the container as the holder of this definition. Used to maintain concurrency invariants. - * - * @param container holder of the definition - * @see io.ballerina.runtime.internal.types.semtype.DefinitionContainer - */ - public void registerContainer(DefinitionContainer container) { - this.container = container; - } - - protected void notifyContainer() { - if (container != null) { - container.definitionUpdated(); - } - } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Env.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Env.java index 10b00639a035..eb2db9bb46d4 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Env.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/Env.java @@ -22,16 +22,18 @@ import io.ballerina.runtime.internal.types.semtype.FunctionAtomicType; import io.ballerina.runtime.internal.types.semtype.ListAtomicType; import io.ballerina.runtime.internal.types.semtype.MappingAtomicType; +import io.ballerina.runtime.internal.types.semtype.MutableSemType; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.WeakHashMap; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Supplier; @@ -65,9 +67,13 @@ public final class Env { private final ReadWriteLock recFunctionLock = new ReentrantReadWriteLock(); private final List recFunctionAtoms; - private final Map cellTypeCache = new ConcurrentHashMap<>(); + private final ReadWriteLock cellTypeCacheLock = new ReentrantReadWriteLock(); + private final Map cellTypeCache = new HashMap<>(); private final AtomicInteger distinctAtomCount = new AtomicInteger(0); + private final TypeCheckSelfDiagnosticsRunner selfDiagnosticsRunner; + + private final AtomicLong pendingTypeResolutions = new AtomicLong(0); private Env() { this.atomTable = new WeakHashMap<>(); @@ -76,6 +82,12 @@ private Env() { this.recFunctionAtoms = new ArrayList<>(); PredefinedTypeEnv.getInstance().initializeEnv(this); + String diagnosticEnable = System.getenv("BAL_TYPE_CHECK_DIAGNOSTIC_ENABLE"); + if ("true".equalsIgnoreCase(diagnosticEnable)) { + this.selfDiagnosticsRunner = new DebugSelfDiagnosticRunner(this); + } else { + this.selfDiagnosticsRunner = new NonOpSelfDiagnosticRunner(); + } } public static Env getInstance() { @@ -116,7 +128,27 @@ SemType getCachedCellType(SemType ty, CellAtomicType.CellMutability mut, Supplie if (ty.some() != 0) { return semTypeCreator.get(); } - return this.cellTypeCache.computeIfAbsent(new CellSemTypeCacheKey(ty, mut), k -> semTypeCreator.get()); + try { + cellTypeCacheLock.readLock().lock(); + SemType cached = this.cellTypeCache.get(new CellSemTypeCacheKey(ty, mut)); + if (cached != null) { + return cached; + } + } finally { + cellTypeCacheLock.readLock().unlock(); + } + try { + cellTypeCacheLock.writeLock().lock(); + SemType cached = this.cellTypeCache.get(new CellSemTypeCacheKey(ty, mut)); + if (cached != null) { + return cached; + } + var result = semTypeCreator.get(); + this.cellTypeCache.put(new CellSemTypeCacheKey(ty, mut), result); + return result; + } finally { + cellTypeCacheLock.writeLock().unlock(); + } } public RecAtom recListAtom() { @@ -257,4 +289,104 @@ public Optional atomicTypeByIndex(int index) { atomLock.readLock().unlock(); } } + + // When it comes to types there are 2 distinct stages, first we need to resolve types (ie turn type + // descriptor in to a semtype) and then do the type checking. In the compiler there is a clear temporal separation + // between these stages, but in runtime since we allow creating type dynamically we must allow them to interleave. + // As result, we have to treat both these stages of the type check. When a type is being used for type checking + // it is resolved (modifying the type after this point is undefined behaviour). To understand concurrency model for + // type checking we can break up type checking to 2 phases as type resolution and type checking. To allow + // concurrent type checking we need ensure fallowing invariants. + // 1. Phase 1 should be able to run in a non-blocking manner. Assume we are checking T1 < T2 and T3 < T4 + // concurrently with T1 depending on T3 and T4 depending on T2. If they are blocking we can have a deadlock. + // 2. Before starting phase 2 all the types involved in the type check must be resolved. In above example T3 which + // is needed for first type check is being resolved as a part of the second type check. + // Furthermore, ideally we shouldn't resolve the same type multiple times and both type checks should be able to + // run parallel as much as possible. + // Given each (strand) thread has its own context, it is easier to reason about concurrency using Context. First + // we require all phase changes to go via the context which will synchronize with other contexts via the shared Env. + // First we allow any number of context to enter phase 1 and run without blocking(property 1). When context + // need to move to phase 2 it must wait for all contexts in phase 1 to finish. To prevent starvation when a + // context has indicated that it needs to move to phase 2 we stop any new context from entering phase 1. When all + // the contexts have reached phase 2 again they all can continue in parallel. At the same time we can allow new + // context to enter phase 1. + + void enterTypeResolutionPhase(Context cx, MutableSemType t) throws InterruptedException { + pendingTypeResolutions.incrementAndGet(); + this.selfDiagnosticsRunner.registerTypeResolutionStart(cx, t); + } + + void exitTypeResolutionPhaseAbruptly(Context cx, Exception ex) { + try { + pendingTypeResolutions.decrementAndGet(); + releaseLock((ReentrantReadWriteLock) atomLock); + releaseLock((ReentrantReadWriteLock) recListLock); + releaseLock((ReentrantReadWriteLock) recMapLock); + releaseLock((ReentrantReadWriteLock) recFunctionLock); + } catch (Exception ignored) { + + } + this.selfDiagnosticsRunner.registerAbruptTypeResolutionEnd(cx, ex); + } + + private void releaseLock(ReentrantReadWriteLock lock) { + if (lock.writeLock().isHeldByCurrentThread()) { + lock.writeLock().unlock(); + } + if (lock.getReadHoldCount() > 0) { + lock.readLock().unlock(); + } + } + + void exitTypeResolutionPhase(Context cx) { + long res = pendingTypeResolutions.decrementAndGet(); + assert res >= 0; + this.selfDiagnosticsRunner.registerTypeResolutionExit(cx); + } + + void enterTypeCheckingPhase(Context cx, SemType t1, SemType t2) { + assert pendingTypeResolutions.get() >= 0; + while (pendingTypeResolutions.get() != 0) { + try { + Thread.sleep(10); + } catch (InterruptedException ignored) { + } + } + this.selfDiagnosticsRunner.registerTypeCheckStart(cx, t1, t2); + } + + void exitTypeCheckingPhase(Context cx) { + this.selfDiagnosticsRunner.registerTypeCheckEnd(cx); + } + + void registerAbruptTypeCheckEnd(Context context, Exception ex) { + this.selfDiagnosticsRunner.registerAbruptTypeCheckEnd(context, ex); + } + + List getRecListAtomsCopy() { + recListLock.readLock().lock(); + try { + return new ArrayList<>(this.recListAtoms); + } finally { + recListLock.readLock().unlock(); + } + } + + List getRecMappingAtomsCopy() { + recMapLock.readLock().lock(); + try { + return new ArrayList<>(this.recMappingAtoms); + } finally { + recMapLock.readLock().unlock(); + } + } + + List getRecFunctionAtomsCopy() { + recFunctionLock.readLock().lock(); + try { + return new ArrayList<>(this.recFunctionAtoms); + } finally { + recFunctionLock.readLock().unlock(); + } + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/NonOpSelfDiagnosticRunner.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/NonOpSelfDiagnosticRunner.java new file mode 100644 index 000000000000..47aa4d078c60 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/NonOpSelfDiagnosticRunner.java @@ -0,0 +1,36 @@ +package io.ballerina.runtime.api.types.semtype; + +import io.ballerina.runtime.internal.types.semtype.MutableSemType; + +public class NonOpSelfDiagnosticRunner implements TypeCheckSelfDiagnosticsRunner { + + @Override + public void registerTypeResolutionStart(Context cx, MutableSemType type) { + + } + + @Override + public void registerTypeCheckStart(Context cx, SemType t1, SemType t2) { + + } + + @Override + public void registerTypeCheckEnd(Context cx) { + + } + + @Override + public void registerAbruptTypeResolutionEnd(Context cx, Exception ex) { + + } + + @Override + public void registerAbruptTypeCheckEnd(Context cx, Exception ex) { + + } + + @Override + public void registerTypeResolutionExit(Context cx) { + + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/PredefinedTypeEnv.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/PredefinedTypeEnv.java index 1545a36e572e..22c385d1a9e1 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/PredefinedTypeEnv.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/PredefinedTypeEnv.java @@ -566,29 +566,19 @@ SemType readonlyType() { // Due to some reason SpotBug thinks this method is overrideable if we don't put final here as well. final void initializeEnv(Env env) { assert initialized.get() : "PredefinedTypeEnv has not fully initialized, check concurrency issues"; - fillRecAtoms(env.recListAtoms, initializedRecListAtoms); - fillRecAtoms(env.recMappingAtoms, initializedRecMappingAtoms); + fillRecAtoms(env.recListAtoms, initializedRecListAtoms, initializedRecListAtoms.size()); + fillRecAtoms(env.recMappingAtoms, initializedRecMappingAtoms, initializedRecMappingAtoms.size()); initializedCellAtoms.forEach(each -> env.cellAtom(each.atomicType())); initializedListAtoms.forEach(each -> env.listAtom(each.atomicType())); } - private void fillRecAtoms(List envRecAtomList, List initializedRecAtoms) { - int count = reservedRecAtomCount(); - for (int i = 0; i < count; i++) { - if (i < initializedRecAtoms.size()) { - envRecAtomList.add(initializedRecAtoms.get(i)); - } else { - // This is mainly to help with bir serialization/deserialization logic. Given the number of such atoms - // will be small this shouldn't be a problem. - envRecAtomList.add(null); - } + private void fillRecAtoms(List envRecAtomList, List initializedRecAtoms, + int reservedAtomCount) { + for (int i = 0; i < reservedAtomCount; i++) { + envRecAtomList.add(initializedRecAtoms.get(i)); } } - private int reservedRecAtomCount() { - return Integer.max(initializedRecListAtoms.size(), initializedRecMappingAtoms.size()); - } - SemType cellSemTypeInner() { return cellSemTypeInner.get(); } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/SemType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/SemType.java index d185c96b6c7f..3690198efa3c 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/SemType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/SemType.java @@ -57,10 +57,11 @@ protected void setSome(int some, SubType[] subTypeData) { this.subTypeData = subTypeData; } - public static SemType tryInto(Type type) { + public static SemType tryInto(Context cx, Type type) { if (type instanceof MutableSemType mutableSemType) { - mutableSemType.updateInnerSemTypeIfNeeded(); + mutableSemType.updateInnerSemTypeIfNeeded(cx); } + return (SemType) type; } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/ShapeAnalyzer.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/ShapeAnalyzer.java index 99bc4c9a51f5..2eceade31e8e 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/ShapeAnalyzer.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/ShapeAnalyzer.java @@ -23,7 +23,7 @@ public static Optional acceptedTypeOf(Context cx, Type typeDesc) { if (typeDesc instanceof TypeWithAcceptedType typeWithAcceptedType) { return typeWithAcceptedType.acceptedTypeOf(cx); } - return Optional.of(SemType.tryInto(typeDesc)); + return Optional.of(SemType.tryInto(cx, typeDesc)); } public static Optional shapeOf(Context cx, Object object) { diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/TypeCheckSelfDiagnosticsRunner.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/TypeCheckSelfDiagnosticsRunner.java new file mode 100644 index 000000000000..aa3b65bc80f4 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/types/semtype/TypeCheckSelfDiagnosticsRunner.java @@ -0,0 +1,18 @@ +package io.ballerina.runtime.api.types.semtype; + +import io.ballerina.runtime.internal.types.semtype.MutableSemType; + +interface TypeCheckSelfDiagnosticsRunner { + + void registerTypeResolutionStart(Context cx, MutableSemType type); + + void registerTypeCheckStart(Context cx, SemType t1, SemType t2); + + void registerTypeCheckEnd(Context cx); + + void registerAbruptTypeResolutionEnd(Context cx, Exception ex); + + void registerAbruptTypeCheckEnd(Context cx, Exception ex); + + void registerTypeResolutionExit(Context cx); +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/values/BValue.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/values/BValue.java index c23f563ff6d1..6386420a90ad 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/values/BValue.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/api/values/BValue.java @@ -67,11 +67,11 @@ default String informalStringValue(BLink parent) { * * @return {@code SemType} representing the value's basic type */ - default SemType widenedType() { + default SemType widenedType(Context cx) { // This is wrong since we are actually returning the actual (narrowed) type of the value. But since this is // used only as an optimization (to avoid recalculating singleton type) in the type checker this is better // than caching the widened types as well. - return SemType.tryInto(getType()); + return SemType.tryInto(cx, getType()); } default Optional inherentTypeOf(Context cx) { diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/TypeChecker.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/TypeChecker.java index 2dedd7250a87..c8ebf148d26f 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/TypeChecker.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/TypeChecker.java @@ -124,8 +124,10 @@ public static Object checkCast(Object sourceVal, Type targetType) { return sourceVal; } Type sourceType = getType(sourceVal); - if (Core.containsBasicType(SemType.tryInto(sourceType), ConvertibleCastMaskHolder.CONVERTIBLE_CAST_MASK) && - Core.containsBasicType(SemType.tryInto(targetType), ConvertibleCastMaskHolder.CONVERTIBLE_CAST_MASK)) { + Context cx = context(); + if (Core.containsBasicType(SemType.tryInto(cx, sourceType), ConvertibleCastMaskHolder.CONVERTIBLE_CAST_MASK) && + Core.containsBasicType(SemType.tryInto(cx, targetType), + ConvertibleCastMaskHolder.CONVERTIBLE_CAST_MASK)) { // We need to maintain order for these? if (targetType instanceof BUnionType unionType) { for (Type memberType : unionType.getMemberTypes()) { @@ -259,12 +261,12 @@ public static boolean anyToJBoolean(Object sourceVal) { public static boolean checkIsType(Object sourceVal, Type targetType) { Context cx = context(); Type sourceType = getType(sourceVal); - if (isSubType(sourceType, targetType)) { + if (isSubType(cx, sourceType, targetType)) { return true; } - SemType sourceSemType = SemType.tryInto(sourceType); + SemType sourceSemType = SemType.tryInto(cx, sourceType); return couldInherentTypeBeDifferent(sourceSemType) && - isSubTypeWithInherentType(cx, sourceVal, SemType.tryInto(targetType)); + isSubTypeWithInherentType(cx, sourceVal, SemType.tryInto(cx, targetType)); } /** @@ -339,7 +341,8 @@ private static SemType appendNumericConversionTypes(SemType semType) { * @return true if the two types are same; false otherwise */ public static boolean isSameType(Type sourceType, Type targetType) { - return Core.isSameType(context(), SemType.tryInto(sourceType), SemType.tryInto(targetType)); + Context cx = context(); + return Core.isSameType(cx, SemType.tryInto(cx, sourceType), SemType.tryInto(cx, targetType)); } public static Type getType(Object value) { @@ -425,8 +428,8 @@ public static boolean isReferenceEqual(Object lhsValue, Object rhsValue) { Context cx = context(); SemType lhsType = widenedType(cx, lhsValue); SemType rhsType = widenedType(cx, rhsValue); - if (isSimpleBasicSemType(lhsType)) { - return isSimpleBasicValuesEqual(lhsValue, rhsValue); + if (isSimpleBasicSemType(cx, lhsType)) { + return isSimpleBasicValuesEqual(cx, lhsValue, rhsValue); } Predicate basicTypePredicate = (basicType) -> Core.isSubType(cx, lhsType, basicType) && Core.isSubType(cx, rhsType, basicType); @@ -494,15 +497,14 @@ private static boolean isFunctionPointerEqual(Type lhsType, Type rhsType) { lhsType.getName().equals(rhsType.getName()) && rhsType.equals(lhsType); } - private static boolean isSimpleBasicValuesEqual(Object v1, Object v2) { - Context cx = context(); + private static boolean isSimpleBasicValuesEqual(Context cx, Object v1, Object v2) { SemType v1Ty = widenedType(cx, v1); - if (!isSimpleBasicSemType(v1Ty)) { + if (!isSimpleBasicSemType(cx, v1Ty)) { return false; } SemType v2Ty = widenedType(cx, v2); - if (!isSimpleBasicSemType(v2Ty)) { + if (!isSimpleBasicSemType(cx, v2Ty)) { return false; } @@ -532,7 +534,8 @@ public static TypedescValue getTypedesc(Object value) { if (type == null) { return null; } - if (belongToSingleBasicTypeOrString(type)) { + Context cx = context(); + if (belongToSingleBasicTypeOrString(cx, type)) { return new TypedescValueImpl(new BFiniteType(value.toString(), Set.of(value), 0)); } if (value instanceof BRefValue bRefValue) { @@ -564,12 +567,12 @@ public static Object getAnnotValue(TypedescValue typedescValue, BString annotTag * @return flag indicating the equivalence of the two types */ public static boolean checkIsType(Type sourceType, Type targetType) { - return isSubType(sourceType, targetType); + return isSubType(context(), sourceType, targetType); } @Deprecated public static boolean checkIsType(Type sourceType, Type targetType, List unresolvedTypes) { - return isSubType(sourceType, targetType); + return isSubType(context(), sourceType, targetType); } /** @@ -585,7 +588,8 @@ public static boolean checkDecimalEqual(DecimalValue lhsValue, DecimalValue rhsV } public static boolean isNumericType(Type type) { - return Core.isSubType(context(), SemType.tryInto(type), NumericTypeHolder.NUMERIC_TYPE); + Context cx = context(); + return Core.isSubType(cx, SemType.tryInto(cx, type), NumericTypeHolder.NUMERIC_TYPE); } public static boolean isByteLiteral(long longValue) { @@ -597,28 +601,25 @@ public static boolean isByteLiteral(long longValue) { private static boolean isSubTypeWithInherentType(Context cx, Object sourceValue, SemType target) { return ShapeAnalyzer.inherentTypeOf(cx, sourceValue) .map(source -> !Core.isEmpty(cx, source) && Core.isSubType(cx, source, target)) - // OR else do the normal type check by taking the shape of .orElse(false); } - private static boolean isSubType(Type source, Type target) { + private static boolean isSubType(Context cx, Type source, Type target) { if (source instanceof CacheableTypeDescriptor sourceCacheableType && target instanceof CacheableTypeDescriptor targetCacheableType) { - return isSubTypeWithCache(sourceCacheableType, targetCacheableType); + return isSubTypeWithCache(cx, sourceCacheableType, targetCacheableType); } - // 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(context(), source, target); + return isSubTypeInner(cx, source, target); } private static boolean isSubTypeInner(Context cx, Type source, Type target) { - SemType sourceSemType = SemType.tryInto(source); - SemType targetSemType = SemType.tryInto(target); + SemType sourceSemType = SemType.tryInto(cx, source); + SemType targetSemType = SemType.tryInto(cx, target); return Core.isSubType(cx, sourceSemType, targetSemType); } - private static boolean isSubTypeWithCache(CacheableTypeDescriptor source, CacheableTypeDescriptor target) { - Context cx = context(); + private static boolean isSubTypeWithCache(Context cx, CacheableTypeDescriptor source, + CacheableTypeDescriptor target) { if (!source.shouldCache() || !target.shouldCache()) { return isSubTypeInner(cx, source, target); } @@ -634,7 +635,7 @@ private static boolean isSubTypeWithCache(CacheableTypeDescriptor source, Cachea private static SemType widenedType(Context cx, Object value) { if (value instanceof BValue bValue) { - return bValue.widenedType(); + return bValue.widenedType(cx); } if (value == null) { return Builder.getNilType(); @@ -652,8 +653,9 @@ private static SemType widenedType(Context cx, Object value) { public static boolean isInherentlyImmutableType(Type sourceType) { // readonly part is there to match to old API + Context cx = context(); return - Core.isSubType(context(), SemType.tryInto(sourceType), + Core.isSubType(cx, SemType.tryInto(cx, sourceType), InherentlyImmutableTypeHolder.INHERENTLY_IMMUTABLE_TYPE) || sourceType instanceof ReadonlyType; } @@ -813,11 +815,10 @@ public static boolean isEqual(Object lhsValue, Object rhsValue, Set c return false; } - return checkValueEqual(lhsValue, rhsValue, new HashSet<>(checkedValues)); + return checkValueEqual(context(), lhsValue, rhsValue, new HashSet<>(checkedValues)); } - private static boolean checkValueEqual(Object lhsValue, Object rhsValue, Set checkedValues) { - Context cx = context(); + private static boolean checkValueEqual(Context cx, Object lhsValue, Object rhsValue, Set checkedValues) { SemType lhsShape = ShapeAnalyzer.inherentTypeOf(cx, lhsValue).orElseThrow(); SemType rhsShape = ShapeAnalyzer.inherentTypeOf(cx, rhsValue).orElseThrow(); Predicate belongToSameBasicType = (basicType) -> Core.containsBasicType(lhsShape, basicType) && @@ -977,7 +978,7 @@ private static boolean isHandleValueRefEqual(Object lhsValue, Object rhsValue) { * @return whether there's an implicit initial value or not. */ public static boolean hasFillerValue(Type type) { - return hasFillerValue(type, new ArrayList<>()); + return hasFillerValue(context(), type, new ArrayList<>()); } private enum FillerValueResult { @@ -999,12 +1000,12 @@ private static FillerValueResult hasFillerValueSemType(Context cx, SemType type) FillerValueResult.FALSE; } - private static boolean hasFillerValue(Type type, List unanalyzedTypes) { + private static boolean hasFillerValue(Context cx, Type type, List unanalyzedTypes) { if (type == null) { return true; } - FillerValueResult fastResult = hasFillerValueSemType(context(), SemType.tryInto(type)); + FillerValueResult fastResult = hasFillerValueSemType(cx, SemType.tryInto(cx, type)); if (fastResult != FillerValueResult.MAYBE) { return fastResult == FillerValueResult.TRUE; } @@ -1022,29 +1023,29 @@ private static boolean hasFillerValue(Type type, List unanalyzedTypes) { case TypeTags.STREAM_TAG, TypeTags.MAP_TAG, TypeTags.ANY_TAG -> true; - case TypeTags.ARRAY_TAG -> checkFillerValue((BArrayType) type, unanalyzedTypes); + case TypeTags.ARRAY_TAG -> checkFillerValue(cx, (BArrayType) type, unanalyzedTypes); case TypeTags.FINITE_TYPE_TAG -> checkFillerValue((BFiniteType) type); case TypeTags.OBJECT_TYPE_TAG, - TypeTags.SERVICE_TAG -> checkFillerValue((BObjectType) type); - case TypeTags.RECORD_TYPE_TAG -> checkFillerValue((BRecordType) type, unanalyzedTypes); - case TypeTags.TUPLE_TAG -> checkFillerValue((BTupleType) type, unanalyzedTypes); + TypeTags.SERVICE_TAG -> checkFillerValue(cx, (BObjectType) type); + case TypeTags.RECORD_TYPE_TAG -> checkFillerValue(cx, (BRecordType) type, unanalyzedTypes); + case TypeTags.TUPLE_TAG -> checkFillerValue(cx, (BTupleType) type, unanalyzedTypes); case TypeTags.UNION_TAG -> checkFillerValue((BUnionType) type, unanalyzedTypes); case TypeTags.TYPE_REFERENCED_TYPE_TAG -> - hasFillerValue(((BTypeReferenceType) type).getReferredType(), unanalyzedTypes); + hasFillerValue(cx, ((BTypeReferenceType) type).getReferredType(), unanalyzedTypes); case TypeTags.INTERSECTION_TAG -> - hasFillerValue(((BIntersectionType) type).getEffectiveType(), unanalyzedTypes); + hasFillerValue(cx, ((BIntersectionType) type).getEffectiveType(), unanalyzedTypes); default -> false; }; } - private static boolean checkFillerValue(BTupleType tupleType, List unAnalyzedTypes) { + private static boolean checkFillerValue(Context cx, BTupleType tupleType, List unAnalyzedTypes) { if (unAnalyzedTypes.contains(tupleType)) { return true; } unAnalyzedTypes.add(tupleType); for (Type member : tupleType.getTupleTypes()) { - if (!hasFillerValue(member, unAnalyzedTypes)) { + if (!hasFillerValue(cx, member, unAnalyzedTypes)) { return false; } } @@ -1153,7 +1154,7 @@ private static boolean containsSameBasicType (Type nonFiniteType, Set fi return true; } - private static boolean checkFillerValue(BRecordType type, List unAnalyzedTypes) { + private static boolean checkFillerValue(Context cx, BRecordType type, List unAnalyzedTypes) { if (unAnalyzedTypes.contains(type)) { return true; } @@ -1170,11 +1171,11 @@ private static boolean checkFillerValue(BRecordType type, List unAnalyzedT return true; } - private static boolean checkFillerValue(BArrayType type, List unAnalyzedTypes) { - return type.getState() == ArrayState.OPEN || hasFillerValue(type.getElementType(), unAnalyzedTypes); + private static boolean checkFillerValue(Context cx, BArrayType type, List unAnalyzedTypes) { + return type.getState() == ArrayState.OPEN || hasFillerValue(cx, type.getElementType(), unAnalyzedTypes); } - private static boolean checkFillerValue(BObjectType type) { + private static boolean checkFillerValue(Context cx, BObjectType type) { MethodType generatedInitMethod = type.getGeneratedInitMethod(); if (generatedInitMethod == null) { // abstract objects doesn't have a filler value. @@ -1250,14 +1251,12 @@ private static BError createTypeCastError(Object value, Type targetType, List ErrorUtils.createTypeCastError(inputValue, PredefinedTypes.TYPE_BYTE)); } - Predicate isIntSubType = (subType) -> Core.isSameType(cx, targetType, SemType.tryInto(subType)); + Predicate isIntSubType = (subType) -> Core.isSameType(cx, targetType, SemType.tryInto(cx, subType)); if (isIntSubType.test(PredefinedTypes.TYPE_INT_SIGNED_32)) { return anyToSigned32(inputValue); } @@ -171,14 +170,14 @@ private static Object castValueToInt(SemType targetType, Object inputValue) { } public static Object castValues(Type targetType, Object inputValue) { - return castValuesInner(SemType.tryInto(targetType), inputValue, + Context cx = TypeChecker.context(); + return castValuesInner(cx, SemType.tryInto(cx, targetType), inputValue, () -> ErrorUtils.createTypeCastError(inputValue, targetType)); } - static Object castValuesInner(SemType targetType, Object inputValue, Supplier errorSupplier) { - Context cx = TypeChecker.context(); + static Object castValuesInner(Context cx, SemType targetType, Object inputValue, Supplier errorSupplier) { if (Core.isSubType(cx, targetType, Builder.getIntType())) { - return castValueToInt(targetType, inputValue); + return castValueToInt(cx, targetType, inputValue); } if (Core.isSubType(cx, targetType, Builder.getDecimalType())) { return anyToDecimalCast(inputValue, () -> @@ -424,10 +423,11 @@ private static Type getConvertibleStructuredTypeInUnion(Object inputValue, Strin public static Type getConvertibleFiniteType(Object inputValue, BFiniteType targetFiniteType, String varName, List errors, Set unresolvedValues, boolean allowNumericConversion) { + Context cx = TypeChecker.context(); // only the first matching type is returned. if (targetFiniteType.valueSpace.size() == 1) { Type valueType = getType(targetFiniteType.valueSpace.iterator().next()); - if (!belongToSingleBasicTypeOrString(valueType) && valueType.getTag() != TypeTags.NULL_TAG) { + if (!belongToSingleBasicTypeOrString(cx, valueType) && valueType.getTag() != TypeTags.NULL_TAG) { return getConvertibleType(inputValue, valueType, varName, unresolvedValues, errors, allowNumericConversion); } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BAnydataType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BAnydataType.java index 71dcdc374f48..26cb9762de48 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BAnydataType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BAnydataType.java @@ -25,6 +25,7 @@ import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.Context; import io.ballerina.runtime.api.types.semtype.Core; import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.internal.values.RefValue; @@ -94,7 +95,7 @@ public String toString() { // semtype. But some things could depend on this being a union type descriptor // as well (which has to be mutable) @Override - public SemType createSemType() { + public SemType createSemType(Context cx) { SemType semType = Builder.getAnyDataType(); if (isReadOnly()) { semType = Core.intersect(semType, Builder.getReadonlyType()); diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BArrayType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BArrayType.java index 07834065c87d..e3b56bd17d35 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BArrayType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BArrayType.java @@ -227,8 +227,8 @@ public void setIntersectionType(IntersectionType intersectionType) { } @Override - public SemType createSemType() { - Env env = Env.getInstance(); + public SemType createSemType(Context cx) { + Env env = cx.env; if (defn.isDefinitionReady()) { return defn.getSemType(env); } @@ -239,7 +239,7 @@ public SemType createSemType() { ListDefinition ld = result.definition(); CellAtomicType.CellMutability mut = isReadOnly() ? CellAtomicType.CellMutability.CELL_MUT_NONE : CellAtomicType.CellMutability.CELL_MUT_LIMITED; - return getSemTypePart(env, ld, size, tryInto(getElementType()), mut); + return getSemTypePart(env, ld, size, tryInto(cx, getElementType()), mut); } private SemType getSemTypePart(Env env, ListDefinition defn, int size, SemType elementType, @@ -266,7 +266,7 @@ protected boolean isDependentlyTypedInner(Set visited) { @Override public Optional inherentTypeOf(Context cx, ShapeSupplier shapeSupplier, Object object) { if (!couldInherentTypeBeDifferent()) { - return Optional.of(getSemType()); + return Optional.of(getSemType(cx)); } AbstractArrayValue value = (AbstractArrayValue) object; SemType cachedShape = value.shapeOf(); diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BErrorType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BErrorType.java index 44e8d987ccbf..9881bb8e706b 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BErrorType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BErrorType.java @@ -29,7 +29,6 @@ import io.ballerina.runtime.api.types.semtype.Core; import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.api.values.BError; -import io.ballerina.runtime.internal.TypeChecker; import io.ballerina.runtime.internal.types.semtype.ErrorUtils; import io.ballerina.runtime.internal.values.ErrorValue; import io.ballerina.runtime.internal.values.MapValueImpl; @@ -128,23 +127,23 @@ public void setIntersectionType(IntersectionType intersectionType) { } @Override - public SemType createSemType() { + public SemType createSemType(Context cx) { SemType err; if (detailType == null || isTopType()) { err = Builder.getErrorType(); } else { - err = ErrorUtils.errorDetail(tryInto(getDetailType())); + err = ErrorUtils.errorDetail(tryInto(cx, getDetailType())); } - initializeDistinctIdSupplierIfNeeded(); + initializeDistinctIdSupplierIfNeeded(cx); return distinctIdSupplier.get().stream().map(ErrorUtils::errorDistinct).reduce(err, Core::intersect); } - private void initializeDistinctIdSupplierIfNeeded() { + private void initializeDistinctIdSupplierIfNeeded(Context cx) { if (distinctIdSupplier == null) { synchronized (this) { if (distinctIdSupplier == null) { - distinctIdSupplier = new DistinctIdSupplier(TypeChecker.context().env, getTypeIdSet()); + distinctIdSupplier = new DistinctIdSupplier(cx.env, getTypeIdSet()); } } } @@ -163,14 +162,14 @@ private boolean isTopType() { @Override public Optional inherentTypeOf(Context cx, ShapeSupplier shapeSupplier, Object object) { if (!couldInherentTypeBeDifferent()) { - return Optional.of(getSemType()); + return Optional.of(getSemType(cx)); } BError errorValue = (BError) object; Object details = errorValue.getDetails(); if (!(details instanceof MapValueImpl errorDetails)) { return Optional.empty(); } - initializeDistinctIdSupplierIfNeeded(); + initializeDistinctIdSupplierIfNeeded(cx); // Should we actually pass the readonly shape supplier here? return BMapType.shapeOfInner(cx, shapeSupplier, errorDetails) .map(ErrorUtils::errorDetail) @@ -190,7 +189,7 @@ public Optional shapeOf(Context cx, ShapeSupplier shapeSupplierFn, Obje @Override public Optional acceptedTypeOf(Context cx) { - return Optional.of(getSemType()); + return Optional.of(getSemType(cx)); } @Override diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFiniteType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFiniteType.java index bcef259ed050..8cb63b5cf11e 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFiniteType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFiniteType.java @@ -210,8 +210,7 @@ public boolean equals(Object o) { } @Override - public SemType createSemType() { - Context cx = TypeChecker.context(); + public SemType createSemType(Context cx) { return this.valueSpace.stream().map(each -> ShapeAnalyzer.inherentTypeOf(cx, each)) .map(Optional::orElseThrow) .reduce(Builder.getNeverType(), Core::union); diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFunctionType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFunctionType.java index ecc2a44c1f98..3ada505c5f5f 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFunctionType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFunctionType.java @@ -26,6 +26,7 @@ import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.Context; import io.ballerina.runtime.api.types.semtype.Env; import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.internal.types.semtype.CellAtomicType; @@ -49,8 +50,6 @@ public class BFunctionType extends BAnnotatableType implements FunctionType { public Type retType; public long flags; public Parameter[] parameters; - private static final Env env = Env.getInstance(); - private static final SemType ISOLATED_TOP = createIsolatedTop(env); private final DefinitionContainer defn = new DefinitionContainer<>(); @@ -223,10 +222,11 @@ private static SemType createIsolatedTop(Env env) { } @Override - public SemType createSemType() { + public SemType createSemType(Context cx) { if (isFunctionTop()) { - return getTopType(); + return getTopType(cx); } + Env env = cx.env; if (defn.isDefinitionReady()) { return defn.getSemType(env); } @@ -237,25 +237,25 @@ public SemType createSemType() { FunctionDefinition fd = result.definition(); SemType[] params = new SemType[parameters.length]; for (int i = 0; i < parameters.length; i++) { - params[i] = getSemType(parameters[i].type); + params[i] = getSemType(cx, parameters[i].type); } SemType rest; if (restType instanceof BArrayType arrayType) { - rest = getSemType(arrayType.getElementType()); + rest = getSemType(cx, arrayType.getElementType()); } else { rest = Builder.getNeverType(); } - SemType returnType = resolveReturnType(); + SemType returnType = resolveReturnType(cx); ListDefinition paramListDefinition = new ListDefinition(); SemType paramType = paramListDefinition.defineListTypeWrapped(env, params, params.length, rest, CellAtomicType.CellMutability.CELL_MUT_NONE); return fd.define(env, paramType, returnType, getQualifiers()); } - private SemType getTopType() { + private SemType getTopType(Context cx) { if (SymbolFlags.isFlagOn(flags, SymbolFlags.ISOLATED)) { - return ISOLATED_TOP; + return createIsolatedTop(cx.env); } return Builder.getFunctionType(); } @@ -265,8 +265,8 @@ FunctionQualifiers getQualifiers() { SymbolFlags.isFlagOn(flags, SymbolFlags.TRANSACTIONAL)); } - private SemType getSemType(Type type) { - return tryInto(type); + private SemType getSemType(Context cx, Type type) { + return tryInto(cx, type); } private boolean isFunctionTop() { @@ -294,17 +294,17 @@ private boolean isDependentlyTypeParameters(Set visited) { .anyMatch(each -> ((MayBeDependentType) each).isDependentlyTyped(visited)); } - private SemType resolveReturnType() { + private SemType resolveReturnType(Context cx) { if (retType == null) { return Builder.getNilType(); } MayBeDependentType retBType = (MayBeDependentType) retType; - SemType returnType = getSemType(retType); + SemType returnType = getSemType(cx, retType); ListDefinition ld = new ListDefinition(); SemType dependentlyTypedBit = retBType.isDependentlyTyped() ? Builder.getBooleanConst(true) : Builder.getBooleanType(); SemType[] innerType = new SemType[]{dependentlyTypedBit, returnType}; - return ld.defineListTypeWrapped(env, innerType, 2, Builder.getNeverType(), + return ld.defineListTypeWrapped(cx.env, innerType, 2, Builder.getNeverType(), CellAtomicType.CellMutability.CELL_MUT_NONE); } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFutureType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFutureType.java index 8839a92922fe..6bdd3d8f788e 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFutureType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BFutureType.java @@ -23,8 +23,8 @@ import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.Context; import io.ballerina.runtime.api.types.semtype.SemType; -import io.ballerina.runtime.internal.TypeChecker; import io.ballerina.runtime.internal.types.semtype.FutureUtils; import java.util.Objects; @@ -101,11 +101,11 @@ private String getConstraintString() { } @Override - public SemType createSemType() { + public SemType createSemType(Context cx) { if (constraint == null) { return Builder.getFutureType(); } - return FutureUtils.futureContaining(TypeChecker.context().env, tryInto(constraint)); + return FutureUtils.futureContaining(cx.env, tryInto(cx, constraint)); } @Override diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BIntersectionType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BIntersectionType.java index dc1991feb42c..dc8d99702e55 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BIntersectionType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BIntersectionType.java @@ -29,7 +29,6 @@ import io.ballerina.runtime.api.types.semtype.Core; import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.api.types.semtype.ShapeAnalyzer; -import io.ballerina.runtime.internal.TypeChecker; import io.ballerina.runtime.internal.types.semtype.ErrorUtils; import io.ballerina.runtime.internal.types.semtype.ObjectDefinition; @@ -229,8 +228,8 @@ public void setIntersectionType(IntersectionType intersectionType) { } @Override - public SemType createSemType() { - return createSemTypeInner(SemType::tryInto); + public SemType createSemType(Context cx) { + return createSemTypeInner(cx, type -> SemType.tryInto(cx, type)); } @Override @@ -239,28 +238,28 @@ protected boolean isDependentlyTypedInner(Set visited) { .anyMatch(type -> ((MayBeDependentType) type).isDependentlyTyped(visited)); } - private SemType createSemTypeInner(Function semTypeFunction) { + private SemType createSemTypeInner(Context cx, Function semTypeFunction) { if (constituentTypes.isEmpty()) { return Builder.getNeverType(); } SemType result = constituentTypes.stream().map(semTypeFunction).reduce(Core::intersect).orElseThrow(); - Optional distinctPart = distinctTypePart(result); + Optional distinctPart = distinctTypePart(cx, result); if (distinctPart.isPresent()) { result = Core.intersect(result, distinctPart.get()); } return result; } - private Optional distinctTypePart(SemType result) { + private Optional distinctTypePart(Context cx, SemType result) { if (Core.isSubtypeSimple(result, Builder.getErrorType())) { BErrorType effectiveErrorType = (BErrorType) getImpliedType(effectiveType); DistinctIdSupplier distinctIdSupplier = - new DistinctIdSupplier(TypeChecker.context().env, effectiveErrorType.getTypeIdSet()); + new DistinctIdSupplier(cx.env, effectiveErrorType.getTypeIdSet()); return distinctIdSupplier.get().stream().map(ErrorUtils::errorDistinct).reduce(Core::intersect); } else if (Core.isSubtypeSimple(result, Builder.getObjectType())) { BObjectType effectiveObjectType = (BObjectType) getImpliedType(effectiveType); DistinctIdSupplier distinctIdSupplier = - new DistinctIdSupplier(TypeChecker.context().env, effectiveObjectType.getTypeIdSet()); + new DistinctIdSupplier(cx.env, effectiveObjectType.getTypeIdSet()); return distinctIdSupplier.get().stream().map(ObjectDefinition::distinct).reduce(Core::intersect); } return Optional.empty(); @@ -277,7 +276,7 @@ public Optional shapeOf(Context cx, ShapeSupplier shapeSupplierFn, Obje @Override public Optional acceptedTypeOf(Context cx) { - return Optional.of(createSemTypeInner(type -> ShapeAnalyzer.acceptedTypeOf(cx, type).orElseThrow())); + return Optional.of(createSemTypeInner(cx, type -> ShapeAnalyzer.acceptedTypeOf(cx, type).orElseThrow())); } @Override diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BMapType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BMapType.java index 43d2058cd4d9..7908a47b8fa5 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BMapType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BMapType.java @@ -186,8 +186,8 @@ public void setIntersectionType(IntersectionType intersectionType) { } @Override - public SemType createSemType() { - Env env = Env.getInstance(); + public SemType createSemType(Context cx) { + Env env = cx.env; if (defn.isDefinitionReady()) { return defn.getSemType(env); } @@ -198,7 +198,7 @@ public SemType createSemType() { MappingDefinition md = result.definition(); CellAtomicType.CellMutability mut = isReadOnly() ? CELL_MUT_NONE : CellAtomicType.CellMutability.CELL_MUT_LIMITED; - return createSemTypeInner(env, md, tryInto(getConstrainedType()), mut); + return createSemTypeInner(env, md, tryInto(cx, getConstrainedType()), mut); } @Override @@ -210,7 +210,7 @@ public void resetSemType() { @Override public Optional inherentTypeOf(Context cx, ShapeSupplier shapeSupplier, Object object) { if (!couldInherentTypeBeDifferent()) { - return Optional.of(getSemType()); + return Optional.of(getSemType(cx)); } MapValueImpl value = (MapValueImpl) object; SemType cachedShape = value.shapeOf(); diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BNetworkObjectType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BNetworkObjectType.java index 5478813f2dc3..dbfcc2d6bd3a 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BNetworkObjectType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BNetworkObjectType.java @@ -22,6 +22,7 @@ import io.ballerina.runtime.api.types.NetworkObjectType; import io.ballerina.runtime.api.types.RemoteMethodType; import io.ballerina.runtime.api.types.ResourceMethodType; +import io.ballerina.runtime.api.types.semtype.Context; import java.util.ArrayList; import java.util.Arrays; @@ -84,18 +85,16 @@ public ResourceMethodType[] getResourceMethods() { } @Override - protected Collection allMethods() { + protected Collection allMethods(Context cx) { Stream methodStream = Arrays.stream(getMethods()) .filter(methodType -> !(SymbolFlags.isFlagOn(methodType.getFlags(), SymbolFlags.REMOTE) || SymbolFlags.isFlagOn(methodType.getFlags(), SymbolFlags.RESOURCE))) - .map(MethodData::fromMethod); + .map(type -> MethodData.fromMethod(cx, type)); Stream remoteMethodStream = Arrays.stream(getRemoteMethods()) - .map(MethodData::fromRemoteMethod); - Stream resourceMethodStream = - Arrays.stream(getResourceMethods()) - .map(method -> MethodData.fromResourceMethod( - (BResourceMethodType) method)); + .map(type -> MethodData.fromRemoteMethod(cx, type)); + Stream resourceMethodStream = Arrays.stream(getResourceMethods()) + .map(method -> MethodData.fromResourceMethod(cx, (BResourceMethodType) method)); return Stream.concat(methodStream, Stream.concat(remoteMethodStream, resourceMethodStream)).toList(); } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BObjectType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BObjectType.java index 713a96f0d469..150eab85f120 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BObjectType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BObjectType.java @@ -40,7 +40,6 @@ import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.values.BObject; import io.ballerina.runtime.api.values.BString; -import io.ballerina.runtime.internal.TypeChecker; import io.ballerina.runtime.internal.scheduling.Scheduler; import io.ballerina.runtime.internal.scheduling.Strand; import io.ballerina.runtime.internal.types.semtype.CellAtomicType; @@ -64,7 +63,7 @@ import java.util.Optional; import java.util.Set; import java.util.StringJoiner; -import java.util.function.Function; +import java.util.function.BiFunction; import static io.ballerina.runtime.api.types.TypeTags.SERVICE_TAG; @@ -89,6 +88,7 @@ public class BObjectType extends BStructureType implements ObjectType, TypeWithS private final DefinitionContainer defn = new DefinitionContainer<>(); private final DefinitionContainer acceptedTypeDefn = new DefinitionContainer<>(); private volatile DistinctIdSupplier distinctIdSupplier; + //private final Lock typeResolutionLock = new ReentrantLock(); /** * Create a {@code BObjectType} which represents the user defined struct type. @@ -281,25 +281,33 @@ public TypeIdSet getTypeIdSet() { } @Override - public final SemType createSemType() { - Env env = Env.getInstance(); - initializeDistinctIdSupplierIfNeeded(env); - CellAtomicType.CellMutability mut = - SymbolFlags.isFlagOn(getFlags(), SymbolFlags.READONLY) ? CellAtomicType.CellMutability.CELL_MUT_NONE : - CellAtomicType.CellMutability.CELL_MUT_LIMITED; - SemType innerType; - if (defn.isDefinitionReady()) { - innerType = defn.getSemType(env); - } else { - var result = defn.trySetDefinition(ObjectDefinition::new); - if (!result.updated()) { + public final SemType createSemType(Context cx) { + try { + // This is wrong (See {@code Env}). Instead this should be done similar to mapping and list definitions + // using rec atoms. + //typeResolutionLock.lock(); + Env env = cx.env; + initializeDistinctIdSupplierIfNeeded(env); + CellAtomicType.CellMutability mut = + SymbolFlags.isFlagOn(getFlags(), SymbolFlags.READONLY) ? + CellAtomicType.CellMutability.CELL_MUT_NONE : + CellAtomicType.CellMutability.CELL_MUT_LIMITED; + SemType innerType; + if (defn.isDefinitionReady()) { innerType = defn.getSemType(env); } else { - ObjectDefinition od = result.definition(); - innerType = semTypeInner(od, mut, SemType::tryInto); + var result = defn.trySetDefinition(ObjectDefinition::new); + if (!result.updated()) { + innerType = defn.getSemType(env); + } else { + ObjectDefinition od = result.definition(); + innerType = semTypeInner(cx, od, mut, SemType::tryInto); + } } + return distinctIdSupplier.get().stream().map(ObjectDefinition::distinct).reduce(innerType, Core::intersect); + } finally { + // typeResolutionLock.unlock(); } - return distinctIdSupplier.get().stream().map(ObjectDefinition::distinct).reduce(innerType, Core::intersect); } private static boolean skipField(Set seen, String name) { @@ -309,9 +317,9 @@ private static boolean skipField(Set seen, String name) { return !seen.add(name); } - private SemType semTypeInner(ObjectDefinition od, CellAtomicType.CellMutability mut, - Function semTypeSupplier) { - Env env = Env.getInstance(); + private SemType semTypeInner(Context cx, ObjectDefinition od, CellAtomicType.CellMutability mut, + BiFunction semTypeSupplier) { + Env env = cx.env; ObjectQualifiers qualifiers = getObjectQualifiers(); List members = new ArrayList<>(); Set seen = new HashSet<>(); @@ -323,10 +331,10 @@ private SemType semTypeInner(ObjectDefinition od, CellAtomicType.CellMutability Field field = entry.getValue(); boolean isPublic = SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.PUBLIC); boolean isImmutable = qualifiers.readonly() | SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.READONLY); - members.add(new Member(name, semTypeSupplier.apply(field.getFieldType()), Member.Kind.Field, + members.add(new Member(name, semTypeSupplier.apply(cx, field.getFieldType()), Member.Kind.Field, isPublic ? Member.Visibility.Public : Member.Visibility.Private, isImmutable)); } - for (MethodData method : allMethods()) { + for (MethodData method : allMethods(cx)) { String name = method.name(); if (skipField(seen, name)) { continue; @@ -355,7 +363,7 @@ private ObjectQualifiers getObjectQualifiers() { @Override public Optional inherentTypeOf(Context cx, ShapeSupplier shapeSupplier, Object object) { if (!couldInherentTypeBeDifferent()) { - return Optional.of(getSemType()); + return Optional.of(getSemType(cx)); } AbstractObjectValue abstractObjectValue = (AbstractObjectValue) object; SemType cachedShape = abstractObjectValue.shapeOf(); @@ -386,7 +394,7 @@ public Optional shapeOf(Context cx, ShapeSupplier shapeSupplierFn, Obje @Override public final Optional acceptedTypeOf(Context cx) { - Env env = Env.getInstance(); + Env env = cx.env; initializeDistinctIdSupplierIfNeeded(cx.env); CellAtomicType.CellMutability mut = CellAtomicType.CellMutability.CELL_MUT_UNLIMITED; SemType innerType; @@ -398,7 +406,8 @@ public final Optional acceptedTypeOf(Context cx) { innerType = acceptedTypeDefn.getSemType(env); } else { ObjectDefinition od = result.definition(); - innerType = semTypeInner(od, mut, (type -> ShapeAnalyzer.acceptedTypeOf(cx, type).orElseThrow())); + innerType = semTypeInner(cx, od, mut, + ((context, type) -> ShapeAnalyzer.acceptedTypeOf(context, type).orElseThrow())); } } return Optional.of( @@ -437,7 +446,7 @@ private SemType valueShape(Context cx, ShapeSupplier shapeSupplier, AbstractObje members.add(new Member(name, fieldShape(cx, shapeSupplier, field, object, isImmutable), Member.Kind.Field, isPublic ? Member.Visibility.Public : Member.Visibility.Private, isImmutable)); } - for (MethodData method : allMethods()) { + for (MethodData method : allMethods(cx)) { String name = method.name(); if (skipField(seen, name)) { continue; @@ -454,11 +463,11 @@ private SemType valueShape(Context cx, ShapeSupplier shapeSupplier, AbstractObje private static SemType fieldShape(Context cx, ShapeSupplier shapeSupplier, Field field, AbstractObjectValue objectValue, boolean isImmutable) { if (!isImmutable) { - return SemType.tryInto(field.getFieldType()); + return SemType.tryInto(cx, field.getFieldType()); } BString fieldName = StringUtils.fromString(field.getFieldName()); Optional shape = shapeSupplier.get(cx, objectValue.get(fieldName)); - assert !shape.isEmpty(); + assert shape.isPresent(); return shape.get(); } @@ -468,29 +477,29 @@ public void resetSemType() { super.resetSemType(); } - protected Collection allMethods() { + protected Collection allMethods(Context cx) { if (methodTypes == null) { return List.of(); } return Arrays.stream(methodTypes) - .map(MethodData::fromMethod).toList(); + .map((type) -> MethodData.fromMethod(cx, type)).toList(); } protected record MethodData(String name, long flags, SemType semType) { - static MethodData fromMethod(MethodType method) { + static MethodData fromMethod(Context cx, MethodType method) { return new MethodData(method.getName(), method.getFlags(), - tryInto(method.getType())); + tryInto(cx, method.getType())); } - static MethodData fromRemoteMethod(MethodType method) { + static MethodData fromRemoteMethod(Context cx, MethodType method) { // Remote methods need to be distinct with remote methods only there can be instance methods with the same // name return new MethodData("@remote_" + method.getName(), method.getFlags(), - tryInto(method.getType())); + tryInto(cx, method.getType())); } - static MethodData fromResourceMethod(BResourceMethodType method) { + static MethodData fromResourceMethod(Context cx, BResourceMethodType method) { StringBuilder sb = new StringBuilder(); sb.append(method.getAccessor()); for (var each : method.getResourcePath()) { @@ -505,28 +514,28 @@ static MethodData fromResourceMethod(BResourceMethodType method) { if (part == null) { paramTypes.add(Builder.getAnyType()); } else { - paramTypes.add(tryInto(part)); + paramTypes.add(tryInto(cx, part)); } } for (Parameter paramType : innerFn.getParameters()) { - paramTypes.add(tryInto(paramType.type)); + paramTypes.add(tryInto(cx, paramType.type)); } SemType rest; Type restType = innerFn.getRestType(); if (restType instanceof BArrayType arrayType) { - rest = tryInto(arrayType.getElementType()); + rest = tryInto(cx, arrayType.getElementType()); } else { rest = Builder.getNeverType(); } SemType returnType; if (innerFn.getReturnType() != null) { - returnType = tryInto(innerFn.getReturnType()); + returnType = tryInto(cx, innerFn.getReturnType()); } else { returnType = Builder.getNilType(); } ListDefinition paramListDefinition = new ListDefinition(); - Env env = TypeChecker.context().env; + Env env = cx.env; SemType paramType = paramListDefinition.defineListTypeWrapped(env, paramTypes.toArray(SemType[]::new), paramTypes.size(), rest, CellAtomicType.CellMutability.CELL_MUT_NONE); FunctionDefinition fd = new FunctionDefinition(); diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BParameterizedType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BParameterizedType.java index 05b9229d68ac..88a869dc6f53 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BParameterizedType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BParameterizedType.java @@ -21,6 +21,7 @@ import io.ballerina.runtime.api.types.ParameterizedType; import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; +import io.ballerina.runtime.api.types.semtype.Context; import io.ballerina.runtime.api.types.semtype.SemType; import java.util.Set; @@ -85,8 +86,8 @@ public int getParamIndex() { } @Override - public SemType createSemType() { - return SemType.tryInto(this.paramValueType); + public SemType createSemType(Context cx) { + return SemType.tryInto(cx, this.paramValueType); } @Override diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BRecordType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BRecordType.java index f64e13db2304..723b99da263c 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BRecordType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BRecordType.java @@ -243,8 +243,8 @@ public Map getDefaultValues() { } @Override - public SemType createSemType() { - Env env = Env.getInstance(); + public SemType createSemType(Context cx) { + Env env = cx.env; if (defn.isDefinitionReady()) { return defn.getSemType(env); } @@ -253,7 +253,7 @@ public SemType createSemType() { return defn.getSemType(env); } MappingDefinition md = result.definition(); - return createSemTypeInner(md, env, mut(), SemType::tryInto); + return createSemTypeInner(md, env, mut(), (type) -> SemType.tryInto(cx, type)); } private CellMutability mut() { @@ -296,7 +296,7 @@ public boolean isDependentlyTypedInner(Set visited) { @Override public Optional inherentTypeOf(Context cx, ShapeSupplier shapeSupplier, Object object) { if (!couldInherentTypeBeDifferent()) { - return Optional.of(getSemType()); + return Optional.of(getSemType(cx)); } MapValueImpl value = (MapValueImpl) object; SemType cachedSemType = value.shapeOf(); @@ -336,7 +336,7 @@ private SemType shapeOfInner(Context cx, ShapeSupplier shapeSupplier, MapValueIm if (!takeFieldShape) { getFields().values().stream() .filter(field -> !handledFields.contains(field.getFieldName())) - .map(field -> fieldShapeWithoutValue(field, field.getFieldName())) + .map(field -> fieldShapeWithoutValue(cx, field, field.getFieldName())) .forEach(fields::add); } MappingDefinition.Field[] fieldsArray = fields.toArray(MappingDefinition.Field[]::new); @@ -344,23 +344,22 @@ private SemType shapeOfInner(Context cx, ShapeSupplier shapeSupplier, MapValueIm if (takeFieldShape) { rest = Builder.getNeverType(); } else { - rest = restFieldType != null ? SemType.tryInto(restFieldType) : getNeverType(); + rest = restFieldType != null ? SemType.tryInto(cx, restFieldType) : getNeverType(); } SemType shape = md.defineMappingTypeWrapped(env, fieldsArray, rest, mut()); value.resetReadonlyShapeDefinition(); return shape; } - private MappingDefinition.Field fieldShapeWithoutValue(Field field, String fieldName) { + private MappingDefinition.Field fieldShapeWithoutValue(Context cx, Field field, String fieldName) { boolean isOptional = fieldIsOptional(fieldName); boolean isReadonly = fieldIsReadonly(fieldName); - SemType fieldType = SemType.tryInto(field.getFieldType()); + SemType fieldType = SemType.tryInto(cx, field.getFieldType()); if (isReadonly && isOptional) { fieldType = Builder.getUndefType(); } - MappingDefinition.Field field1 = new MappingDefinition.Field(field.getFieldName(), fieldType, + return new MappingDefinition.Field(field.getFieldName(), fieldType, isReadonly, isOptional); - return field1; } @Override @@ -424,7 +423,7 @@ private MappingDefinition.Field fieldShape(Context cx, ShapeSupplier shapeSuppli optionalField = false; fieldType = shapeSupplier.get(cx, fieldValue).orElseThrow(); } else { - fieldType = SemType.tryInto(fieldType(fieldName)); + fieldType = SemType.tryInto(cx, fieldType(fieldName)); } return new MappingDefinition.Field(fieldName, fieldType, readonlyField, optionalField); } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BSemTypeWrapper.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BSemTypeWrapper.java index 4c1f5ed85391..a99cd93e6b21 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BSemTypeWrapper.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BSemTypeWrapper.java @@ -140,12 +140,14 @@ public boolean isAnydata() { @Override public boolean isPureType() { - Context cx = TypeChecker.context(); - return Core.isSubType(cx, this, Builder.getErrorType()) || isAnydata(); + return Core.isSubtypeSimple(this, Builder.getErrorType()) || isAnydata(); } @Override public boolean isReadOnly() { + if (Core.isSubtypeSimple(this, Builder.getInherentlyImmutable())) { + return true; + } Context cx = TypeChecker.context(); return Core.isSubType(cx, this, Builder.getReadonlyType()); } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BStreamType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BStreamType.java index 1382a3e29943..9c57d5cf47e3 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BStreamType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BStreamType.java @@ -25,9 +25,9 @@ import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.TypeTags; import io.ballerina.runtime.api.types.semtype.Builder; +import io.ballerina.runtime.api.types.semtype.Context; import io.ballerina.runtime.api.types.semtype.Env; import io.ballerina.runtime.api.types.semtype.SemType; -import io.ballerina.runtime.internal.TypeChecker; import io.ballerina.runtime.internal.types.semtype.DefinitionContainer; import io.ballerina.runtime.internal.types.semtype.StreamDefinition; import io.ballerina.runtime.internal.values.StreamValue; @@ -145,11 +145,11 @@ public boolean equals(Object obj) { } @Override - public SemType createSemType() { + public SemType createSemType(Context cx) { if (constraint == null) { return Builder.getStreamType(); } - Env env = TypeChecker.context().env; + Env env = cx.env; if (definition.isDefinitionReady()) { return definition.getSemType(env); } @@ -158,7 +158,7 @@ public SemType createSemType() { return definition.getSemType(env); } StreamDefinition sd = result.definition(); - return sd.define(env, tryInto(constraint), tryInto(completionType)); + return sd.define(env, tryInto(cx, constraint), tryInto(cx, completionType)); } @Override diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTableType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTableType.java index df743c54c344..45881c896dcb 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTableType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTableType.java @@ -28,7 +28,6 @@ import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.api.types.semtype.ShapeAnalyzer; import io.ballerina.runtime.api.values.BTable; -import io.ballerina.runtime.internal.TypeChecker; import io.ballerina.runtime.internal.types.semtype.TableUtils; import io.ballerina.runtime.internal.values.ReadOnlyUtils; import io.ballerina.runtime.internal.values.TableValue; @@ -173,17 +172,16 @@ public boolean isAnydata() { } @Override - public SemType createSemType() { - return createSemTypeWithConstraint(tryInto(constraint)); + public SemType createSemType(Context cx) { + return createSemTypeWithConstraint(cx, tryInto(cx, constraint)); } - private SemType createSemTypeWithConstraint(SemType constraintType) { + private SemType createSemTypeWithConstraint(Context cx, SemType constraintType) { SemType semType; - Context cx = TypeChecker.context(); if (fieldNames.length > 0) { semType = TableUtils.tableContainingKeySpecifier(cx, constraintType, fieldNames); } else if (keyType != null) { - semType = TableUtils.tableContainingKeyConstraint(cx, constraintType, tryInto(keyType)); + semType = TableUtils.tableContainingKeyConstraint(cx, constraintType, tryInto(cx, keyType)); } else { semType = TableUtils.tableContaining(cx.env, constraintType); } @@ -197,7 +195,7 @@ private SemType createSemTypeWithConstraint(SemType constraintType) { @Override public Optional inherentTypeOf(Context cx, ShapeSupplier shapeSupplier, Object object) { if (!couldInherentTypeBeDifferent()) { - return Optional.of(getSemType()); + return Optional.of(getSemType(cx)); } BTable table = (BTable) object; SemType cachedShape = table.shapeOf(); @@ -236,10 +234,10 @@ public Optional acceptedTypeOf(Context cx) { private SemType valueShape(Context cx, ShapeSupplier shapeSupplier, BTable table) { SemType constraintType = Builder.getNeverType(); for (var value : table.values()) { - SemType valueShape = shapeSupplier.get(cx, value).orElse(SemType.tryInto(constraint)); + SemType valueShape = shapeSupplier.get(cx, value).orElse(SemType.tryInto(cx, constraint)); constraintType = Core.union(constraintType, valueShape); } - return createSemTypeWithConstraint(constraintType); + return createSemTypeWithConstraint(cx, constraintType); } @Override diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTupleType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTupleType.java index ccda11d6f298..52b7a9289e49 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTupleType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTupleType.java @@ -41,7 +41,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.function.Function; +import java.util.function.BiFunction; import java.util.stream.Collectors; import static io.ballerina.runtime.api.types.semtype.Builder.getNeverType; @@ -331,8 +331,8 @@ public String getAnnotationKey() { } @Override - public SemType createSemType() { - Env env = Env.getInstance(); + public SemType createSemType(Context cx) { + Env env = cx.env; if (defn.isDefinitionReady()) { return defn.getSemType(env); } @@ -341,24 +341,26 @@ public SemType createSemType() { return defn.getSemType(env); } ListDefinition ld = result.definition(); - return createSemTypeInner(env, ld, SemType::tryInto, mut()); + return createSemTypeInner(cx, ld, SemType::tryInto, mut()); } private CellAtomicType.CellMutability mut() { return isReadOnly() ? CELL_MUT_NONE : CellAtomicType.CellMutability.CELL_MUT_LIMITED; } - private SemType createSemTypeInner(Env env, ListDefinition ld, Function semTypeFunction, + private SemType createSemTypeInner(Context cx, ListDefinition ld, + BiFunction semTypeFunction, CellAtomicType.CellMutability mut) { + Env env = cx.env; SemType[] memberTypes = new SemType[tupleTypes.size()]; for (int i = 0; i < tupleTypes.size(); i++) { - SemType memberType = semTypeFunction.apply(tupleTypes.get(i)); + SemType memberType = semTypeFunction.apply(cx, tupleTypes.get(i)); if (Core.isNever(memberType)) { return getNeverType(); } memberTypes[i] = memberType; } - SemType rest = restType != null ? semTypeFunction.apply(restType) : getNeverType(); + SemType rest = restType != null ? semTypeFunction.apply(cx, restType) : getNeverType(); return ld.defineListTypeWrapped(env, memberTypes, memberTypes.length, rest, mut); } @@ -377,7 +379,7 @@ protected boolean isDependentlyTypedInner(Set visited) { @Override public Optional inherentTypeOf(Context cx, ShapeSupplier shapeSupplier, Object object) { if (!couldInherentTypeBeDifferent()) { - return Optional.of(getSemType()); + return Optional.of(getSemType(cx)); } AbstractArrayValue value = (AbstractArrayValue) object; SemType cachedShape = value.shapeOf(); @@ -410,7 +412,8 @@ public Optional acceptedTypeOf(Context cx) { return Optional.of(acceptedTypeDefn.getSemType(env)); } ListDefinition ld = result.definition(); - return Optional.of(createSemTypeInner(env, ld, (type) -> ShapeAnalyzer.acceptedTypeOf(cx, type).orElseThrow(), + return Optional.of(createSemTypeInner(cx, ld, + (context, type) -> ShapeAnalyzer.acceptedTypeOf(context, type).orElseThrow(), CELL_MUT_UNLIMITED)); } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BType.java index 1fdd10c91847..86d7bc34f729 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BType.java @@ -248,23 +248,28 @@ public Type getCachedImpliedType() { } @Override - public SemType createSemType() { + public SemType createSemType(Context cx) { throw new IllegalStateException("Child that are used for type checking must implement this method"); } @Override - public void updateInnerSemTypeIfNeeded() { - synchronized (this) { - if (cachedSemType == null) { - cachedSemType = createSemType(); + public void updateInnerSemTypeIfNeeded(Context cx) { + if (cachedSemType == null) { + try { + cx.enterTypeResolutionPhase(this); + cachedSemType = createSemType(cx); setAll(cachedSemType.all()); setSome(cachedSemType.some(), cachedSemType.subTypeData()); + cx.exitTypeResolutionPhase(); + } catch (InterruptedException e) { + cx.exitTypeResolutionPhaseAbruptly(e); + throw new RuntimeException(e); } } } - protected SemType getSemType() { - updateInnerSemTypeIfNeeded(); + protected SemType getSemType(Context cx) { + updateInnerSemTypeIfNeeded(cx); return cachedSemType; } @@ -302,7 +307,7 @@ public final Optional cachedTypeCheckResult(Context cx, CacheableTypeDe } } - private synchronized void initializeCacheIfNeeded(Context cx) { + private void initializeCacheIfNeeded(Context cx) { typeCacheLock.readLock().lock(); boolean shouldInitialize = typeCheckCache == null; typeCacheLock.readLock().unlock(); diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTypeReferenceType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTypeReferenceType.java index 2e6384699111..c78429dd0124 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTypeReferenceType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTypeReferenceType.java @@ -132,9 +132,9 @@ public void setIntersectionType(IntersectionType intersectionType) { } @Override - public SemType createSemType() { + public SemType createSemType(Context cx) { Type referredType = getReferredType(); - return tryInto(referredType); + return tryInto(cx, referredType); } @Override @@ -145,7 +145,7 @@ protected boolean isDependentlyTypedInner(Set visited) { @Override public Optional inherentTypeOf(Context cx, ShapeSupplier shapeSupplier, Object object) { if (!couldInherentTypeBeDifferent()) { - return Optional.of(getSemType()); + return Optional.of(getSemType(cx)); } Type referredType = getReferredType(); if (referredType instanceof TypeWithShape typeWithShape) { diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTypedescType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTypedescType.java index da696b1bf335..fbda372b2985 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTypedescType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BTypedescType.java @@ -27,7 +27,6 @@ import io.ballerina.runtime.api.types.semtype.Builder; import io.ballerina.runtime.api.types.semtype.Context; import io.ballerina.runtime.api.types.semtype.SemType; -import io.ballerina.runtime.internal.TypeChecker; import io.ballerina.runtime.internal.types.semtype.TypedescUtils; import io.ballerina.runtime.internal.values.TypedescValue; import io.ballerina.runtime.internal.values.TypedescValueImpl; @@ -95,12 +94,11 @@ public String toString() { } @Override - public SemType createSemType() { + public SemType createSemType(Context cx) { if (constraint == null) { return Builder.getTypeDescType(); } - SemType constraint = tryInto(getConstraint()); - Context cx = TypeChecker.context(); + SemType constraint = tryInto(cx, getConstraint()); return TypedescUtils.typedescContaining(cx.env, constraint); } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BUnionType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BUnionType.java index bdd97fe67536..107d3eb2f0d0 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BUnionType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BUnionType.java @@ -553,8 +553,8 @@ public void setIntersectionType(IntersectionType intersectionType) { } @Override - public SemType createSemType() { - return memberTypes.stream().map(SemType::tryInto).reduce(Builder.getNeverType(), Core::union); + public SemType createSemType(Context cx) { + return memberTypes.stream().map(type -> SemType.tryInto(cx, type)).reduce(Builder.getNeverType(), Core::union); } @Override diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BXmlType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BXmlType.java index 45dd60c0ad10..aba39af0a1d3 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BXmlType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/BXmlType.java @@ -156,16 +156,16 @@ public Optional getIntersectionType() { } @Override - public SemType createSemType() { + public SemType createSemType(Context cx) { SemType semType; if (constraint == null) { semType = pickTopType(); } else { SemType contraintSemtype; if (constraint instanceof ParameterizedType parameterizedType) { - contraintSemtype = tryInto(parameterizedType.getParamValueType()); + contraintSemtype = tryInto(cx, parameterizedType.getParamValueType()); } else { - contraintSemtype = tryInto(constraint); + contraintSemtype = tryInto(cx, constraint); } semType = XmlUtils.xmlSequence(contraintSemtype); } @@ -192,7 +192,7 @@ public void setIntersectionType(IntersectionType intersectionType) { public Optional inherentTypeOf(Context cx, ShapeSupplier shapeSupplier, Object object) { XmlValue xmlValue = (XmlValue) object; if (!isReadOnly(xmlValue)) { - return Optional.of(getSemType()); + return Optional.of(getSemType(cx)); } return readonlyShapeOf(object); } @@ -209,7 +209,7 @@ public Optional shapeOf(Context cx, ShapeSupplier shapeSupplierFn, Obje @Override public Optional acceptedTypeOf(Context cx) { - return Optional.of(getSemType()); + return Optional.of(getSemType(cx)); } private Optional readonlyShapeOf(Object object) { diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/DefinitionContainer.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/DefinitionContainer.java index 2b4373a2de5e..66e443419554 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/DefinitionContainer.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/DefinitionContainer.java @@ -4,63 +4,43 @@ import io.ballerina.runtime.api.types.semtype.Env; import io.ballerina.runtime.api.types.semtype.SemType; -import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Supplier; /** - * Container used to maintain concurrency invariants when creating a potentially recursive semtype. - * - * It maintains fallowing invariants. - * 1. When the type is being defined only the thread that is defining the type may proceed - * 2. After definition is completed any number of threads may proceed concurrently In order to achieve this container - * has three phases (init, defining, defined). At init phase (that is no definition has been set) any number of threads - * may proceed concurrently. When a thread sets a definition {@code setDefinition} container enters the defining phase. - * In that phase only that thread may continue in {@code getSemType} method (this is to allow for recursive type - * definitions). Container registers with the {@code Definition} using {@code registerContainer} method. When the - * {@code Definition} has been defined (ie. {@code Env} has an atom corresponding to the definition) it must notify the - * container using {@code definitionUpdated} method. At this point container moves to defined phase allowing concurrent - * access to {@code getSemType}. + * Container used to maintain concurrent safety when creating {@code Definitions}. * * @param type of the definition * @since 2201.11.0 */ public class DefinitionContainer { - private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); private volatile E definition; - private final ReentrantLock recTypeLock = new ReentrantLock(); - private volatile boolean isDefining = false; + private final ReentrantLock lock = new ReentrantLock(); public boolean isDefinitionReady() { try { - rwLock.readLock().lock(); + lock.lock(); return definition != null; } finally { - rwLock.readLock().unlock(); + lock.unlock(); } } + /** + * Get the semtype of the definition. Must call {@code isDefinitionReady} before calling this method. + * + * @param env {@code Env} in which type is defined at + * @return recursive semtype representing the type + */ public SemType getSemType(Env env) { - try { - rwLock.readLock().lock(); - // We don't need this check to be synchronized since {@code trySetDefinition} will hold the write lock until - // it completes, So isDefining should always be at a consistent state - if (isDefining) { - // This should prevent threads other than the defining thread to access the rec atom. - recTypeLock.lock(); - } - return definition.getSemType(env); - } finally { - rwLock.readLock().unlock(); - } + return definition.getSemType(env); } public DefinitionUpdateResult trySetDefinition(Supplier supplier) { try { - rwLock.writeLock().lock(); + lock.lock(); boolean updated; E newDefinition; if (this.definition != null) { @@ -69,33 +49,23 @@ public DefinitionUpdateResult trySetDefinition(Supplier supplier) { } else { updated = true; newDefinition = supplier.get(); - newDefinition.registerContainer(this); - this.recTypeLock.lock(); - isDefining = true; this.definition = newDefinition; } return new DefinitionUpdateResult<>(newDefinition, updated); } finally { - rwLock.writeLock().unlock(); + lock.unlock(); } } public void clear() { try { - rwLock.writeLock().lock(); - // This shouldn't happen because defining thread should hold the lock. - assert !isDefining; + lock.lock(); this.definition = null; } finally { - rwLock.writeLock().unlock(); + lock.unlock(); } } - public void definitionUpdated() { - recTypeLock.unlock(); - isDefining = false; - } - /** * Result of trying to update the definition. * diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/FunctionDefinition.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/FunctionDefinition.java index 4878cbe1f717..25215726ffe9 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/FunctionDefinition.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/FunctionDefinition.java @@ -27,6 +27,9 @@ import io.ballerina.runtime.api.types.semtype.RecAtom; import io.ballerina.runtime.api.types.semtype.SemType; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + /** * {@code Definition} used to create function subtypes. * @@ -36,15 +39,21 @@ public class FunctionDefinition extends Definition { private volatile RecAtom rec; private volatile SemType semType; + private final Lock lock = new ReentrantLock(); @Override public SemType getSemType(Env env) { - if (this.semType != null) { - return this.semType; - } else { - RecAtom rec = env.recFunctionAtom(); - this.rec = rec; - return this.createSemType(rec); + try { + this.lock.lock(); + if (this.semType != null) { + return this.semType; + } else { + RecAtom rec = env.recFunctionAtom(); + this.rec = rec; + return this.createSemType(rec); + } + } finally { + this.lock.unlock(); } } @@ -57,17 +66,20 @@ private SemType createSemType(Atom atom) { public SemType define(Env env, SemType args, SemType ret, FunctionQualifiers qualifiers) { FunctionAtomicType atomicType = new FunctionAtomicType(args, ret, qualifiers.toSemType(env)); - RecAtom rec = this.rec; - Atom atom; - if (rec != null) { - atom = rec; - env.setRecFunctionAtomType(rec, atomicType); - } else { - atom = env.functionAtom(atomicType); + try { + lock.lock(); + RecAtom rec = this.rec; + Atom atom; + if (rec != null) { + atom = rec; + env.setRecFunctionAtomType(rec, atomicType); + } else { + atom = env.functionAtom(atomicType); + } + return this.createSemType(atom); + } finally { + lock.unlock(); } - SemType semType = this.createSemType(atom); - notifyContainer(); - return semType; } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ListDefinition.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ListDefinition.java index 58bd4674453e..43030d1383a2 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ListDefinition.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ListDefinition.java @@ -27,6 +27,9 @@ import io.ballerina.runtime.api.types.semtype.RecAtom; import io.ballerina.runtime.api.types.semtype.SemType; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + import static io.ballerina.runtime.api.types.semtype.BddNode.bddAtom; import static io.ballerina.runtime.api.types.semtype.Builder.basicSubType; import static io.ballerina.runtime.api.types.semtype.Builder.getUndefType; @@ -43,16 +46,23 @@ public class ListDefinition extends Definition { private volatile RecAtom rec = null; private volatile SemType semType = null; + private final Lock lock = new ReentrantLock(); @Override public SemType getSemType(Env env) { - SemType s = this.semType; - if (s == null) { - RecAtom rec = env.recListAtom(); - this.rec = rec; - return this.createSemType(env, rec); + try { + this.lock.lock(); + SemType s = this.semType; + if (s == null) { + assert rec == null; + RecAtom rec = env.recListAtom(); + this.rec = rec; + return this.createSemType(env, rec); + } + return s; + } finally { + this.lock.unlock(); } - return s; } public SemType defineListTypeWrapped(Env env, SemType[] initial, int fixedLength, SemType rest, @@ -63,23 +73,26 @@ public SemType defineListTypeWrapped(Env env, SemType[] initial, int fixedLength } SemType restCell = Builder.getCellContaining(env, union(rest, getUndefType()), isNever(rest) ? CELL_MUT_NONE : mut); - SemType semType = define(env, initialCells, fixedLength, restCell); - notifyContainer(); - return semType; + return define(env, initialCells, fixedLength, restCell); } private SemType define(Env env, SemType[] initial, int fixedLength, SemType rest) { FixedLengthArray members = FixedLengthArray.normalized(initial, fixedLength); ListAtomicType atomicType = new ListAtomicType(members, rest); Atom atom; - RecAtom rec = this.rec; - if (rec != null) { - atom = rec; - env.setRecListAtomType(rec, atomicType); - } else { - atom = env.listAtom(atomicType); + try { + lock.lock(); + RecAtom rec = this.rec; + if (rec != null) { + atom = rec; + env.setRecListAtomType(rec, atomicType); + } else { + atom = env.listAtom(atomicType); + } + return this.createSemType(env, atom); + } finally { + lock.unlock(); } - return this.createSemType(env, atom); } private SemType createSemType(Env env, Atom atom) { diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/MappingDefinition.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/MappingDefinition.java index 5e9ae332e55b..415411845e0e 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/MappingDefinition.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/MappingDefinition.java @@ -30,6 +30,8 @@ import java.util.Arrays; import java.util.Comparator; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import static io.ballerina.runtime.api.types.semtype.BddNode.bddAtom; import static io.ballerina.runtime.api.types.semtype.Builder.basicSubType; @@ -46,16 +48,21 @@ public class MappingDefinition extends Definition { private volatile RecAtom rec = null; private volatile SemType semType = null; + private final Lock lock = new ReentrantLock(); @Override public SemType getSemType(Env env) { - SemType s = this.semType; - if (s == null) { + try { + lock.lock(); + if (this.semType != null) { + return this.semType; + } + assert this.rec == null; RecAtom rec = env.recMappingAtom(); this.rec = rec; return this.createSemType(env, rec); - } else { - return s; + } finally { + lock.unlock(); } } @@ -75,9 +82,7 @@ public SemType defineMappingTypeWrapped(Env env, Field[] fields, SemType rest, C } SemType restCell = Builder.getCellContaining(env, union(rest, getUndefType()), isNever(rest) ? CellAtomicType.CellMutability.CELL_MUT_NONE : mut); - SemType semType = define(env, cellFields, restCell); - notifyContainer(); - return semType; + return define(env, cellFields, restCell); } SemType define(Env env, BCellField[] cellFields, SemType rest) { @@ -86,14 +91,19 @@ SemType define(Env env, BCellField[] cellFields, SemType rest) { sortAndSplitFields(cellFields, names, types); MappingAtomicType atomicType = new MappingAtomicType(names, types, rest); Atom atom; - RecAtom rec = this.rec; - if (rec != null) { - atom = rec; - env.setRecMappingAtomType(rec, atomicType); - } else { - atom = env.mappingAtom(atomicType); + try { + lock.lock(); + RecAtom rec = this.rec; + if (rec != null) { + atom = rec; + env.setRecMappingAtomType(rec, atomicType); + } else { + atom = env.mappingAtom(atomicType); + } + return this.createSemType(env, atom); + } finally { + lock.unlock(); } - return this.createSemType(env, atom); } private void sortAndSplitFields(BCellField[] fields, String[] names, SemType[] types) { diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/MutableSemType.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/MutableSemType.java index 2059cb237766..e72a7c0e16ff 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/MutableSemType.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/MutableSemType.java @@ -18,6 +18,7 @@ package io.ballerina.runtime.internal.types.semtype; +import io.ballerina.runtime.api.types.semtype.Context; import io.ballerina.runtime.api.types.semtype.SemType; import io.ballerina.runtime.internal.types.BType; @@ -29,9 +30,9 @@ */ public sealed interface MutableSemType permits BType { - SemType createSemType(); + SemType createSemType(Context cx); void resetSemType(); - void updateInnerSemTypeIfNeeded(); + void updateInnerSemTypeIfNeeded(Context cx); } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ObjectDefinition.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ObjectDefinition.java index f843d04d43b2..4b04eda1f9b2 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ObjectDefinition.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/ObjectDefinition.java @@ -63,9 +63,7 @@ public SemType define(Env env, ObjectQualifiers qualifiers, List members SemType mappingType = mappingDefinition.define(env, Stream.concat(memberStream, qualifierStream) .map(field -> MappingDefinition.BCellField.from(env, field, mut)) .toArray(MappingDefinition.BCellField[]::new), restMemberType(env, mut, qualifiers.readonly())); - SemType semType = objectContaining(mappingType); - notifyContainer(); - return semType; + return objectContaining(mappingType); } private SemType objectContaining(SemType mappingType) { diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/StreamDefinition.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/StreamDefinition.java index 2023672f5848..1f4e9aab0a75 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/StreamDefinition.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/types/semtype/StreamDefinition.java @@ -50,9 +50,7 @@ public SemType define(Env env, SemType valueType, SemType completionType) { } SemType tuple = listDefinition.defineListTypeWrapped(env, new SemType[]{valueType, completionType}, 2, Builder.getNeverType(), CellAtomicType.CellMutability.CELL_MUT_LIMITED); - SemType semType = streamContaining(tuple); - notifyContainer(); - return semType; + return streamContaining(tuple); } private SemType streamContaining(SemType tupleType) { diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/utils/MapUtils.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/utils/MapUtils.java index 62ac70673626..e87581a4c04e 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/utils/MapUtils.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/utils/MapUtils.java @@ -118,7 +118,7 @@ public static boolean handleInherentTypeViolatingRecordUpdate( } private static boolean containsNilType(Type type) { - return Core.containsBasicType(SemType.tryInto(type), Builder.getNilType()); + return Core.containsBasicType(SemType.tryInto(TypeChecker.context(), type), Builder.getNilType()); } public static BError createOpNotSupportedError(Type type, String op) { diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/utils/ValueConverter.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/utils/ValueConverter.java index b610c8a18316..cc4bf3b308c9 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/utils/ValueConverter.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/utils/ValueConverter.java @@ -184,9 +184,9 @@ private static Object xmlSequenceHack(Object value, Type targetType) { } Context cx = TypeChecker.context(); List list = new ArrayList<>(); - SemType targetSemType = SemType.tryInto(targetType); + SemType targetSemType = SemType.tryInto(cx, targetType); for (BXml child : xmlSequence.getChildrenList()) { - SemType childType = SemType.tryInto(child.getType()); + SemType childType = SemType.tryInto(cx, child.getType()); boolean isReadonly = Core.isSubType(cx, Core.intersect(childType, targetSemType), Builder.getReadonlyType()); if (isReadonly) { diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/FPValue.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/FPValue.java index 6e116666a45b..d19ccb53fab3 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/FPValue.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/FPValue.java @@ -107,6 +107,6 @@ public String toString() { @Override public Optional inherentTypeOf(Context cx) { - return Optional.of(SemType.tryInto(getType())); + return Optional.of(SemType.tryInto(cx, getType())); } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpValue.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpValue.java index a00800f64b53..460871ea7083 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpValue.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpValue.java @@ -127,7 +127,7 @@ public boolean equals(Object o, Set visitedValues) { } @Override - public SemType widenedType() { + public SemType widenedType(Context cx) { return Builder.getRegexType(); }