All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.qbicc.facts.Facts Maven / Gradle / Ivy

There is a newer version: 0.77.0
Show newest version
package org.qbicc.facts;

import java.lang.invoke.ConstantBootstraps;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

import org.eclipse.collections.api.factory.Lists;
import org.eclipse.collections.api.factory.primitive.ObjectIntMaps;
import org.eclipse.collections.api.list.ImmutableList;
import org.eclipse.collections.api.map.primitive.ImmutableObjectIntMap;
import org.qbicc.context.AttachmentKey;
import org.qbicc.context.CompilationContext;
import org.qbicc.context.PhaseAttachmentKey;

/**
 * A central point for registering and tracking facts about types and members.
 */
public final class Facts {
    private static final VarHandle factIdsHandle = ConstantBootstraps.fieldVarHandle(MethodHandles.lookup(), "factIds", VarHandle.class, Facts.class, ImmutableObjectIntMap.class);
    private static final VarHandle longArrayHandle = ConstantBootstraps.arrayVarHandle(MethodHandles.lookup(), "_", VarHandle.class, long[].class);

    private final CompilationContext ctxt;

    private static final AttachmentKey KEY = new AttachmentKey<>();

    private static final PhaseAttachmentKey PER_PHASE_KEY = new PhaseAttachmentKey<>();

    @SuppressWarnings("FieldMayBeFinal") // VarHandle
    private volatile ImmutableObjectIntMap> factIds = ObjectIntMaps.immutable.empty();

    static final class PerPhase {
        // The array value contains two elements:
        //   [0] - bits representing discovered facts
        //   [1] - bits representing executed actions
        // It seems like it would be possible to deduce the actions from the old and new facts, however this doesn't cover late-added actions
        final Map facts = new ConcurrentHashMap<>();
        final Map, long[]> counts = new ConcurrentHashMap<>();
        volatile ImmutableList> actions = Lists.immutable.empty();

        PerPhase() {}
    }

    private Facts(CompilationContext ctxt) {
        this.ctxt = ctxt;
    }

    public static Facts get(CompilationContext ctxt) {
        Facts facts = ctxt.getAttachment(KEY);
        if (facts == null) {
            facts = new Facts(ctxt);
            Facts appearing = ctxt.putAttachmentIfAbsent(KEY, facts);
            if (appearing != null) {
                facts = appearing;
            }
        }
        return facts;
    }

    int getFactIndex(Fact fact) {
        int id;
        ImmutableObjectIntMap> oldMap, newMap;
        for (;;) {
            oldMap = factIds;
            id = oldMap.getIfAbsent(fact, -1);
            if (id != -1) {
                return id;
            }
            id = oldMap.size();
            // if we need more, we'll have to do a slight design adjustment in order to preserve atomicity across multi-word RMW
            if (id >= 64) {
                throw new IllegalStateException("Too many facts");
            }
            newMap = oldMap.newWithKeyValue(fact, id);
            if (factIdsHandle.compareAndSet(this, oldMap, newMap)) {
                return id;
            }
        }
    }

    private PerPhase getPerPhase() {
        PerPhase perPhase = ctxt.getAttachment(PER_PHASE_KEY);
        if (perPhase == null) {
            perPhase = new PerPhase();
            PerPhase appearing = ctxt.putAttachmentIfAbsent(PER_PHASE_KEY, perPhase);
            if (appearing != null) {
                perPhase = appearing;
            }
        }
        return perPhase;
    }

    private PerPhase getPreviousPerPhase() {
        return ctxt.getPreviousPhaseAttachment(PER_PHASE_KEY);
    }

    private void registerAction(long bits, BiConsumer consumer) {
        PerPhase perPhase = getPerPhase();
        synchronized (perPhase) {
            Action action = new Action<>(bits, consumer);
            ImmutableList> newActions = perPhase.actions.newWith(action);
            perPhase.actions = newActions;
            Map factsMap = perPhase.facts;
            if (! factsMap.isEmpty()) {
                // execute all newly-registered actions
                int actionBit = newActions.size() - 1;
                for (Map.Entry entry : factsMap.entrySet()) {
                    long[] array = entry.getValue();
                    long factBits = (long) longArrayHandle.getVolatile(array, 0);
                    if (action.isNewlySatisfiedBy(0, factBits)) {
                        long oldVal;
                        long newVal;
                        for (;;) {
                            oldVal = (long) longArrayHandle.getVolatile(array, 1);
                            newVal = oldVal | 1L << actionBit;
                            if (oldVal == newVal) {
                                break;
                            }
                            if (longArrayHandle.compareAndSet(array, 1, oldVal, newVal)) {
                                action.accept(entry.getKey());
                                break;
                            }
                        }
                    }
                }
            }
        }
    }

    private static long[] newArray(Object ignored) {
        // [0] = fact bits
        // [1] = action bits
        return new long[2];
    }

    private  long discover(E item, long factBits) {
        PerPhase perPhase = getPerPhase();
        Map facts = perPhase.facts;
        long[] array = facts.computeIfAbsent(item, Facts::newArray);
        long oldFacts, newFacts;
        do {
            oldFacts = (long) longArrayHandle.getVolatile(array, 0);
            if ((oldFacts & factBits) == factBits) {
                // already known
                return 0;
            }
            newFacts = oldFacts | factBits;
        } while (! longArrayHandle.compareAndSet(array, 0, oldFacts, newFacts));
        // todo: optimize by utilizing newFacts ^ oldFacts to find the actions that could be newly satisfied
        ImmutableList> actions = perPhase.actions;
        int size = actions.size();
        for (int i = 0; i < size; i ++) {
            Action action = actions.get(i);
            if (action.isNewlySatisfiedBy(oldFacts, newFacts)) {
                long oldVal;
                long newVal;
                for (;;) {
                    oldVal = (long) longArrayHandle.getVolatile(array, 1);
                    newVal = oldVal | 1L << i;
                    if (oldVal == newVal) {
                        break;
                    }
                    if (longArrayHandle.compareAndSet(array, 1, oldVal, newVal)) {
                        action.accept(item);
                        break;
                    }
                }
            }
        }
        return newFacts & ~oldFacts;
    }

    public  void discover(E item, Fact fact) {
        // type check
        fact.getElementType().cast(item);
        if (discover(item, 1L << getFactIndex(fact)) != 0) {
            longArrayHandle.getAndAdd(getPerPhase().counts.computeIfAbsent(fact, Facts::newArray), 0, 1L);
        }
    }

    public  void discover(E item, Fact fact1, Fact fact2) {
        // type check
        fact1.getElementType().cast(item);
        fact2.getElementType().cast(item);
        long bit1 = 1L << getFactIndex(fact1);
        long bit2 = 1L << getFactIndex(fact2);
        long newly = discover(item, bit1 | bit2);
        PerPhase perPhase = getPerPhase();
        if ((newly & bit1) != 0) {
            longArrayHandle.getAndAdd(perPhase.counts.computeIfAbsent(fact1, Facts::newArray), 0, 1L);
        }
        if ((newly & bit2) != 0) {
            longArrayHandle.getAndAdd(perPhase.counts.computeIfAbsent(fact2, Facts::newArray), 0, 1L);
        }
    }

    public  void discover(E item, Fact fact1, Fact fact2, Fact fact3) {
        // type check
        fact1.getElementType().cast(item);
        fact2.getElementType().cast(item);
        fact3.getElementType().cast(item);
        long bit1 = 1L << getFactIndex(fact1);
        long bit2 = 1L << getFactIndex(fact2);
        long bit3 = 1L << getFactIndex(fact3);
        long newly = discover(item, bit1 | bit2 | bit3);
        PerPhase perPhase = getPerPhase();
        if ((newly & bit1) != 0) {
            longArrayHandle.getAndAdd(perPhase.counts.computeIfAbsent(fact1, Facts::newArray), 0, 1L);
        }
        if ((newly & bit2) != 0) {
            longArrayHandle.getAndAdd(perPhase.counts.computeIfAbsent(fact2, Facts::newArray), 0, 1L);
        }
        if ((newly & bit3) != 0) {
            longArrayHandle.getAndAdd(perPhase.counts.computeIfAbsent(fact3, Facts::newArray), 0, 1L);
        }
    }

    public long getDiscoveredCount(Fact fact) {
        long[] array = getPerPhase().counts.get(fact);
        return array == null ? 0 : (long) longArrayHandle.getVolatile(array, 0);
    }

    public  boolean isDiscovered(final E item, final Fact fact) {
        long[] array = getPerPhase().facts.get(item);
        long factBits = 1L << getFactIndex(fact);
        return array != null && fact.getElementType().isInstance(item) && (array[0] & factBits) == factBits;
    }

    private  boolean hadAllFactBits(E item, long factBits) {
        PerPhase previous = getPreviousPerPhase();
        if (previous == null) {
            return false;
        }
        long[] array = previous.facts.get(item);
        return array != null && (array[0] & factBits) == factBits;
    }

    private  boolean hadAnyFactBits(E item, long factBits) {
        PerPhase previous = getPreviousPerPhase();
        if (previous == null) {
            return false;
        }
        long[] array = previous.facts.get(item);
        return array != null && (array[0] & factBits) != 0;
    }

    public  boolean hadFact(E item, Fact fact) {
        return fact.getElementType().isInstance(item) && hadAllFactBits(item, 1L << getFactIndex(fact));
    }

    public  boolean hadAllFacts(E item, Fact fact1, Fact fact2) {
        return fact1.getElementType().isInstance(item) && fact2.getElementType().isInstance(item) && hadAllFactBits(item, 1L << getFactIndex(fact1) | 1L << getFactIndex(fact2));
    }

    public  boolean hadAllFacts(E item, Fact fact1, Fact fact2, Fact fact3) {
        return fact1.getElementType().isInstance(item) && fact2.getElementType().isInstance(item) && fact3.getElementType().isInstance(item) && hadAllFactBits(item, 1L << getFactIndex(fact1) | 1L << getFactIndex(fact2) | 1L << getFactIndex(fact3));
    }

    public  boolean hadAnyFacts(E item, Fact fact1, Fact fact2) {
        return fact1.getElementType().isInstance(item) && fact2.getElementType().isInstance(item) && hadAnyFactBits(item, 1L << getFactIndex(fact1) | 1L << getFactIndex(fact2));
    }

    public  boolean hadAnyFacts(E item, Fact fact1, Fact fact2, Fact fact3) {
        return fact1.getElementType().isInstance(item) && fact2.getElementType().isInstance(item) && fact3.getElementType().isInstance(item) && hadAnyFactBits(item, 1L << getFactIndex(fact1) | 1L << getFactIndex(fact2) | 1L << getFactIndex(fact3));
    }

    public  void registerInlineAction(Condition> condition, BiConsumer action) {
        condition.getRegisterFunction((facts, bits) -> facts.registerAction(bits, action)).accept(this, 0);
    }

    public  void registerInlineAction(Condition> condition, Consumer action) {
        registerInlineAction(condition, (e, f) -> action.accept(e));
    }

    public  void registerAction(Condition> condition, BiConsumer action) {
        BiConsumer bc = (e, f) -> ctxt.submitTask(e, e2 -> action.accept(e2, f));
        condition.getRegisterFunction((facts, bits) -> facts.registerAction(bits, bc)).accept(this, 0);
    }

    public  void registerAction(Condition> condition, Consumer action) {
        registerAction(condition, (e, f) -> action.accept(e));
    }

    public CompilationContext getCompilationContext() {
        return ctxt;
    }

    public boolean hasPreviousFacts() {
        return ctxt.getPreviousPhaseAttachment(PER_PHASE_KEY) != null;
    }

    final class Action implements Consumer {
        private final long requiredBits;
        private final BiConsumer consumer;

        Action(long requiredBits, BiConsumer consumer) {
            this.requiredBits = requiredBits;
            this.consumer = consumer;
        }

        boolean isNewlySatisfiedBy(long oldBits, long newBits) {
            long requiredBits = this.requiredBits;
            return (oldBits & requiredBits) != requiredBits && (newBits & requiredBits) == requiredBits;
        }

        @SuppressWarnings("unchecked")
        public void accept(final Object item) {
            consumer.accept((K) item, Facts.this);
        }
    }
}