com.wrmsr.wava.analyze.ValueTypeAnalysis.java Source code

Java tutorial

Introduction

Here is the source code for com.wrmsr.wava.analyze.ValueTypeAnalysis.java

Source

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.wrmsr.wava.analyze;

import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.wrmsr.wava.core.node.Binary;
import com.wrmsr.wava.core.node.Block;
import com.wrmsr.wava.core.node.Break;
import com.wrmsr.wava.core.node.BreakTable;
import com.wrmsr.wava.core.node.Call;
import com.wrmsr.wava.core.node.CallIndirect;
import com.wrmsr.wava.core.node.Const;
import com.wrmsr.wava.core.node.GetLocal;
import com.wrmsr.wava.core.node.If;
import com.wrmsr.wava.core.node.Label;
import com.wrmsr.wava.core.node.Load;
import com.wrmsr.wava.core.node.Loop;
import com.wrmsr.wava.core.node.Node;
import com.wrmsr.wava.core.node.Nop;
import com.wrmsr.wava.core.node.Return;
import com.wrmsr.wava.core.node.Select;
import com.wrmsr.wava.core.node.SetLocal;
import com.wrmsr.wava.core.node.Store;
import com.wrmsr.wava.core.node.Switch;
import com.wrmsr.wava.core.node.Unary;
import com.wrmsr.wava.core.node.Unreachable;
import com.wrmsr.wava.core.node.visitor.Visitor;
import com.wrmsr.wava.core.type.Name;
import com.wrmsr.wava.core.type.Type;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.organicdesign.fp.collections.ImMap;
import org.organicdesign.fp.collections.PersistentHashMap;

import javax.annotation.concurrent.Immutable;

import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Maps.newIdentityHashMap;
import static com.google.common.collect.Sets.immutableEnumSet;
import static com.wrmsr.wava.util.collect.MoreCollectors.toImmutableList;
import static com.wrmsr.wava.util.collect.MoreLists.listInit;
import static com.wrmsr.wava.util.collect.MoreLists.listLast;
import static java.util.Collections.unmodifiableMap;
import static java.util.Objects.requireNonNull;

/*
TODO:
 - Types.UNREACHABLE may appear anywhere, are considered any type, and may be used. :||||
*/

@Immutable
public final class ValueTypeAnalysis {
    public static final ImMap<Name, Set<Type>> EMPTY_BREAK_VALUE_TYPES = PersistentHashMap.empty();

    public enum Initialization {
        INLINE, // value, no statements :: => 5
        SETUP, // value, statements    :: { x = 4; } => 5
        VOID; // no value, statements :: { break l; } =>
    }

    public enum Ownership {
        FREE, // no temps      :: (const 5)
        TEMP, // new temp      :: (label a (break a 5))
        INHERITED, // modified temp :: (+ (label a (break a 5)) 5)
        VOID; // no value      :: (return)
    }

    public static final class Entry {
        private final Type type;
        private final Initialization initialization;
        private final Ownership ownership;
        private final ImMap<Name, Set<Type>> breakValueTypes;

        private final boolean isUsed;

        private Entry(Type type, Initialization initialization, Ownership ownership,
                ImMap<Name, Set<Type>> breakValueTypes, boolean isUsed) {
            this.type = requireNonNull(type);
            this.initialization = requireNonNull(initialization);
            this.ownership = requireNonNull(ownership);
            this.breakValueTypes = requireNonNull(breakValueTypes);
            this.isUsed = isUsed;
            // note that type=NONE with isUsed=true is legal (breaks/returns with nop values) so long as nothing attempts to.. um.. ~use~ the value
            // checkArgument((type == Type.NONE) == (initialization == Initialization.VOID));
            // checkArgument((type == Type.NONE) == (ownership == Ownership.VOID));
        }

        private Entry(Type type, Initialization initialization, Ownership ownership,
                ImMap<Name, Set<Type>> breakValueTypes, Context context) {
            this(type, initialization, ownership, breakValueTypes, context.isUsed);
        }

        @Override
        public String toString() {
            return MoreObjects.toStringHelper(this).add("type", type).add("initialization", initialization)
                    .add("ownership", ownership).add("breakValueTypes", breakValueTypes).add("isUsed", isUsed)
                    .toString();
        }

        public Type getType() {
            return type;
        }

        public Initialization getInitialization() {
            return initialization;
        }

        public Ownership getOwnership() {
            return ownership;
        }

        public boolean isUsed() {
            return isUsed;
        }

        public ImMap<Name, Set<Type>> getBreakValueTypes() {
            return breakValueTypes;
        }
    }

    private final Map<Node, Entry> entries;

    private ValueTypeAnalysis(Map<Node, Entry> entries) {
        this.entries = unmodifiableMap(entries);
    }

    public Entry get(Node node) {
        return requireNonNull(entries.get(node));
    }

    public Map<Node, Entry> getEntries() {
        return entries;
    }

    private static final class Context {
        private final boolean isUsed;

        public Context(boolean isUsed) {
            this.isUsed = isUsed;
        }
    }

    private static ImMap<Name, Set<Type>> updateBreakValueTypes(ImMap<Name, Set<Type>> map, List<Name> targets,
            Type type) {
        for (Name target : targets) {
            map = map.assoc(target,
                    immutableEnumSet(Sets.union(map.getOrElse(target, ImmutableSet.of()), ImmutableSet.of(type))));
        }
        return map;
    }

    private static ImMap<Name, Set<Type>> mergeBreakValueTypes(Iterator<ImMap<Name, Set<Type>>> it) {
        ImMap<Name, Set<Type>> ret = EMPTY_BREAK_VALUE_TYPES;
        while (it.hasNext()) {
            ImMap<Name, Set<Type>> cur = it.next();
            for (Map.Entry<Name, Set<Type>> entry : cur.entrySet()) {
                ret = ret.assoc(entry.getKey(), immutableEnumSet(
                        Sets.union(cur.getOrElse(entry.getKey(), ImmutableSet.of()), entry.getValue())));
            }
        }
        return ret;
    }

    private static ImMap<Name, Set<Type>> mergeBreakValueTypes(Iterable<ImMap<Name, Set<Type>>> items) {
        return mergeBreakValueTypes(items.iterator());
    }

    private static ImMap<Name, Set<Type>> mergeBreakValueTypes(ImMap<Name, Set<Type>> left,
            ImMap<Name, Set<Type>> right) {
        for (Map.Entry<Name, Set<Type>> entry : right.entrySet()) {
            left = left.assoc(entry.getKey(), immutableEnumSet(
                    Sets.union(right.getOrElse(entry.getKey(), ImmutableSet.of()), entry.getValue())));
        }
        return left;
    }

    private static Initialization mergeValueInitializations(Initialization... is) {
        for (Initialization i : is) {
            checkArgument(i != Initialization.VOID);
            if (i != Initialization.INLINE) {
                checkState(i == Initialization.SETUP);
                return i;
            }
        }
        return Initialization.INLINE;
    }

    private static Ownership mergeValueOwnerships(Ownership... os) {
        for (Ownership o : os) {
            checkArgument(o != Ownership.VOID);
            if (o == Ownership.TEMP || o == Ownership.INHERITED) {
                return Ownership.INHERITED;
            }
            checkArgument(o == Ownership.FREE);
        }
        return Ownership.FREE;
    }

    public static ValueTypeAnalysis analyze(Node root, boolean isRootUsed) {
        Map<Node, Entry> analyses = newIdentityHashMap();
        Entry rootAnalysis = root.accept(new Visitor<Context, Entry>() {
            private Entry analyzeChild(Node child, Context context) {
                Entry analysis = child.accept(this, context);
                analyses.put(child, analysis);
                return analysis;
            }

            @Override
            protected Entry visitNode(Node node, Context context) {
                throw new IllegalStateException();
            }

            @Override
            public Entry visitBinary(Binary node, Context context) {
                Entry left = analyzeChild(node.getLeft(), new Context(true));
                Entry right = analyzeChild(node.getRight(), new Context(true));
                checkState(left.getType() == right.getType());
                checkState(left.getType().isConcrete());
                Type resultType = requireNonNull(
                        node.getOp().getTypeMap().get(ImmutablePair.of(left.getType(), right.getType())));
                checkState(node.getType() == resultType);
                return new Entry(node.getType(),
                        mergeValueInitializations(left.getInitialization(), right.getInitialization()),
                        mergeValueOwnerships(left.getOwnership(), right.getOwnership()),
                        mergeBreakValueTypes(left.getBreakValueTypes(), right.getBreakValueTypes()), context);
            }

            @Override
            public Entry visitBlock(Block node, Context context) {
                List<Node> children = node.getChildren();
                List<Entry> init = listInit(children).map(c -> analyzeChild(c, new Context(false)))
                        .collect(toImmutableList());
                Entry last = analyzeChild(listLast(children), new Context(context.isUsed));
                ImMap<Name, Set<Type>> breakValueTypes = Stream.concat(init.stream(), Stream.of(last))
                        .map(Entry::getBreakValueTypes)
                        .reduce(EMPTY_BREAK_VALUE_TYPES, ValueTypeAnalysis::mergeBreakValueTypes);
                return new Entry(last.getType(),
                        init.isEmpty() && last.getInitialization() == Initialization.INLINE ? Initialization.INLINE
                                : !last.getType().isConcrete() ? Initialization.VOID : Initialization.SETUP,
                        last.getOwnership(), breakValueTypes, context);
            }

            @Override
            public Entry visitBreak(Break node, Context context) {
                // checkState(!context.isUsed);
                Entry value = analyzeChild(node.getValue(), new Context(true));
                return new Entry(Type.NONE, Initialization.VOID, Ownership.VOID, updateBreakValueTypes(
                        value.getBreakValueTypes(), ImmutableList.of(node.getTarget()), value.getType()), context);
            }

            @Override
            public Entry visitBreakTable(BreakTable node, Context context) {
                // checkState(!context.isUsed);
                Entry condition = analyzeChild(node.getCondition(), new Context(true));
                return new Entry(Type.NONE, Initialization.VOID, Ownership.VOID,
                        updateBreakValueTypes(condition.getBreakValueTypes(), ImmutableList.<Name>builder()
                                .addAll(node.getTargets()).add(node.getDefaultTarget()).build(), Type.NONE),
                        context);
            }

            @Override
            public Entry visitCall(Call node, Context context) {
                // TODO: check operands?
                Call.Target target = node.getTarget();
                Type type = node.getSignature().getResult();
                requireNonNull(type);
                List<Entry> operands = node.getOperands().stream().map(o -> analyzeChild(o, new Context(true)))
                        .collect(toImmutableList());
                checkState(operands.stream().allMatch(o -> o.getType().isConcrete()));
                return new Entry(type,
                        mergeValueInitializations(operands.stream().map(Entry::getInitialization)
                                .collect(toImmutableList()).toArray(new Initialization[] {})),
                        Ownership.FREE, operands.stream().map(Entry::getBreakValueTypes)
                                .reduce(EMPTY_BREAK_VALUE_TYPES, ValueTypeAnalysis::mergeBreakValueTypes),
                        context);
            }

            @Override
            public Entry visitCallIndirect(CallIndirect node, Context context) {
                Entry target = analyzeChild(node.getTarget(), new Context(true));
                checkState(target.getType().isConcrete());
                List<Entry> operands = node.getOperands().stream().map(o -> analyzeChild(o, new Context(true)))
                        .collect(toImmutableList());
                checkState(operands.stream().allMatch(o -> o.getType().isConcrete()));
                return new Entry(node.getSignature().getResult(),
                        mergeValueInitializations(
                                Stream.concat(Stream.of(target), operands.stream()).map(Entry::getInitialization)
                                        .collect(toImmutableList()).toArray(new Initialization[] {})),
                        Ownership.FREE,
                        Stream.concat(Stream.of(target), operands.stream()).map(Entry::getBreakValueTypes)
                                .reduce(EMPTY_BREAK_VALUE_TYPES, ValueTypeAnalysis::mergeBreakValueTypes),
                        context);
            }

            @Override
            public Entry visitConst(Const node, Context context) {
                return new Entry(node.getLiteral().getType(), Initialization.INLINE, Ownership.FREE,
                        EMPTY_BREAK_VALUE_TYPES, context);
            }

            @Override
            public Entry visitGetLocal(GetLocal node, Context context) {
                return new Entry(node.getType(), Initialization.INLINE, Ownership.FREE, EMPTY_BREAK_VALUE_TYPES,
                        context);
            }

            @Override
            public Entry visitIf(If node, Context context) {
                Entry condition = analyzeChild(node.getCondition(), new Context(true));
                Entry ifTrue = analyzeChild(node.getIfTrue(), new Context(context.isUsed));
                Entry ifFalse = analyzeChild(node.getIfFalse(), new Context(context.isUsed));
                checkState(condition.getType().isConcrete());
                ImMap<Name, Set<Type>> breakValueTypes = Stream.of(condition, ifTrue, ifFalse)
                        .map(Entry::getBreakValueTypes)
                        .reduce(EMPTY_BREAK_VALUE_TYPES, ValueTypeAnalysis::mergeBreakValueTypes);
                if (context.isUsed) {
                    checkState(ifTrue.getType() == ifFalse.getType());
                    checkState(ifTrue.getType().isConcrete());
                    return new Entry(ifTrue.getType(),
                            mergeValueInitializations(condition.getInitialization(), ifTrue.getInitialization(),
                                    ifFalse.getInitialization()),
                            mergeValueOwnerships(ifTrue.getOwnership(), ifFalse.getOwnership()), breakValueTypes,
                            context);
                } else {
                    return new Entry(Type.NONE, Initialization.VOID, Ownership.VOID, breakValueTypes, context);
                }
            }

            @Override
            public Entry visitLabel(Label node, Context context) {
                // FIXME CFA -> ignore unreachable tails? or expect removed?
                Entry body = analyzeChild(node.getBody(), new Context(context.isUsed));
                ImMap<Name, Set<Type>> breakValueTypes = body.getBreakValueTypes();
                Type type = body.getType();
                Initialization initialization = body.getInitialization();
                Ownership ownership = body.getOwnership();
                if (breakValueTypes.containsKey(node.getName())) {
                    Type breakType = Iterables.getOnlyElement(breakValueTypes.get(node.getName()));
                    if (breakType.isConcrete()) {
                        checkState(type == breakType || type == Type.NONE);
                        type = breakType;
                        initialization = Initialization.SETUP;
                        ownership = Ownership.TEMP;
                    } else {
                        if (context.isUsed) {
                            checkState(type == Type.NONE);
                        } else {
                            type = Type.NONE;
                            initialization = Initialization.VOID;
                            ownership = Ownership.VOID;
                        }
                    }
                    breakValueTypes = breakValueTypes.without(node.getName());
                }
                return new Entry(type, initialization, ownership, breakValueTypes, context);
            }

            @Override
            public Entry visitLoad(Load node, Context context) {
                Entry ptr = analyzeChild(node.getPtr(), new Context(true));
                checkState(ptr.getType() == Type.I32);
                return new Entry(node.getType(), ptr.getInitialization(), Ownership.FREE, ptr.getBreakValueTypes(),
                        context);
            }

            @Override
            public Entry visitLoop(Loop node, Context context) {
                Entry body = analyzeChild(node.getBody(), new Context(context.isUsed));
                ImMap<Name, Set<Type>> breakValueTypes = body.getBreakValueTypes();
                if (breakValueTypes.containsKey(node.getName())) {
                    checkState(breakValueTypes.get(node.getName()).equals(EnumSet.of(Type.NONE)));
                    breakValueTypes = breakValueTypes.without(node.getName());
                }
                return new Entry(body.getType(), body.getInitialization(), body.getOwnership(), breakValueTypes,
                        context);
            }

            @Override
            public Entry visitNop(Nop node, Context context) {
                return new Entry(Type.NONE, Initialization.VOID, Ownership.VOID, EMPTY_BREAK_VALUE_TYPES, context);
            }

            @Override
            public Entry visitReturn(Return node, Context context) {
                checkState(!context.isUsed);
                Entry value = analyzeChild(node.getValue(), new Context(true));
                return new Entry(Type.NONE, Initialization.VOID, Ownership.VOID, value.getBreakValueTypes(),
                        context);
            }

            @Override
            public Entry visitSelect(Select node, Context context) {
                Entry ifTrue = analyzeChild(node.getIfTrue(), new Context(true));
                Entry ifFalse = analyzeChild(node.getIfFalse(), new Context(true));
                Entry condition = analyzeChild(node.getCondition(), new Context(true));
                checkState(ifTrue.getType() == ifFalse.getType());
                checkState(ifTrue.getType().isConcrete());
                checkState(condition.getType() == Type.I32);
                return new Entry(ifTrue.getType(), Initialization.SETUP, // FIXME? used check?
                        Ownership.TEMP, mergeBreakValueTypes(ImmutableList.of(ifTrue.getBreakValueTypes(),
                                ifFalse.getBreakValueTypes(), condition.getBreakValueTypes())),
                        context);
            }

            @Override
            public Entry visitSetLocal(SetLocal node, Context context) {
                Entry value = analyzeChild(node.getValue(), new Context(true));
                checkState(value.getType().isConcrete());
                checkState(value.getType() == node.getType());
                return new Entry(value.getType(), value.getInitialization(), value.getOwnership(),
                        value.getBreakValueTypes(), context);
            }

            @Override
            public Entry visitStore(Store node, Context context) {
                Entry ptr = analyzeChild(node.getPtr(), new Context(true));
                Entry value = analyzeChild(node.getValue(), new Context(true));
                checkState(ptr.getType() == Type.I32);
                checkState(value.getType() == node.getType());
                return new Entry(node.getType(),
                        mergeValueInitializations(ptr.getInitialization(), value.getInitialization()),
                        value.getOwnership(),
                        mergeBreakValueTypes(ptr.getBreakValueTypes(), value.getBreakValueTypes()), context);
            }

            @Override
            public Entry visitSwitch(Switch node, Context context) {
                List<Entry> cases = node.getEntries().stream().map(Switch.Entry::getBody)
                        .map(child -> analyzeChild(child, new Context(false))).collect(toImmutableList());
                Entry condition = analyzeChild(node.getCondition(), new Context(true));
                checkState(condition.getType().isConcrete() && !condition.getType().isFloat());
                return new Entry(Type.NONE, Initialization.VOID, Ownership.VOID,
                        mergeBreakValueTypes(Stream.concat(Stream.of(condition.getBreakValueTypes()),
                                cases.stream().map(Entry::getBreakValueTypes)).iterator()),
                        context);
            }

            @Override
            public Entry visitUnary(Unary node, Context context) {
                Entry value = analyzeChild(node.getValue(), new Context(true));
                checkState(value.getType().isConcrete());
                Set<Type> resultTypes = requireNonNull(node.getOp().getTypeMap().get(value.getType()));
                checkState(resultTypes.contains(node.getType()));
                return new Entry(node.getType(), value.getInitialization(), value.getOwnership(),
                        value.getBreakValueTypes(), context);
            }

            @Override
            public Entry visitUnreachable(Unreachable node, Context context) {
                return new Entry(Type.UNREACHABLE, Initialization.VOID, Ownership.VOID, EMPTY_BREAK_VALUE_TYPES,
                        context);
            }
        }, new Context(isRootUsed));
        checkState(rootAnalysis.getBreakValueTypes().isEmpty());
        analyses.put(root, rootAnalysis);
        return new ValueTypeAnalysis(analyses);
    }
}