From ff059919a798a02a5792111be3e5d4bf1de8058e Mon Sep 17 00:00:00 2001
From: Alexey Andreev <Alexey.Andreev@jetbrains.com>
Date: Sun, 8 Jan 2017 22:04:24 +0300
Subject: [PATCH] Eliminate exception joint in favour of phi functions with
 multiple inputs per source basic block

---
 .../org/teavm/ast/optimization/Optimizer.java |   1 -
 .../optimization/ReadWriteStatsBuilder.java   |   8 -
 .../main/java/org/teavm/cache/ProgramIO.java  |  19 --
 .../dependency/DataFlowGraphBuilder.java      |  14 -
 .../dependency/DependencyGraphBuilder.java    |  14 -
 core/src/main/java/org/teavm/model/Phi.java   |   4 -
 .../main/java/org/teavm/model/Program.java    |  29 ++
 .../java/org/teavm/model/TryCatchBlock.java   |  66 ----
 .../org/teavm/model/TryCatchBlockReader.java  |   4 -
 .../java/org/teavm/model/TryCatchJoint.java   |  52 ---
 .../org/teavm/model/TryCatchJointReader.java  |  26 --
 .../model/analysis/NullnessInformation.java   |  18 +-
 .../analysis/NullnessInformationBuilder.java  |   9 -
 .../lowlevel/ClassInitializerEliminator.java  |   2 -
 ...ceptionHandlingShadowStackContributor.java |  53 ++-
 .../optimization/GlobalValueNumbering.java    |  10 -
 .../model/optimization/LoopInversionImpl.java |   8 -
 .../UnusedVariableElimination.java            |  11 -
 .../VariableUsageGraphBuilder.java            | 123 ++-----
 .../org/teavm/model/text/ListingBuilder.java  |  18 +-
 .../org/teavm/model/text/ListingParser.java   |  32 +-
 .../model/util/InstructionVariableMapper.java |  66 ++--
 .../model/util/InterferenceGraphBuilder.java  |   9 -
 .../teavm/model/util/LivenessAnalyzer.java    |  19 +-
 .../java/org/teavm/model/util/PhiUpdater.java | 304 +++++-------------
 .../org/teavm/model/util/ProgramUtils.java    |  41 +--
 .../teavm/model/util/RegisterAllocator.java   |  51 ---
 .../org/teavm/model/util/TypeInferer.java     |   8 -
 .../nullness/tryCatchJoint.extended.txt       |   6 +-
 .../nullness/tryCatchJoint.original.txt       |   4 +-
 .../test/resources/model/text/exceptions.txt  |   4 +-
 .../phi-updater/exceptionPhi.expected.txt     |   2 +-
 .../exceptionPhiMultiple.expected.txt         |  10 +-
 .../existingExceptionPhi.expected.txt         |  12 +-
 .../existingExceptionPhi.original.txt         |  12 +-
 .../metaprogramming/impl/AliasFinder.java     |  12 -
 .../impl/optimization/BoxingElimination.java  |   9 -
 37 files changed, 246 insertions(+), 844 deletions(-)
 delete mode 100644 core/src/main/java/org/teavm/model/TryCatchJoint.java
 delete mode 100644 core/src/main/java/org/teavm/model/TryCatchJointReader.java

diff --git a/core/src/main/java/org/teavm/ast/optimization/Optimizer.java b/core/src/main/java/org/teavm/ast/optimization/Optimizer.java
index d42e96421..98ff2ff09 100644
--- a/core/src/main/java/org/teavm/ast/optimization/Optimizer.java
+++ b/core/src/main/java/org/teavm/ast/optimization/Optimizer.java
@@ -16,7 +16,6 @@
 package org.teavm.ast.optimization;
 
 import java.util.BitSet;
-import java.util.List;
 import org.teavm.ast.AsyncMethodNode;
 import org.teavm.ast.AsyncMethodPart;
 import org.teavm.ast.RegularMethodNode;
diff --git a/core/src/main/java/org/teavm/ast/optimization/ReadWriteStatsBuilder.java b/core/src/main/java/org/teavm/ast/optimization/ReadWriteStatsBuilder.java
index 1b29da3a5..5a685a198 100644
--- a/core/src/main/java/org/teavm/ast/optimization/ReadWriteStatsBuilder.java
+++ b/core/src/main/java/org/teavm/ast/optimization/ReadWriteStatsBuilder.java
@@ -78,14 +78,6 @@ class ReadWriteStatsBuilder {
                     }
                 }
             }
-            for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) {
-                for (TryCatchJoint joint : tryCatch.getJoints()) {
-                    writes[joint.getReceiver().getIndex()] += joint.getSourceVariables().size();
-                    for (Variable var : joint.getSourceVariables()) {
-                        reads[var.getIndex()]++;
-                    }
-                }
-            }
 
             for (int succ : dom.outgoingEdges(node)) {
                 stack.push(succ);
diff --git a/core/src/main/java/org/teavm/cache/ProgramIO.java b/core/src/main/java/org/teavm/cache/ProgramIO.java
index eccdb63fe..8a3045cd5 100644
--- a/core/src/main/java/org/teavm/cache/ProgramIO.java
+++ b/core/src/main/java/org/teavm/cache/ProgramIO.java
@@ -63,14 +63,6 @@ public class ProgramIO {
                 data.writeInt(tryCatch.getExceptionType() != null ? symbolTable.lookup(
                         tryCatch.getExceptionType()) : -1);
                 data.writeShort(tryCatch.getHandler().getIndex());
-                data.writeShort(tryCatch.getJoints().size());
-                for (TryCatchJoint joint : tryCatch.getJoints()) {
-                    data.writeShort(joint.getReceiver().getIndex());
-                    data.writeShort(joint.getSourceVariables().size());
-                    for (Variable sourceVar : joint.getSourceVariables()) {
-                        data.writeShort(sourceVar.getIndex());
-                    }
-                }
             }
             TextLocation location = null;
             InstructionWriter insnWriter = new InstructionWriter(data);
@@ -141,17 +133,6 @@ public class ProgramIO {
                 }
                 tryCatch.setHandler(program.basicBlockAt(data.readShort()));
 
-                int jointCount = data.readShort();
-                for (int k = 0; k < jointCount; ++k) {
-                    TryCatchJoint joint = new TryCatchJoint();
-                    joint.setReceiver(program.variableAt(data.readShort()));
-                    int jointSourceCount = data.readShort();
-                    for (int m = 0; m < jointSourceCount; ++m) {
-                        joint.getSourceVariables().add(program.variableAt(data.readShort()));
-                    }
-                    tryCatch.getJoints().add(joint);
-                }
-
                 block.getTryCatchBlocks().add(tryCatch);
             }
 
diff --git a/core/src/main/java/org/teavm/dependency/DataFlowGraphBuilder.java b/core/src/main/java/org/teavm/dependency/DataFlowGraphBuilder.java
index 044b6f612..1e043241e 100644
--- a/core/src/main/java/org/teavm/dependency/DataFlowGraphBuilder.java
+++ b/core/src/main/java/org/teavm/dependency/DataFlowGraphBuilder.java
@@ -64,13 +64,6 @@ public class DataFlowGraphBuilder extends AbstractInstructionReader {
                     builder.addEdge(from, to);
                 }
             }
-            for (TryCatchBlockReader tryCatch : block.readTryCatchBlocks()) {
-                for (TryCatchJointReader joint : tryCatch.readJoints()) {
-                    for (VariableReader sourceVar : joint.readSourceVariables()) {
-                        builder.addEdge(sourceVar.getIndex(), joint.getReceiver().getIndex());
-                    }
-                }
-            }
             block.readAllInstructions(this);
         }
         Graph graph = builder.build();
@@ -221,13 +214,6 @@ public class DataFlowGraphBuilder extends AbstractInstructionReader {
         }
     }
 
-    @Override
-    public void invokeDynamic(VariableReader receiver, VariableReader instance, MethodDescriptor method,
-            List<? extends VariableReader> arguments, MethodHandle bootstrapMethod,
-            List<RuntimeConstant> bootstrapArguments) {
-        // Should be eliminated by bootstrap method substitutor
-    }
-
     @Override
     public void nullCheck(VariableReader receiver, VariableReader value) {
         connect(value.getIndex(), receiver.getIndex());
diff --git a/core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java b/core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java
index 309d2ca2b..78b069b82 100644
--- a/core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java
+++ b/core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java
@@ -44,7 +44,6 @@ import org.teavm.model.Program;
 import org.teavm.model.RuntimeConstant;
 import org.teavm.model.TextLocation;
 import org.teavm.model.TryCatchBlockReader;
-import org.teavm.model.TryCatchJointReader;
 import org.teavm.model.ValueType;
 import org.teavm.model.VariableReader;
 import org.teavm.model.emit.ProgramEmitter;
@@ -142,19 +141,6 @@ class DependencyGraphBuilder {
                 if (tryCatch.getExceptionType() != null) {
                     dependencyChecker.linkClass(tryCatch.getExceptionType(), new CallLocation(caller.getMethod()));
                 }
-
-                for (TryCatchJointReader joint : tryCatch.readJoints()) {
-                    DependencyNode receiverNode = nodes[joint.getReceiver().getIndex()];
-                    if (receiverNode == null) {
-                        continue;
-                    }
-                    for (VariableReader source : joint.readSourceVariables()) {
-                        DependencyNode sourceNode = nodes[source.getIndex()];
-                        if (sourceNode != null) {
-                            sourceNode.connect(receiverNode);
-                        }
-                    }
-                }
             }
         }
 
diff --git a/core/src/main/java/org/teavm/model/Phi.java b/core/src/main/java/org/teavm/model/Phi.java
index 00f715882..40db6ddaf 100644
--- a/core/src/main/java/org/teavm/model/Phi.java
+++ b/core/src/main/java/org/teavm/model/Phi.java
@@ -20,10 +20,6 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
-/**
- *
- * @author Alexey Andreev
- */
 public class Phi implements PhiReader {
     private BasicBlock basicBlock;
     private Variable receiver;
diff --git a/core/src/main/java/org/teavm/model/Program.java b/core/src/main/java/org/teavm/model/Program.java
index 2274586ca..ba6b0b57d 100644
--- a/core/src/main/java/org/teavm/model/Program.java
+++ b/core/src/main/java/org/teavm/model/Program.java
@@ -65,6 +65,35 @@ public class Program implements ProgramReader {
         return basicBlocks;
     }
 
+    public void rearrangeBasicBlocks(List<BasicBlock> basicBlocks) {
+        if (!isPacked()) {
+            throw new IllegalStateException("This operation is not supported on unpacked programs");
+        }
+
+        if (basicBlocks.size() != this.basicBlocks.size()) {
+            throw new IllegalArgumentException("New list of basic blocks has wrong size ("
+                + basicBlocks.size() + ", expected " + basicBlockCount() + ")");
+        }
+
+        boolean[] indexes = new boolean[basicBlocks.size()];
+        for (BasicBlock block : basicBlocks) {
+            if (block.getProgram() != this) {
+                throw new IllegalArgumentException("The list of basic blocks contains a basic block from "
+                        + "another program");
+            }
+            if (indexes[block.getIndex()]) {
+                throw new IllegalArgumentException("The list of basic blocks contains same basic block twice");
+            }
+            indexes[block.getIndex()] = true;
+        }
+
+        this.basicBlocks.clear();
+        this.basicBlocks.addAll(basicBlocks);
+        for (int i = 0; i < this.basicBlocks.size(); ++i) {
+            this.basicBlocks.get(i).setIndex(i);
+        }
+    }
+
     public void deleteVariable(int index) {
         Variable variable = variables.get(index);
         if (variable == null) {
diff --git a/core/src/main/java/org/teavm/model/TryCatchBlock.java b/core/src/main/java/org/teavm/model/TryCatchBlock.java
index 9a5846dc1..2b020c103 100644
--- a/core/src/main/java/org/teavm/model/TryCatchBlock.java
+++ b/core/src/main/java/org/teavm/model/TryCatchBlock.java
@@ -15,64 +15,10 @@
  */
 package org.teavm.model;
 
-import java.util.AbstractList;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
 public class TryCatchBlock implements TryCatchBlockReader {
     BasicBlock protectedBlock;
     private BasicBlock handler;
     private String exceptionType;
-    private List<TryCatchJoint> joints = new ArrayList<>();
-    private List<TryCatchJointReader> immutableJoints;
-
-    private List<TryCatchJoint> safeJoints = new AbstractList<TryCatchJoint>() {
-        @Override
-        public TryCatchJoint get(int index) {
-            return joints.get(index);
-        }
-
-        @Override
-        public int size() {
-            return joints.size();
-        }
-
-        @Override
-        public void add(int index, TryCatchJoint e) {
-            if (e.getBlock() != null) {
-                throw new IllegalArgumentException("This joint is already in some basic block");
-            }
-            e.block = TryCatchBlock.this;
-            joints.add(index, e);
-        }
-
-        @Override
-        public TryCatchJoint set(int index, TryCatchJoint element) {
-            if (element.block != null) {
-                throw new IllegalArgumentException("This phi is already in some basic block");
-            }
-            TryCatchJoint oldJoint = joints.get(index);
-            oldJoint.block = null;
-            element.block = TryCatchBlock.this;
-            return joints.set(index, element);
-        }
-
-        @Override
-        public TryCatchJoint remove(int index) {
-            TryCatchJoint joint = joints.remove(index);
-            joint.block = null;
-            return joint;
-        }
-
-        @Override
-        public void clear() {
-            for (TryCatchJoint joint : joints) {
-                joint.block = null;
-            }
-            joints.clear();
-        }
-    };
 
     @Override
     public BasicBlock getHandler() {
@@ -96,16 +42,4 @@ public class TryCatchBlock implements TryCatchBlockReader {
     public BasicBlock getProtectedBlock() {
         return protectedBlock;
     }
-
-    public List<TryCatchJoint> getJoints() {
-        return safeJoints;
-    }
-
-    @Override
-    public List<TryCatchJointReader> readJoints() {
-        if (immutableJoints == null) {
-            immutableJoints = Collections.unmodifiableList(safeJoints);
-        }
-        return immutableJoints;
-    }
 }
diff --git a/core/src/main/java/org/teavm/model/TryCatchBlockReader.java b/core/src/main/java/org/teavm/model/TryCatchBlockReader.java
index a49344476..a710db5ca 100644
--- a/core/src/main/java/org/teavm/model/TryCatchBlockReader.java
+++ b/core/src/main/java/org/teavm/model/TryCatchBlockReader.java
@@ -15,14 +15,10 @@
  */
 package org.teavm.model;
 
-import java.util.List;
-
 public interface TryCatchBlockReader {
     BasicBlockReader getProtectedBlock();
 
     BasicBlockReader getHandler();
 
     String getExceptionType();
-
-    List<TryCatchJointReader> readJoints();
 }
diff --git a/core/src/main/java/org/teavm/model/TryCatchJoint.java b/core/src/main/java/org/teavm/model/TryCatchJoint.java
deleted file mode 100644
index 919d876f6..000000000
--- a/core/src/main/java/org/teavm/model/TryCatchJoint.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- *  Copyright 2016 Alexey Andreev.
- *
- *  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 org.teavm.model;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-public class TryCatchJoint implements TryCatchJointReader {
-    private List<Variable> sourceVariables = new ArrayList<>();
-    private List<VariableReader> readonlySourceVariables;
-    private Variable receiver;
-    TryCatchBlock block;
-
-    @Override
-    public List<VariableReader> readSourceVariables() {
-        if (readonlySourceVariables == null) {
-            readonlySourceVariables = Collections.unmodifiableList(sourceVariables);
-        }
-        return readonlySourceVariables;
-    }
-
-    public List<Variable> getSourceVariables() {
-        return sourceVariables;
-    }
-
-    @Override
-    public Variable getReceiver() {
-        return receiver;
-    }
-
-    public void setReceiver(Variable receiver) {
-        this.receiver = receiver;
-    }
-
-    public TryCatchBlock getBlock() {
-        return block;
-    }
-}
diff --git a/core/src/main/java/org/teavm/model/TryCatchJointReader.java b/core/src/main/java/org/teavm/model/TryCatchJointReader.java
deleted file mode 100644
index 2045d7529..000000000
--- a/core/src/main/java/org/teavm/model/TryCatchJointReader.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- *  Copyright 2016 Alexey Andreev.
- *
- *  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 org.teavm.model;
-
-import java.util.List;
-
-public interface TryCatchJointReader {
-    List<VariableReader> readSourceVariables();
-
-    VariableReader getReceiver();
-
-    TryCatchBlockReader getBlock();
-}
diff --git a/core/src/main/java/org/teavm/model/analysis/NullnessInformation.java b/core/src/main/java/org/teavm/model/analysis/NullnessInformation.java
index 1769b52f1..d82be9666 100644
--- a/core/src/main/java/org/teavm/model/analysis/NullnessInformation.java
+++ b/core/src/main/java/org/teavm/model/analysis/NullnessInformation.java
@@ -24,8 +24,6 @@ import org.teavm.model.Instruction;
 import org.teavm.model.MethodDescriptor;
 import org.teavm.model.Phi;
 import org.teavm.model.Program;
-import org.teavm.model.TryCatchBlock;
-import org.teavm.model.TryCatchJoint;
 import org.teavm.model.Variable;
 import org.teavm.model.util.DefinitionExtractor;
 import org.teavm.model.util.InstructionVariableMapper;
@@ -57,7 +55,6 @@ public class NullnessInformation {
 
     public void dispose() {
         Set<Phi> phisToRemove = new HashSet<>(phiUpdater.getSynthesizedPhis());
-        Set<TryCatchJoint> jointsToRemove = new HashSet<>(phiUpdater.getSynthesizedJoints());
         DefinitionExtractor defExtractor = new DefinitionExtractor();
         InstructionVariableMapper variableMapper = new InstructionVariableMapper(var -> {
             int source = phiUpdater.getSourceVariable(var.getIndex());
@@ -65,9 +62,6 @@ public class NullnessInformation {
         });
         for (BasicBlock block : program.getBasicBlocks()) {
             block.getPhis().removeIf(phisToRemove::contains);
-            for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) {
-                tryCatch.getJoints().removeIf(jointsToRemove::contains);
-            }
             for (Instruction insn : block) {
                 insn.acceptVisitor(defExtractor);
                 if (Arrays.stream(defExtractor.getDefinedVariables())
@@ -78,8 +72,18 @@ public class NullnessInformation {
                 }
             }
             variableMapper.applyToPhis(block);
-            variableMapper.applyToTryCatchBlocks(block);
+            if (block.getExceptionVariable() != null) {
+                block.setExceptionVariable(variableMapper.map(block.getExceptionVariable()));
+            }
         }
+
+        for (int i = 0; i < program.variableCount(); ++i) {
+            int sourceVar = phiUpdater.getSourceVariable(i);
+            if (sourceVar >= 0 && sourceVar != i) {
+                program.deleteVariable(i);
+            }
+        }
+        program.pack();
     }
 
     public static NullnessInformation build(Program program, MethodDescriptor methodDescriptor) {
diff --git a/core/src/main/java/org/teavm/model/analysis/NullnessInformationBuilder.java b/core/src/main/java/org/teavm/model/analysis/NullnessInformationBuilder.java
index 90f53c718..8927339eb 100644
--- a/core/src/main/java/org/teavm/model/analysis/NullnessInformationBuilder.java
+++ b/core/src/main/java/org/teavm/model/analysis/NullnessInformationBuilder.java
@@ -33,8 +33,6 @@ import org.teavm.model.Instruction;
 import org.teavm.model.MethodDescriptor;
 import org.teavm.model.Phi;
 import org.teavm.model.Program;
-import org.teavm.model.TryCatchBlock;
-import org.teavm.model.TryCatchJoint;
 import org.teavm.model.Variable;
 import org.teavm.model.instructions.AbstractInstructionVisitor;
 import org.teavm.model.instructions.ArrayLengthInstruction;
@@ -126,13 +124,6 @@ class NullnessInformationBuilder {
                     builder.addEdge(incoming.getValue().getIndex(), phi.getReceiver().getIndex());
                 }
             }
-            for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) {
-                for (TryCatchJoint joint : tryCatch.getJoints()) {
-                    for (Variable sourceVar : joint.getSourceVariables()) {
-                        builder.addEdge(sourceVar.getIndex(), joint.getReceiver().getIndex());
-                    }
-                }
-            }
 
             for (Instruction instruction : block) {
                 if (instruction instanceof AssignInstruction) {
diff --git a/core/src/main/java/org/teavm/model/lowlevel/ClassInitializerEliminator.java b/core/src/main/java/org/teavm/model/lowlevel/ClassInitializerEliminator.java
index 7c72a2836..fab5328f0 100644
--- a/core/src/main/java/org/teavm/model/lowlevel/ClassInitializerEliminator.java
+++ b/core/src/main/java/org/teavm/model/lowlevel/ClassInitializerEliminator.java
@@ -16,7 +16,6 @@
 package org.teavm.model.lowlevel;
 
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import org.teavm.interop.StaticInit;
 import org.teavm.interop.Structure;
@@ -26,7 +25,6 @@ import org.teavm.model.ClassReaderSource;
 import org.teavm.model.Instruction;
 import org.teavm.model.MethodDescriptor;
 import org.teavm.model.Program;
-import org.teavm.model.instructions.EmptyInstruction;
 import org.teavm.model.instructions.InitClassInstruction;
 
 public class ClassInitializerEliminator {
diff --git a/core/src/main/java/org/teavm/model/lowlevel/ExceptionHandlingShadowStackContributor.java b/core/src/main/java/org/teavm/model/lowlevel/ExceptionHandlingShadowStackContributor.java
index 3ac9ba48b..8ca3a67f5 100644
--- a/core/src/main/java/org/teavm/model/lowlevel/ExceptionHandlingShadowStackContributor.java
+++ b/core/src/main/java/org/teavm/model/lowlevel/ExceptionHandlingShadowStackContributor.java
@@ -18,6 +18,7 @@ package org.teavm.model.lowlevel;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.stream.Collectors;
 import org.teavm.common.DominatorTree;
 import org.teavm.common.Graph;
 import org.teavm.common.GraphUtils;
@@ -29,7 +30,6 @@ import org.teavm.model.Phi;
 import org.teavm.model.Program;
 import org.teavm.model.TextLocation;
 import org.teavm.model.TryCatchBlock;
-import org.teavm.model.TryCatchJoint;
 import org.teavm.model.ValueType;
 import org.teavm.model.Variable;
 import org.teavm.model.instructions.BinaryBranchingCondition;
@@ -64,7 +64,6 @@ public class ExceptionHandlingShadowStackContributor {
     private Program program;
     private DominatorTree dom;
     private BasicBlock[] variableDefinitionPlaces;
-    private Phi[] jointPhis;
     private boolean hasExceptionHandlers;
 
     public ExceptionHandlingShadowStackContributor(ManagedMethodRepository managedMethodRepository,
@@ -77,7 +76,6 @@ public class ExceptionHandlingShadowStackContributor {
         Graph cfg = ProgramUtils.buildControlFlowGraph(program);
         dom = GraphUtils.buildDominatorTree(cfg);
         variableDefinitionPlaces = ProgramUtils.getVariableDefinitionPlaces(program);
-        jointPhis = new Phi[program.variableCount()];
     }
 
     public boolean contribute() {
@@ -126,17 +124,26 @@ public class ExceptionHandlingShadowStackContributor {
         int[] currentJointSources = new int[program.variableCount()];
         int[] jointReceiverMap = new int[program.variableCount()];
         Arrays.fill(currentJointSources, -1);
+
         for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) {
-            for (TryCatchJoint joint : tryCatch.getJoints()) {
-                for (Variable sourceVar : joint.getSourceVariables()) {
+            for (Phi phi : tryCatch.getHandler().getPhis()) {
+                List<Variable> sourceVariables = phi.getIncomings().stream()
+                        .filter(incoming -> incoming.getSource() == tryCatch.getProtectedBlock())
+                        .map(incoming -> incoming.getValue())
+                        .collect(Collectors.toList());
+                if (sourceVariables.isEmpty()) {
+                    continue;
+                }
+
+                for (Variable sourceVar : sourceVariables) {
                     BasicBlock sourceVarDefinedAt = variableDefinitionPlaces[sourceVar.getIndex()];
                     if (dom.dominates(sourceVarDefinedAt.getIndex(), block.getIndex())) {
-                        currentJointSources[joint.getReceiver().getIndex()] = sourceVar.getIndex();
+                        currentJointSources[phi.getReceiver().getIndex()] = sourceVar.getIndex();
                         break;
                     }
                 }
-                for (Variable sourceVar : joint.getSourceVariables()) {
-                    jointReceiverMap[sourceVar.getIndex()] = joint.getReceiver().getIndex();
+                for (Variable sourceVar : sourceVariables) {
+                    jointReceiverMap[sourceVar.getIndex()] = phi.getReceiver().getIndex();
                 }
             }
         }
@@ -281,13 +288,17 @@ public class ExceptionHandlingShadowStackContributor {
                 switchInsn.getEntries().add(catchEntry);
             }
 
-            for (TryCatchJoint joint : tryCatch.getJoints()) {
-                Phi phi = getJointPhi(joint);
-                Incoming incoming = new Incoming();
-                incoming.setSource(block);
-                int value = currentJointSources[joint.getReceiver().getIndex()];
-                incoming.setValue(program.variableAt(value));
-                phi.getIncomings().add(incoming);
+            for (Phi phi : tryCatch.getHandler().getPhis()) {
+                int value = currentJointSources[phi.getReceiver().getIndex()];
+                if (value < 0) {
+                    continue;
+                }
+                for (Incoming incoming : phi.getIncomings()) {
+                    if (incoming.getValue().getIndex() == value) {
+                        incoming.setSource(block);
+                        break;
+                    }
+                }
             }
         }
 
@@ -327,18 +338,6 @@ public class ExceptionHandlingShadowStackContributor {
         return defaultExceptionHandler;
     }
 
-    private Phi getJointPhi(TryCatchJoint joint) {
-        Phi phi = jointPhis[joint.getReceiver().getIndex()];
-        if (phi == null) {
-            phi = new Phi();
-            phi.setReceiver(joint.getReceiver());
-            BasicBlock handler = program.basicBlockAt(joint.getBlock().getHandler().getIndex());
-            handler.getPhis().add(phi);
-            jointPhis[joint.getReceiver().getIndex()] = phi;
-        }
-        return phi;
-    }
-
     private Variable createReturnValueInstructions(BasicBlock block) {
         ValueType returnType = method.getReturnType();
         if (returnType == ValueType.VOID) {
diff --git a/core/src/main/java/org/teavm/model/optimization/GlobalValueNumbering.java b/core/src/main/java/org/teavm/model/optimization/GlobalValueNumbering.java
index 029106f26..c9714fdd4 100644
--- a/core/src/main/java/org/teavm/model/optimization/GlobalValueNumbering.java
+++ b/core/src/main/java/org/teavm/model/optimization/GlobalValueNumbering.java
@@ -28,8 +28,6 @@ import org.teavm.model.Incoming;
 import org.teavm.model.Instruction;
 import org.teavm.model.InvokeDynamicInstruction;
 import org.teavm.model.Program;
-import org.teavm.model.TryCatchBlock;
-import org.teavm.model.TryCatchJoint;
 import org.teavm.model.Variable;
 import org.teavm.model.instructions.ArrayLengthInstruction;
 import org.teavm.model.instructions.AssignInstruction;
@@ -164,14 +162,6 @@ public class GlobalValueNumbering implements MethodOptimization {
                     }
                 }
             }
-            for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) {
-                for (TryCatchJoint joint : tryCatch.getJoints()) {
-                    for (int i = 0; i < joint.getSourceVariables().size(); ++i) {
-                        int sourceVar = map[joint.getSourceVariables().get(i).getIndex()];
-                        joint.getSourceVariables().set(i, program.variableAt(sourceVar));
-                    }
-                }
-            }
             for (Incoming incoming : outgoings.get(v)) {
                 int value = map[incoming.getValue().getIndex()];
                 incoming.setValue(program.variableAt(value));
diff --git a/core/src/main/java/org/teavm/model/optimization/LoopInversionImpl.java b/core/src/main/java/org/teavm/model/optimization/LoopInversionImpl.java
index 7947e2e0a..0642d232d 100644
--- a/core/src/main/java/org/teavm/model/optimization/LoopInversionImpl.java
+++ b/core/src/main/java/org/teavm/model/optimization/LoopInversionImpl.java
@@ -39,7 +39,6 @@ import org.teavm.model.MethodReference;
 import org.teavm.model.Phi;
 import org.teavm.model.Program;
 import org.teavm.model.TryCatchBlock;
-import org.teavm.model.TryCatchJoint;
 import org.teavm.model.Variable;
 import org.teavm.model.analysis.NullnessInformation;
 import org.teavm.model.util.BasicBlockMapper;
@@ -345,13 +344,6 @@ class LoopInversionImpl {
                     tryCatchCopy.setExceptionType(tryCatch.getExceptionType());
                     tryCatchCopy.setHandler(program.basicBlockAt(copiedNodes.getOrDefault(handler, handler)));
                     targetBlock.getTryCatchBlocks().add(tryCatchCopy);
-
-                    for (TryCatchJoint joint : tryCatch.getJoints()) {
-                        TryCatchJoint jointCopy = new TryCatchJoint();
-                        jointCopy.setReceiver(joint.getReceiver());
-                        jointCopy.getSourceVariables().addAll(joint.getSourceVariables());
-                        tryCatchCopy.getJoints().add(jointCopy);
-                    }
                 }
             }
 
diff --git a/core/src/main/java/org/teavm/model/optimization/UnusedVariableElimination.java b/core/src/main/java/org/teavm/model/optimization/UnusedVariableElimination.java
index 34e82f5e9..42d3319d9 100644
--- a/core/src/main/java/org/teavm/model/optimization/UnusedVariableElimination.java
+++ b/core/src/main/java/org/teavm/model/optimization/UnusedVariableElimination.java
@@ -22,8 +22,6 @@ import org.teavm.model.InvokeDynamicInstruction;
 import org.teavm.model.MethodReader;
 import org.teavm.model.Phi;
 import org.teavm.model.Program;
-import org.teavm.model.TryCatchBlock;
-import org.teavm.model.TryCatchJoint;
 import org.teavm.model.Variable;
 import org.teavm.model.instructions.AbstractInstructionVisitor;
 import org.teavm.model.instructions.ArrayLengthInstruction;
@@ -105,15 +103,6 @@ public class UnusedVariableElimination implements MethodOptimization {
                 }
             }
 
-            for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) {
-                for (int j = 0; j < tryCatch.getJoints().size(); ++j) {
-                    TryCatchJoint joint = tryCatch.getJoints().get(j);
-                    if (!used[joint.getReceiver().getIndex()]) {
-                        tryCatch.getJoints().remove(j--);
-                    }
-                }
-            }
-
             for (int j = 0; j < block.getPhis().size(); ++j) {
                 Phi phi = block.getPhis().get(j);
                 if (!used[phi.getReceiver().getIndex()]) {
diff --git a/core/src/main/java/org/teavm/model/optimization/VariableUsageGraphBuilder.java b/core/src/main/java/org/teavm/model/optimization/VariableUsageGraphBuilder.java
index 3fad34ec0..56d69cbe5 100644
--- a/core/src/main/java/org/teavm/model/optimization/VariableUsageGraphBuilder.java
+++ b/core/src/main/java/org/teavm/model/optimization/VariableUsageGraphBuilder.java
@@ -17,8 +17,28 @@ package org.teavm.model.optimization;
 
 import org.teavm.common.Graph;
 import org.teavm.common.GraphBuilder;
-import org.teavm.model.*;
-import org.teavm.model.instructions.*;
+import org.teavm.model.BasicBlock;
+import org.teavm.model.Incoming;
+import org.teavm.model.Instruction;
+import org.teavm.model.Phi;
+import org.teavm.model.Program;
+import org.teavm.model.Variable;
+import org.teavm.model.instructions.AbstractInstructionVisitor;
+import org.teavm.model.instructions.ArrayLengthInstruction;
+import org.teavm.model.instructions.AssignInstruction;
+import org.teavm.model.instructions.BinaryInstruction;
+import org.teavm.model.instructions.CastInstruction;
+import org.teavm.model.instructions.CastIntegerInstruction;
+import org.teavm.model.instructions.CastNumberInstruction;
+import org.teavm.model.instructions.CloneArrayInstruction;
+import org.teavm.model.instructions.ConstructArrayInstruction;
+import org.teavm.model.instructions.ConstructMultiArrayInstruction;
+import org.teavm.model.instructions.GetElementInstruction;
+import org.teavm.model.instructions.GetFieldInstruction;
+import org.teavm.model.instructions.IsInstanceInstruction;
+import org.teavm.model.instructions.NegateInstruction;
+import org.teavm.model.instructions.NullCheckInstruction;
+import org.teavm.model.instructions.UnwrapArrayInstruction;
 
 public final class VariableUsageGraphBuilder {
     private VariableUsageGraphBuilder() {
@@ -37,18 +57,11 @@ public final class VariableUsageGraphBuilder {
                     builder.addEdge(incoming.getValue().getIndex(), phi.getReceiver().getIndex());
                 }
             }
-            for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) {
-                for (TryCatchJoint joint : tryCatch.getJoints()) {
-                    for (Variable sourceVar : joint.getSourceVariables()) {
-                        builder.addEdge(sourceVar.getIndex(), joint.getReceiver().getIndex());
-                    }
-                }
-            }
         }
         return builder.build();
     }
 
-    private static class InstructionAnalyzer implements InstructionVisitor {
+    private static class InstructionAnalyzer extends AbstractInstructionVisitor {
         private GraphBuilder builder;
 
         public InstructionAnalyzer(GraphBuilder builder) {
@@ -61,38 +74,6 @@ public final class VariableUsageGraphBuilder {
             }
         }
 
-        @Override
-        public void visit(EmptyInstruction insn) {
-        }
-
-        @Override
-        public void visit(ClassConstantInstruction insn) {
-        }
-
-        @Override
-        public void visit(NullConstantInstruction insn) {
-        }
-
-        @Override
-        public void visit(IntegerConstantInstruction insn) {
-        }
-
-        @Override
-        public void visit(LongConstantInstruction insn) {
-        }
-
-        @Override
-        public void visit(FloatConstantInstruction insn) {
-        }
-
-        @Override
-        public void visit(DoubleConstantInstruction insn) {
-        }
-
-        @Override
-        public void visit(StringConstantInstruction insn) {
-        }
-
         @Override
         public void visit(BinaryInstruction insn) {
             use(insn.getReceiver(), insn.getFirstOperand(), insn.getSecondOperand());
@@ -123,39 +104,11 @@ public final class VariableUsageGraphBuilder {
             use(insn.getReceiver(), insn.getValue());
         }
 
-        @Override
-        public void visit(BranchingInstruction insn) {
-        }
-
-        @Override
-        public void visit(BinaryBranchingInstruction insn) {
-        }
-
-        @Override
-        public void visit(JumpInstruction insn) {
-        }
-
-        @Override
-        public void visit(SwitchInstruction insn) {
-        }
-
-        @Override
-        public void visit(ExitInstruction insn) {
-        }
-
-        @Override
-        public void visit(RaiseInstruction insn) {
-        }
-
         @Override
         public void visit(ConstructArrayInstruction insn) {
             use(insn.getReceiver(), insn.getSize());
         }
 
-        @Override
-        public void visit(ConstructInstruction insn) {
-        }
-
         @Override
         public void visit(ConstructMultiArrayInstruction insn) {
             use(insn.getReceiver(), insn.getDimensions().toArray(new Variable[0]));
@@ -168,10 +121,6 @@ public final class VariableUsageGraphBuilder {
             }
         }
 
-        @Override
-        public void visit(PutFieldInstruction insn) {
-        }
-
         @Override
         public void visit(ArrayLengthInstruction insn) {
             use(insn.getReceiver(), insn.getArray());
@@ -192,40 +141,14 @@ public final class VariableUsageGraphBuilder {
             use(insn.getReceiver(), insn.getArray(), insn.getIndex());
         }
 
-        @Override
-        public void visit(PutElementInstruction insn) {
-        }
-
-        @Override
-        public void visit(InvokeInstruction insn) {
-        }
-
-        @Override
-        public void visit(InvokeDynamicInstruction insn) {
-        }
-
         @Override
         public void visit(IsInstanceInstruction insn) {
             use(insn.getReceiver(), insn.getValue());
         }
 
-        @Override
-        public void visit(InitClassInstruction insn) {
-        }
-
         @Override
         public void visit(NullCheckInstruction insn) {
             use(insn.getReceiver(), insn.getValue());
         }
-
-        @Override
-        public void visit(MonitorEnterInstruction insn) {
-
-        }
-
-        @Override
-        public void visit(MonitorExitInstruction insn) {
-
-        }
     }
 }
diff --git a/core/src/main/java/org/teavm/model/text/ListingBuilder.java b/core/src/main/java/org/teavm/model/text/ListingBuilder.java
index 7ee31b1c5..59bc02a03 100644
--- a/core/src/main/java/org/teavm/model/text/ListingBuilder.java
+++ b/core/src/main/java/org/teavm/model/text/ListingBuilder.java
@@ -17,8 +17,14 @@ package org.teavm.model.text;
 
 import java.util.List;
 import java.util.Objects;
-import java.util.stream.Collectors;
-import org.teavm.model.*;
+import org.teavm.model.BasicBlockReader;
+import org.teavm.model.IncomingReader;
+import org.teavm.model.InstructionIterator;
+import org.teavm.model.PhiReader;
+import org.teavm.model.ProgramReader;
+import org.teavm.model.TextLocation;
+import org.teavm.model.TryCatchBlockReader;
+import org.teavm.model.VariableReader;
 
 public class ListingBuilder {
     public String buildListing(ProgramReader program, String prefix) {
@@ -87,14 +93,6 @@ public class ListingBuilder {
                 }
                 sb.append(" goto $").append(tryCatch.getHandler().getIndex());
                 sb.append("\n");
-                for (TryCatchJointReader joint : tryCatch.readJoints()) {
-                    sb.append("      @").append(stringifier.getVariableLabel(joint.getReceiver().getIndex()))
-                            .append(" := ephi ");
-                    sb.append(joint.readSourceVariables().stream()
-                            .map(sourceVar -> "@" + stringifier.getVariableLabel(sourceVar.getIndex()))
-                            .collect(Collectors.joining(", ")));
-                    sb.append("\n");
-                }
             }
         }
         return sb.toString();
diff --git a/core/src/main/java/org/teavm/model/text/ListingParser.java b/core/src/main/java/org/teavm/model/text/ListingParser.java
index 88d85db5f..a6bbf0b13 100644
--- a/core/src/main/java/org/teavm/model/text/ListingParser.java
+++ b/core/src/main/java/org/teavm/model/text/ListingParser.java
@@ -34,7 +34,6 @@ import org.teavm.model.Phi;
 import org.teavm.model.Program;
 import org.teavm.model.TextLocation;
 import org.teavm.model.TryCatchBlock;
-import org.teavm.model.TryCatchJoint;
 import org.teavm.model.ValueType;
 import org.teavm.model.Variable;
 import org.teavm.model.instructions.ArrayElementType;
@@ -89,9 +88,9 @@ public class ListingParser {
     private Map<String, BasicBlock> blockMap;
     private Map<String, Integer> blockFirstOccurrence;
     private Set<String> declaredBlocks = new HashSet<>();
+    private List<BasicBlock> orderedBlocks = new ArrayList<>();
     private TextLocation currentLocation;
     private BasicBlock currentBlock;
-    private TryCatchBlock currentTryCatch;
 
     public Program parse(Reader reader) throws IOException, ListingParseException {
         try {
@@ -114,6 +113,9 @@ public class ListingParser {
                 throw new ListingParseException("Block not defined: " + blockName, blockIndex);
             }
 
+            program.pack();
+            program.rearrangeBasicBlocks(orderedBlocks);
+
             return program;
         } finally {
             program = null;
@@ -177,7 +179,7 @@ public class ListingParser {
             b.setLabel(k);
             return b;
         });
-        currentTryCatch = null;
+        orderedBlocks.add(currentBlock);
 
         currentLocation = null;
         do {
@@ -361,10 +363,6 @@ public class ListingParser {
                         lexer.nextToken();
                         parsePhi(receiver);
                         break;
-                    case "ephi":
-                        lexer.nextToken();
-                        parseExceptionPhi(receiver);
-                        break;
                     case "classOf":
                         lexer.nextToken();
                         parseClassLiteral(receiver);
@@ -593,24 +591,6 @@ public class ListingParser {
         currentBlock.getPhis().add(phi);
     }
 
-    private void parseExceptionPhi(Variable receiver) throws IOException, ListingParseException {
-        int phiStart = lexer.getIndex();
-
-        TryCatchJoint joint = new TryCatchJoint();
-        joint.setReceiver(receiver);
-        joint.getSourceVariables().add(expectVariable());
-        while (lexer.getToken() == ListingToken.COMMA) {
-            lexer.nextToken();
-            joint.getSourceVariables().add(expectVariable());
-        }
-
-        if (currentTryCatch == null) {
-            throw new ListingParseException("Exception phi must appear right after catch block", phiStart);
-        }
-
-        currentTryCatch.getJoints().add(joint);
-    }
-
     private void parseClassLiteral(Variable receiver) throws IOException, ListingParseException {
         ValueType type = expectValueType();
         ClassConstantInstruction insn = new ClassConstantInstruction();
@@ -792,7 +772,6 @@ public class ListingParser {
         }
         expectKeyword("goto");
         tryCatch.setHandler(expectBlock());
-        currentTryCatch = tryCatch;
         currentBlock.getTryCatchBlocks().add(tryCatch);
     }
 
@@ -1170,7 +1149,6 @@ public class ListingParser {
     }
 
     private void addInstruction(Instruction instruction) throws ListingParseException {
-        currentTryCatch = null;
         instruction.setLocation(currentLocation);
         currentBlock.add(instruction);
     }
diff --git a/core/src/main/java/org/teavm/model/util/InstructionVariableMapper.java b/core/src/main/java/org/teavm/model/util/InstructionVariableMapper.java
index 528963c3a..277866939 100644
--- a/core/src/main/java/org/teavm/model/util/InstructionVariableMapper.java
+++ b/core/src/main/java/org/teavm/model/util/InstructionVariableMapper.java
@@ -21,12 +21,43 @@ import org.teavm.model.Incoming;
 import org.teavm.model.Instruction;
 import org.teavm.model.InvokeDynamicInstruction;
 import org.teavm.model.Phi;
-import org.teavm.model.TryCatchBlock;
-import org.teavm.model.TryCatchJoint;
 import org.teavm.model.Variable;
-import org.teavm.model.instructions.*;
+import org.teavm.model.instructions.AbstractInstructionVisitor;
+import org.teavm.model.instructions.ArrayLengthInstruction;
+import org.teavm.model.instructions.AssignInstruction;
+import org.teavm.model.instructions.BinaryBranchingInstruction;
+import org.teavm.model.instructions.BinaryInstruction;
+import org.teavm.model.instructions.BranchingInstruction;
+import org.teavm.model.instructions.CastInstruction;
+import org.teavm.model.instructions.CastIntegerInstruction;
+import org.teavm.model.instructions.CastNumberInstruction;
+import org.teavm.model.instructions.ClassConstantInstruction;
+import org.teavm.model.instructions.CloneArrayInstruction;
+import org.teavm.model.instructions.ConstructArrayInstruction;
+import org.teavm.model.instructions.ConstructInstruction;
+import org.teavm.model.instructions.ConstructMultiArrayInstruction;
+import org.teavm.model.instructions.DoubleConstantInstruction;
+import org.teavm.model.instructions.ExitInstruction;
+import org.teavm.model.instructions.FloatConstantInstruction;
+import org.teavm.model.instructions.GetElementInstruction;
+import org.teavm.model.instructions.GetFieldInstruction;
+import org.teavm.model.instructions.IntegerConstantInstruction;
+import org.teavm.model.instructions.InvokeInstruction;
+import org.teavm.model.instructions.IsInstanceInstruction;
+import org.teavm.model.instructions.LongConstantInstruction;
+import org.teavm.model.instructions.MonitorEnterInstruction;
+import org.teavm.model.instructions.MonitorExitInstruction;
+import org.teavm.model.instructions.NegateInstruction;
+import org.teavm.model.instructions.NullCheckInstruction;
+import org.teavm.model.instructions.NullConstantInstruction;
+import org.teavm.model.instructions.PutElementInstruction;
+import org.teavm.model.instructions.PutFieldInstruction;
+import org.teavm.model.instructions.RaiseInstruction;
+import org.teavm.model.instructions.StringConstantInstruction;
+import org.teavm.model.instructions.SwitchInstruction;
+import org.teavm.model.instructions.UnwrapArrayInstruction;
 
-public class InstructionVariableMapper implements InstructionVisitor {
+public class InstructionVariableMapper extends AbstractInstructionVisitor {
     private final Function<Variable, Variable> f;
 
     public InstructionVariableMapper(Function<Variable, Variable> f) {
@@ -40,7 +71,6 @@ public class InstructionVariableMapper implements InstructionVisitor {
 
         applyToInstructions(block);
         applyToPhis(block);
-        applyToTryCatchBlocks(block);
     }
 
     public void applyToInstructions(BasicBlock block) {
@@ -58,26 +88,10 @@ public class InstructionVariableMapper implements InstructionVisitor {
         }
     }
 
-    public void applyToTryCatchBlocks(BasicBlock block) {
-        for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) {
-            for (TryCatchJoint joint : tryCatch.getJoints()) {
-                joint.setReceiver(map(joint.getReceiver()));
-                for (int i = 0; i < joint.getSourceVariables().size(); ++i) {
-                    Variable var = joint.getSourceVariables().get(i);
-                    joint.getSourceVariables().set(i, map(var));
-                }
-            }
-        }
-    }
-
-    private Variable map(Variable var) {
+    public Variable map(Variable var) {
         return f.apply(var);
     }
 
-    @Override
-    public void visit(EmptyInstruction insn) {
-    }
-
     @Override
     public void visit(ClassConstantInstruction insn) {
         insn.setReceiver(map(insn.getReceiver()));
@@ -161,10 +175,6 @@ public class InstructionVariableMapper implements InstructionVisitor {
         insn.setSecondOperand(map(insn.getSecondOperand()));
     }
 
-    @Override
-    public void visit(JumpInstruction insn) {
-    }
-
     @Override
     public void visit(SwitchInstruction insn) {
         insn.setCondition(map(insn.getCondition()));
@@ -281,10 +291,6 @@ public class InstructionVariableMapper implements InstructionVisitor {
         insn.setValue(map(insn.getValue()));
     }
 
-    @Override
-    public void visit(InitClassInstruction insn) {
-    }
-
     @Override
     public void visit(NullCheckInstruction insn) {
         insn.setReceiver(map(insn.getReceiver()));
diff --git a/core/src/main/java/org/teavm/model/util/InterferenceGraphBuilder.java b/core/src/main/java/org/teavm/model/util/InterferenceGraphBuilder.java
index 3fab77af1..3929bf8fb 100644
--- a/core/src/main/java/org/teavm/model/util/InterferenceGraphBuilder.java
+++ b/core/src/main/java/org/teavm/model/util/InterferenceGraphBuilder.java
@@ -52,15 +52,6 @@ class InterferenceGraphBuilder {
                 live.add(nodes.get(outgoing.getValue().getIndex()));
             }
 
-            for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) {
-                for (TryCatchJoint joint : tryCatch.getJoints()) {
-                    for (Variable sourceVar : joint.getSourceVariables()) {
-                        live.add(nodes.get(sourceVar.getIndex()));
-                    }
-                    live.remove(nodes.get(joint.getReceiver().getIndex()));
-                }
-            }
-
             for (Instruction insn = block.getLastInstruction(); insn != null; insn = insn.getPrevious()) {
                 insn.acceptVisitor(useExtractor);
                 insn.acceptVisitor(defExtractor);
diff --git a/core/src/main/java/org/teavm/model/util/LivenessAnalyzer.java b/core/src/main/java/org/teavm/model/util/LivenessAnalyzer.java
index cff039973..1d852dd70 100644
--- a/core/src/main/java/org/teavm/model/util/LivenessAnalyzer.java
+++ b/core/src/main/java/org/teavm/model/util/LivenessAnalyzer.java
@@ -21,7 +21,12 @@ import java.util.ArrayDeque;
 import java.util.BitSet;
 import java.util.Deque;
 import org.teavm.common.Graph;
-import org.teavm.model.*;
+import org.teavm.model.BasicBlock;
+import org.teavm.model.Incoming;
+import org.teavm.model.Instruction;
+import org.teavm.model.Phi;
+import org.teavm.model.Program;
+import org.teavm.model.Variable;
 
 public class LivenessAnalyzer {
     private BitSet[] liveVars;
@@ -70,18 +75,6 @@ public class LivenessAnalyzer {
                 }
             }
 
-            for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) {
-                for (TryCatchJoint joint : tryCatch.getJoints()) {
-                    definitions[joint.getReceiver().getIndex()] = i;
-                    for (Variable sourceVar : joint.getSourceVariables()) {
-                        Task task = new Task();
-                        task.block = i;
-                        task.var = sourceVar.getIndex();
-                        stack.push(task);
-                    }
-                }
-            }
-
             for (Phi phi : block.getPhis()) {
                 definitions[phi.getReceiver().getIndex()] = i;
                 for (Incoming incoming : phi.getIncomings()) {
diff --git a/core/src/main/java/org/teavm/model/util/PhiUpdater.java b/core/src/main/java/org/teavm/model/util/PhiUpdater.java
index fb5e16b82..3a8d99323 100644
--- a/core/src/main/java/org/teavm/model/util/PhiUpdater.java
+++ b/core/src/main/java/org/teavm/model/util/PhiUpdater.java
@@ -27,12 +27,7 @@ import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collections;
 import java.util.Deque;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
 import org.teavm.common.DominatorTree;
 import org.teavm.common.Graph;
 import org.teavm.common.GraphUtils;
@@ -44,7 +39,6 @@ import org.teavm.model.InvokeDynamicInstruction;
 import org.teavm.model.Phi;
 import org.teavm.model.Program;
 import org.teavm.model.TryCatchBlock;
-import org.teavm.model.TryCatchJoint;
 import org.teavm.model.Variable;
 import org.teavm.model.instructions.ArrayLengthInstruction;
 import org.teavm.model.instructions.AssignInstruction;
@@ -92,21 +86,17 @@ public class PhiUpdater {
     private int[][] domFrontiers;
     private Variable[] variableMap;
     private boolean[] variableDefined;
+    private List<List<Variable>> definedVersions = new ArrayList<>();
     private BasicBlock currentBlock;
-    private TryCatchBlock currentTryCatch;
     private Phi[][] phiMap;
     private int[][] phiIndexMap;
-    private Map<TryCatchBlock, Map<Variable, TryCatchJoint>> jointMap = new HashMap<>();
     private List<List<Phi>> synthesizedPhisByBlock = new ArrayList<>();
     private IntObjectMap<Phi> phisByReceiver = new IntObjectOpenHashMap<>();
-    private IntObjectMap<TryCatchJoint> jointsByReceiver = new IntObjectOpenHashMap<>();
     private BitSet usedPhis = new BitSet();
-    private List<List<List<TryCatchJoint>>> synthesizedJointsByBlock = new ArrayList<>();
     private Variable[] originalExceptionVariables;
     private boolean[] usedDefinitions;
     private IntegerArray variableToSourceMap = new IntegerArray(10);
     private List<Phi> synthesizedPhis = new ArrayList<>();
-    private List<TryCatchJoint> synthesizedJoints = new ArrayList<>();
 
     public int getSourceVariable(int var) {
         if (var >= variableToSourceMap.size()) {
@@ -119,17 +109,12 @@ public class PhiUpdater {
         return synthesizedPhis;
     }
 
-    public List<TryCatchJoint> getSynthesizedJoints() {
-        return synthesizedJoints;
-    }
-
     public void updatePhis(Program program, Variable[] arguments) {
         if (program.basicBlockCount() == 0) {
             return;
         }
         this.program = program;
         phisByReceiver.clear();
-        jointsByReceiver.clear();
         cfg = ProgramUtils.buildControlFlowGraph(program);
         domTree = GraphUtils.buildDominatorTree(cfg);
         domFrontiers = new int[cfg.size()][];
@@ -141,12 +126,14 @@ public class PhiUpdater {
             variableMap[i] = arguments[i];
             usedDefinitions[i] = true;
         }
+
         for (int i = 0; i < program.variableCount(); ++i) {
             variableToSourceMap.add(-1);
         }
+        definedVersions.addAll(Collections.nCopies(program.variableCount(), null));
+
         phiMap = new Phi[program.basicBlockCount()][];
         phiIndexMap = new int[program.basicBlockCount()][];
-        jointMap.clear();
         for (int i = 0; i < phiMap.length; ++i) {
             phiMap[i] = new Phi[program.variableCount()];
             phiIndexMap[i] = new int[program.variableCount()];
@@ -154,15 +141,9 @@ public class PhiUpdater {
         domFrontiers = GraphUtils.findDominanceFrontiers(cfg, domTree);
 
         synthesizedPhisByBlock.clear();
-        synthesizedJointsByBlock.clear();
 
         for (int i = 0; i < program.basicBlockCount(); ++i) {
             synthesizedPhisByBlock.add(new ArrayList<>());
-            synthesizedJointsByBlock.add(new ArrayList<>());
-            int catchCount = program.basicBlockAt(i).getTryCatchBlocks().size();
-            for (int j = 0; j < catchCount; ++j) {
-                synthesizedJointsByBlock.get(i).add(new ArrayList<>());
-            }
         }
 
         originalExceptionVariables = new Variable[program.basicBlockCount()];
@@ -176,7 +157,6 @@ public class PhiUpdater {
 
     private void estimatePhis() {
         DefinitionExtractor definitionExtractor = new DefinitionExtractor();
-        List<List<TryCatchJoint>> inputJoints = getInputJoints(program);
         variableDefined = new boolean[program.variableCount()];
 
         IntDeque stack = new IntArrayDeque();
@@ -189,10 +169,6 @@ public class PhiUpdater {
                 markAssignment(currentBlock.getExceptionVariable());
             }
 
-            for (TryCatchJoint joint : inputJoints.get(currentBlock.getIndex())) {
-                markAssignment(joint.getReceiver());
-            }
-
             for (Phi phi : currentBlock.getPhis()) {
                 markAssignment(phi.getReceiver());
             }
@@ -200,20 +176,8 @@ public class PhiUpdater {
             for (Instruction insn : currentBlock) {
                 currentBlock = program.basicBlockAt(i);
                 insn.acceptVisitor(definitionExtractor);
-                Set<Variable> definedVariables = new HashSet<>();
                 for (Variable var : definitionExtractor.getDefinedVariables()) {
                     markAssignment(var);
-                    definedVariables.add(var);
-                }
-
-                Set<BasicBlock> handlers = currentBlock.getTryCatchBlocks().stream()
-                        .map(tryCatch -> tryCatch.getHandler())
-                        .collect(Collectors.toSet());
-                for (BasicBlock handler : handlers) {
-                    currentBlock = handler;
-                    for (Variable var : definedVariables) {
-                        markAssignment(var);
-                    }
                 }
             }
 
@@ -226,8 +190,6 @@ public class PhiUpdater {
     private static class Task {
         Variable[] variables;
         BasicBlock block;
-        TryCatchBlock tryCatch;
-        int tryCatchIndex;
     }
 
     private void renameVariables() {
@@ -244,69 +206,35 @@ public class PhiUpdater {
         List<List<Incoming>> phiOutputs = ProgramUtils.getPhiOutputs(program);
 
         while (!stack.isEmpty()) {
+            Collections.fill(definedVersions, null);
             Task task = stack.pop();
 
             currentBlock = task.block;
-            currentTryCatch = task.tryCatch;
             int index = currentBlock.getIndex();
             variableMap = task.variables.clone();
 
-            if (currentTryCatch == null) {
-                if (currentBlock.getExceptionVariable() != null) {
-                    currentBlock.setExceptionVariable(define(currentBlock.getExceptionVariable()));
-                }
-
-                for (Phi phi : synthesizedPhisByBlock.get(index)) {
-                    Variable var = program.createVariable();
-                    var.setDebugName(phi.getReceiver().getDebugName());
-                    var.setLabel(phi.getReceiver().getLabel());
-                    mapVariable(phi.getReceiver().getIndex(), var);
-                    phisByReceiver.put(var.getIndex(), phi);
-                    phi.setReceiver(var);
-                }
-                for (Phi phi : currentBlock.getPhis()) {
-                    phi.setReceiver(define(phi.getReceiver()));
-                }
-
-                for (Instruction insn : currentBlock) {
-                    insn.acceptVisitor(consumer);
-                }
-            } else {
-                for (TryCatchJoint joint : currentTryCatch.getJoints()) {
-                    joint.setReceiver(define(joint.getReceiver()));
-                }
+            if (currentBlock.getExceptionVariable() != null) {
+                currentBlock.setExceptionVariable(define(currentBlock.getExceptionVariable()));
             }
 
-            boolean tryCatchIsSuccessor = currentTryCatch != null
-                    && domTree.immediateDominatorOf(currentTryCatch.getHandler().getIndex()) == index;
-            if (currentTryCatch != null) {
-                for (TryCatchJoint joint : synthesizedJointsByBlock.get(index).get(task.tryCatchIndex)) {
-                    Variable var = program.createVariable();
-                    var.setDebugName(joint.getReceiver().getDebugName());
-                    var.setLabel(joint.getReceiver().getLabel());
-                    mapVariable(joint.getReceiver().getIndex(), var);
-                    joint.setReceiver(var);
-                    jointsByReceiver.put(var.getIndex(), joint);
-                }
+            for (Phi phi : synthesizedPhisByBlock.get(index)) {
+                Variable var = program.createVariable();
+                var.setDebugName(phi.getReceiver().getDebugName());
+                var.setLabel(phi.getReceiver().getLabel());
+                mapVariable(phi.getReceiver().getIndex(), var);
+                phisByReceiver.put(var.getIndex(), phi);
+                phi.setReceiver(var);
+            }
+            for (Phi phi : currentBlock.getPhis()) {
+                phi.setReceiver(define(phi.getReceiver()));
             }
 
-            int[] successors;
-            List<TryCatchBlock> tryCatchBlockSuccessors = new ArrayList<>();
-            IntSet tryCatchSuccessors = new IntOpenHashSet();
-            if (currentTryCatch != null) {
-                successors = tryCatchIsSuccessor ? new int[] { currentTryCatch.getHandler().getIndex() } : new int[0];
-            } else {
-                List<TryCatchBlock> tryCatchBlocks = currentBlock.getTryCatchBlocks();
-                for (int i = 0; i < tryCatchBlocks.size(); i++) {
-                    TryCatchBlock tryCatch = tryCatchBlocks.get(i);
-                    tryCatchSuccessors.add(tryCatch.getHandler().getIndex());
-                    tryCatchBlockSuccessors.add(tryCatch);
-                }
-                successors = Arrays.stream(domGraph.outgoingEdges(index))
-                        .filter(successor -> !tryCatchSuccessors.contains(successor))
-                        .toArray();
+            for (Instruction insn : currentBlock) {
+                insn.acceptVisitor(consumer);
             }
 
+            int[] successors = domGraph.outgoingEdges(index);
+
             IntSet successorSet = IntOpenHashSet.from(successors);
             for (Incoming output : phiOutputs.get(index)) {
                 if (successorSet.contains(output.getPhi().getBasicBlock().getIndex())) {
@@ -314,13 +242,6 @@ public class PhiUpdater {
                     output.setValue(use(var));
                 }
             }
-            if (tryCatchIsSuccessor) {
-                for (TryCatchJoint joint : currentTryCatch.getJoints()) {
-                    for (int i = 0; i < joint.getSourceVariables().size(); ++i) {
-                        joint.getSourceVariables().set(i, use(joint.getSourceVariables().get(i)));
-                    }
-                }
-            }
 
             for (int j = successors.length - 1; j >= 0; --j) {
                 int successor = successors[j];
@@ -330,24 +251,13 @@ public class PhiUpdater {
                 stack.push(next);
             }
 
-            for (int j = tryCatchBlockSuccessors.size() - 1; j >= 0; --j) {
-                TryCatchBlock tryCatch = tryCatchBlockSuccessors.get(j);
-                Task next = new Task();
-                next.variables = variableMap.clone();
-                next.block = currentBlock;
-                next.tryCatch = tryCatch;
-                next.tryCatchIndex = j;
-                stack.push(next);
+            IntSet exceptionHandlingSuccessors = new IntOpenHashSet();
+            for (TryCatchBlock tryCatch : currentBlock.getTryCatchBlocks()) {
+                exceptionHandlingSuccessors.add(tryCatch.getHandler().getIndex());
             }
 
-            if (currentTryCatch == null) {
-                for (int successor : cfg.outgoingEdges(index)) {
-                    if (!tryCatchSuccessors.contains(successor)) {
-                        renameOutgoingPhis(successor);
-                    }
-                }
-            } else {
-                renameOutgoingPhis(currentTryCatch.getHandler().getIndex());
+            for (int successor : cfg.outgoingEdges(index)) {
+                renameOutgoingPhis(successor, exceptionHandlingSuccessors.contains(successor));
             }
         }
     }
@@ -363,21 +273,6 @@ public class PhiUpdater {
                     synthesizedPhis.add(phi);
                 }
             }
-
-            List<List<TryCatchJoint>> joints = synthesizedJointsByBlock.get(i);
-            for (int j = 0; j < joints.size(); ++j) {
-                List<TryCatchJoint> jointList = joints.get(j);
-                TryCatchBlock targetTryCatch = program.basicBlockAt(i).getTryCatchBlocks().get(j);
-                for (TryCatchJoint joint : jointList) {
-                    if (!usedPhis.get(joint.getReceiver().getIndex())) {
-                        continue;
-                    }
-                    if (!joint.getSourceVariables().isEmpty()) {
-                        targetTryCatch.getJoints().add(joint);
-                        synthesizedJoints.add(joint);
-                    }
-                }
-            }
         }
     }
 
@@ -388,11 +283,6 @@ public class PhiUpdater {
                 worklist.addLast(receiverIndex);
             }
         }
-        for (int receiverIndex : jointsByReceiver.keys().toArray()) {
-            if (usedPhis.get(receiverIndex)) {
-                worklist.addLast(receiverIndex);
-            }
-        }
 
         IntSet visited = new IntOpenHashSet();
         while (!worklist.isEmpty()) {
@@ -410,19 +300,10 @@ public class PhiUpdater {
                     }
                 }
             }
-
-            TryCatchJoint joint = jointsByReceiver.get(varIndex);
-            if (joint != null) {
-                for (Variable sourceVar : joint.getSourceVariables()) {
-                    if (!visited.contains(sourceVar.getIndex())) {
-                        worklist.addLast(sourceVar.getIndex());
-                    }
-                }
-            }
         }
     }
 
-    private void renameOutgoingPhis(int successor) {
+    private void renameOutgoingPhis(int successor, boolean allVersions) {
         int[] phiIndexes = phiIndexMap[successor];
         List<Phi> phis = synthesizedPhisByBlock.get(successor);
 
@@ -430,6 +311,16 @@ public class PhiUpdater {
             Phi phi = phis.get(j);
             Variable var = variableMap[phiIndexes[j]];
             if (var != null) {
+                List<Variable> versions = definedVersions.get(phiIndexes[j]);
+                if (versions != null && allVersions) {
+                    for (Variable version : versions) {
+                        Incoming incoming = new Incoming();
+                        incoming.setSource(currentBlock);
+                        incoming.setValue(version);
+                        phi.getIncomings().add(incoming);
+                    }
+                }
+
                 Incoming incoming = new Incoming();
                 incoming.setSource(currentBlock);
                 incoming.setValue(var);
@@ -440,91 +331,66 @@ public class PhiUpdater {
     }
 
     private void markAssignment(Variable var) {
-        BasicBlock[] worklist = new BasicBlock[program.basicBlockCount() * 4];
-        int head = 0;
-        worklist[head++] = currentBlock;
+        Deque<BasicBlock> worklist = new ArrayDeque<>();
+        worklist.push(currentBlock);
 
         if (variableDefined[var.getIndex()]) {
-            BasicBlock startBlock = currentBlock;
-            List<TryCatchBlock> tryCatchBlocks = startBlock.getTryCatchBlocks();
-            for (int i = 0; i < tryCatchBlocks.size(); i++) {
-                TryCatchBlock tryCatch = tryCatchBlocks.get(i);
-                TryCatchJoint joint = jointMap.computeIfAbsent(tryCatch, k -> new HashMap<>()).get(var);
-                if (joint == null) {
-                    joint = new TryCatchJoint();
-                    joint.setReceiver(var);
-                    synthesizedJointsByBlock.get(startBlock.getIndex()).get(i).add(joint);
-                    jointMap.get(tryCatch).put(var, joint);
-                    worklist[head++] = tryCatch.getHandler();
-                }
+            for (TryCatchBlock tryCatch : currentBlock.getTryCatchBlocks()) {
+                placePhi(tryCatch.getHandler().getIndex(), var, currentBlock, worklist);
             }
         } else {
             variableDefined[var.getIndex()] = true;
         }
 
-        while (head > 0) {
-            BasicBlock block = worklist[--head];
+        while (!worklist.isEmpty()) {
+            BasicBlock block = worklist.pop();
             int[] frontiers = domFrontiers[block.getIndex()];
 
             if (frontiers != null) {
                 for (int frontier : frontiers) {
-                    BasicBlock frontierBlock = program.basicBlockAt(frontier);
-                    if (frontierBlock.getExceptionVariable() == var) {
-                        continue;
-                    }
-
-                    boolean exists = frontierBlock.getPhis().stream()
-                            .flatMap(phi -> phi.getIncomings().stream())
-                            .anyMatch(incoming -> incoming.getSource() == block && incoming.getValue() == var);
-                    if (exists) {
-                        continue;
-                    }
-
-                    Phi phi = phiMap[frontier][var.getIndex()];
-                    if (phi == null) {
-                        phi = new Phi();
-                        phi.setReceiver(var);
-                        phiIndexMap[frontier][synthesizedPhisByBlock.get(frontier).size()] = var.getIndex();
-                        synthesizedPhisByBlock.get(frontier).add(phi);
-                        phiMap[frontier][var.getIndex()] = phi;
-                        worklist[head++] = frontierBlock;
-                    }
+                    placePhi(frontier, var, block, worklist);
                 }
             }
         }
     }
 
-    private Variable define(Variable var) {
-        Variable old = variableMap[var.getIndex()];
-        Variable original = var;
-        var = introduce(var);
-        propagateToTryCatch(original, var, old);
-        mapVariable(original.getIndex(), var);
-        return var;
+    private void placePhi(int frontier, Variable var, BasicBlock block, Deque<BasicBlock> worklist) {
+        BasicBlock frontierBlock = program.basicBlockAt(frontier);
+        if (frontierBlock.getExceptionVariable() == var) {
+            return;
+        }
+
+        boolean exists = frontierBlock.getPhis().stream()
+                .flatMap(phi -> phi.getIncomings().stream())
+                .anyMatch(incoming -> incoming.getSource() == block && incoming.getValue() == var);
+        if (exists) {
+            return;
+        }
+
+        Phi phi = phiMap[frontier][var.getIndex()];
+        if (phi == null) {
+            phi = new Phi();
+            phi.setReceiver(var);
+            phiIndexMap[frontier][synthesizedPhisByBlock.get(frontier).size()] = var.getIndex();
+            synthesizedPhisByBlock.get(frontier).add(phi);
+            phiMap[frontier][var.getIndex()] = phi;
+            worklist.push(frontierBlock);
+        }
     }
 
-    private void propagateToTryCatch(Variable original, Variable var, Variable old) {
-        for (int i = 0; i < currentBlock.getTryCatchBlocks().size(); ++i) {
-            TryCatchBlock tryCatch = currentBlock.getTryCatchBlocks().get(i);
-            if (originalExceptionVariables[tryCatch.getHandler().getIndex()] == original) {
-                continue;
+    private Variable define(Variable var) {
+        Variable old = variableMap[var.getIndex()];
+        if (old != null) {
+            if (definedVersions.get(var.getIndex()) == null) {
+                definedVersions.set(var.getIndex(), new ArrayList<>());
             }
-
-            Map<Variable, TryCatchJoint> joints = jointMap.get(tryCatch);
-            if (joints == null) {
-                continue;
-            }
-
-            TryCatchJoint joint = joints.get(original);
-            if (joint == null) {
-                continue;
-            }
-
-            if (joint.getSourceVariables().isEmpty() && old != null) {
-                joint.getSourceVariables().add(old);
-            }
-            joint.getSourceVariables().add(var);
+            definedVersions.get(var.getIndex()).add(old);
         }
+
+        Variable original = var;
+        var = introduce(var);
+        mapVariable(original.getIndex(), var);
+        return var;
     }
 
     private void mapVariable(int index, Variable var) {
@@ -551,25 +417,13 @@ public class PhiUpdater {
     private Variable use(Variable var) {
         Variable mappedVar = variableMap[var.getIndex()];
         if (mappedVar == null) {
-            throw new AssertionError("Variable used before definition: " + var.getDisplayLabel());
+            throw new AssertionError("Variable used before definition: @" + var.getDisplayLabel()
+                    + " at $" + currentBlock.getIndex());
         }
         usedPhis.set(mappedVar.getIndex());
         return mappedVar;
     }
 
-    private static List<List<TryCatchJoint>> getInputJoints(Program program) {
-        List<List<TryCatchJoint>> inputJoints = new ArrayList<>(Collections.nCopies(program.basicBlockCount(), null));
-        for (int i = 0; i < program.basicBlockCount(); ++i) {
-            inputJoints.set(i, new ArrayList<>());
-        }
-        for (BasicBlock block : program.getBasicBlocks()) {
-            for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) {
-                inputJoints.get(tryCatch.getHandler().getIndex()).addAll(tryCatch.getJoints());
-            }
-        }
-        return inputJoints;
-    }
-
     private InstructionVisitor consumer = new InstructionVisitor() {
         @Override
         public void visit(EmptyInstruction insn) {
diff --git a/core/src/main/java/org/teavm/model/util/ProgramUtils.java b/core/src/main/java/org/teavm/model/util/ProgramUtils.java
index 8e54a0c2d..d7bc95c30 100644
--- a/core/src/main/java/org/teavm/model/util/ProgramUtils.java
+++ b/core/src/main/java/org/teavm/model/util/ProgramUtils.java
@@ -36,10 +36,7 @@ import org.teavm.model.ProgramReader;
 import org.teavm.model.TextLocation;
 import org.teavm.model.TryCatchBlock;
 import org.teavm.model.TryCatchBlockReader;
-import org.teavm.model.TryCatchJoint;
-import org.teavm.model.TryCatchJointReader;
 import org.teavm.model.Variable;
-import org.teavm.model.VariableReader;
 
 public final class ProgramUtils {
     private ProgramUtils() {
@@ -110,6 +107,7 @@ public final class ProgramUtils {
         InstructionReadVisitor visitor = new InstructionReadVisitor(copyReader);
         while (from != to) {
             from.acceptVisitor(visitor);
+            copyReader.getCopy().setLocation(from.getLocation());
             result.add(copyReader.getCopy());
             from = from.getNext();
         }
@@ -138,25 +136,11 @@ public final class ProgramUtils {
             TryCatchBlock tryCatchCopy = new TryCatchBlock();
             tryCatchCopy.setExceptionType(tryCatch.getExceptionType());
             tryCatchCopy.setHandler(target.basicBlockAt(tryCatch.getHandler().getIndex()));
-            tryCatchCopy.getJoints().addAll(copyTryCatchJoints(tryCatch, target));
             result.add(tryCatchCopy);
         }
         return result;
     }
 
-    public static List<TryCatchJoint> copyTryCatchJoints(TryCatchBlockReader block, Program target) {
-        List<TryCatchJoint> result = new ArrayList<>();
-        for (TryCatchJointReader joint : block.readJoints()) {
-            TryCatchJoint jointCopy = new TryCatchJoint();
-            jointCopy.setReceiver(target.variableAt(joint.getReceiver().getIndex()));
-            for (VariableReader sourceVar : joint.readSourceVariables()) {
-                jointCopy.getSourceVariables().add(target.variableAt(sourceVar.getIndex()));
-            }
-            result.add(jointCopy);
-        }
-        return result;
-    }
-
     public static List<List<Incoming>> getPhiOutputs(Program program) {
         List<List<Incoming>> outputs = new ArrayList<>(program.basicBlockCount());
         for (int i = 0; i < program.basicBlockCount(); ++i) {
@@ -175,23 +159,6 @@ public final class ProgramUtils {
         return outputs;
     }
 
-    public static List<List<Incoming>> getPhiOutputsByVariable(Program program) {
-        List<List<Incoming>> outputs = new ArrayList<>(program.variableCount());
-        for (int i = 0; i < program.variableCount(); ++i) {
-            outputs.add(new ArrayList<>());
-        }
-
-        for (BasicBlock block : program.getBasicBlocks()) {
-            for (Phi phi : block.getPhis()) {
-                for (Incoming incoming : phi.getIncomings()) {
-                    outputs.get(incoming.getValue().getIndex()).add(incoming);
-                }
-            }
-        }
-
-        return outputs;
-    }
-
     public static BasicBlock[] getVariableDefinitionPlaces(Program program) {
         BasicBlock[] places = new BasicBlock[program.variableCount()];
         DefinitionExtractor defExtractor = new DefinitionExtractor();
@@ -213,12 +180,6 @@ public final class ProgramUtils {
                     places[var.getIndex()] = block;
                 }
             }
-
-            for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) {
-                for (TryCatchJoint joint : tryCatch.getJoints()) {
-                    places[joint.getReceiver().getIndex()] = block;
-                }
-            }
         }
         return places;
     }
diff --git a/core/src/main/java/org/teavm/model/util/RegisterAllocator.java b/core/src/main/java/org/teavm/model/util/RegisterAllocator.java
index ca4a4ddf4..4126615d7 100644
--- a/core/src/main/java/org/teavm/model/util/RegisterAllocator.java
+++ b/core/src/main/java/org/teavm/model/util/RegisterAllocator.java
@@ -18,10 +18,8 @@ package org.teavm.model.util;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import org.teavm.common.DisjointSet;
 import org.teavm.common.MutableGraphEdge;
 import org.teavm.common.MutableGraphNode;
@@ -33,15 +31,12 @@ import org.teavm.model.MethodReference;
 import org.teavm.model.Phi;
 import org.teavm.model.Program;
 import org.teavm.model.ProgramReader;
-import org.teavm.model.TryCatchBlock;
-import org.teavm.model.TryCatchJoint;
 import org.teavm.model.Variable;
 import org.teavm.model.instructions.AssignInstruction;
 import org.teavm.model.instructions.JumpInstruction;
 
 public class RegisterAllocator {
     public void allocateRegisters(MethodReader method, Program program) {
-        insertJointArgumentsCopies(program);
         insertPhiArgumentsCopies(program);
         InterferenceGraphBuilder interferenceBuilder = new InterferenceGraphBuilder();
         LivenessAnalyzer liveness = new LivenessAnalyzer();
@@ -91,9 +86,6 @@ public class RegisterAllocator {
 
         for (int i = 0; i < program.basicBlockCount(); ++i) {
             program.basicBlockAt(i).getPhis().clear();
-            for (TryCatchBlock tryCatch : program.basicBlockAt(i).getTryCatchBlocks()) {
-                tryCatch.getJoints().clear();
-            }
         }
     }
 
@@ -137,42 +129,6 @@ public class RegisterAllocator {
         }
     }
 
-    private void insertJointArgumentsCopies(Program program) {
-        for (int i = 0; i < program.basicBlockCount(); ++i) {
-            BasicBlock block = program.basicBlockAt(i);
-            for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) {
-                tryCatch.getJoints().forEach(this::insertCopy);
-            }
-        }
-    }
-
-    private void insertCopy(TryCatchJoint joint) {
-        Set<Variable> variableSet = new HashSet<>(joint.getSourceVariables());
-
-        BasicBlock block = joint.getBlock().getProtectedBlock();
-        DefinitionExtractor defExtractor = new DefinitionExtractor();
-        Instruction nextInsn;
-        for (Instruction insn = block.getFirstInstruction(); insn != null; insn = nextInsn) {
-            nextInsn = insn.getNext();
-            insn.acceptVisitor(defExtractor);
-            for (Variable definedVar : defExtractor.getDefinedVariables()) {
-                if (variableSet.remove(definedVar)) {
-                    AssignInstruction copyInsn = new AssignInstruction();
-                    copyInsn.setReceiver(joint.getReceiver());
-                    copyInsn.setAssignee(definedVar);
-                    insn.insertNext(copyInsn);
-                }
-            }
-        }
-
-        for (Variable enteringVar : variableSet) {
-            AssignInstruction copyInsn = new AssignInstruction();
-            copyInsn.setReceiver(joint.getReceiver());
-            copyInsn.setAssignee(enteringVar);
-            block.addFirst(copyInsn);
-        }
-    }
-
     private void insertPhiArgumentsCopies(Program program) {
         for (int i = 0; i < program.basicBlockCount(); ++i) {
             Map<BasicBlock, BasicBlock> blockMap = new HashMap<>();
@@ -321,13 +277,6 @@ public class RegisterAllocator {
                     classes.union(phi.getReceiver().getIndex(), incoming.getValue().getIndex());
                 }
             }
-            for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) {
-                for (TryCatchJoint joint : tryCatch.getJoints()) {
-                    for (Variable sourceVar : joint.getSourceVariables()) {
-                        classes.union(sourceVar.getIndex(), joint.getReceiver().getIndex());
-                    }
-                }
-            }
         }
         return classes;
     }
diff --git a/core/src/main/java/org/teavm/model/util/TypeInferer.java b/core/src/main/java/org/teavm/model/util/TypeInferer.java
index 36db9b292..0c97a6803 100644
--- a/core/src/main/java/org/teavm/model/util/TypeInferer.java
+++ b/core/src/main/java/org/teavm/model/util/TypeInferer.java
@@ -52,14 +52,6 @@ public class TypeInferer {
                     builder.addEdge(incoming.getValue().getIndex(), phi.getReceiver().getIndex());
                 }
             }
-
-            for (TryCatchBlockReader tryCatch : block.readTryCatchBlocks()) {
-                for (TryCatchJointReader joint : tryCatch.readJoints()) {
-                    for (VariableReader sourceVar : joint.readSourceVariables()) {
-                        builder.addEdge(sourceVar.getIndex(), joint.getReceiver().getIndex());
-                    }
-                }
-            }
         }
 
         IntegerStack stack = new IntegerStack(sz);
diff --git a/core/src/test/resources/model/analysis/nullness/tryCatchJoint.extended.txt b/core/src/test/resources/model/analysis/nullness/tryCatchJoint.extended.txt
index a8bad3c1e..68a7faa48 100644
--- a/core/src/test/resources/model/analysis/nullness/tryCatchJoint.extended.txt
+++ b/core/src/test/resources/model/analysis/nullness/tryCatchJoint.extended.txt
@@ -8,9 +8,9 @@ $if0
     invokeVirtual `Bar.baz()LBar;` @bar
     @bar_1 := nullCheck @bar
     goto $join
-catch java.lang.RuntimeException goto $if0Handler
-    @bar_2 := ephi @bar, @bar_1
+    catch java.lang.RuntimeException goto $if0Handler
 $if0Handler
+    @bar_2 := phi @bar from $if0, @bar_1 from $if0
     goto $join
 $else
     invokeVirtual `Bar.baz()LBar;` @bar
@@ -19,7 +19,7 @@ $else
 $else1
     invokeVirtual `Bar.baz2()LBar;` @bar_3
     goto $join
-catch java.lang.RuntimeException goto $elseHandler
+    catch java.lang.RuntimeException goto $elseHandler
 $elseHandler
     goto $join
 $join
diff --git a/core/src/test/resources/model/analysis/nullness/tryCatchJoint.original.txt b/core/src/test/resources/model/analysis/nullness/tryCatchJoint.original.txt
index 20a724067..c445b0701 100644
--- a/core/src/test/resources/model/analysis/nullness/tryCatchJoint.original.txt
+++ b/core/src/test/resources/model/analysis/nullness/tryCatchJoint.original.txt
@@ -7,7 +7,7 @@ $start
 $if0
     invokeVirtual `Bar.baz()LBar;` @bar
     goto $join
-catch java.lang.RuntimeException goto $if0Handler
+    catch java.lang.RuntimeException goto $if0Handler
 $if0Handler
     goto $join
 $else
@@ -16,7 +16,7 @@ $else
 $else1
     invokeVirtual `Bar.baz2()LBar;` @bar
     goto $join
-catch java.lang.RuntimeException goto $elseHandler
+    catch java.lang.RuntimeException goto $elseHandler
 $elseHandler
     goto $join
 $join
diff --git a/core/src/test/resources/model/text/exceptions.txt b/core/src/test/resources/model/text/exceptions.txt
index 08112a485..4a2506737 100644
--- a/core/src/test/resources/model/text/exceptions.txt
+++ b/core/src/test/resources/model/text/exceptions.txt
@@ -5,9 +5,9 @@ $start
     invokeStatic `foo.Bar.baz()I`
     @a_3 := 2
     return @a_3
-catch java.lang.Exception goto $handler
-    @a_h := ephi @a_1, @a_2, @a_3
+    catch java.lang.Exception goto $handler
 $handler
+    @a_h := phi @a_1 from $start, @a_2 from $start, @a_3 from $start
     @e := exception
     @out := field `java.lang.String.out` as `Ljava/io/PrintStream;`
     invokeVirtual `java.io.PrintStream.println(Ljava/lang/Object;)V` @out, @e
diff --git a/core/src/test/resources/model/util/phi-updater/exceptionPhi.expected.txt b/core/src/test/resources/model/util/phi-updater/exceptionPhi.expected.txt
index 8b6075ec5..76c1ec367 100644
--- a/core/src/test/resources/model/util/phi-updater/exceptionPhi.expected.txt
+++ b/core/src/test/resources/model/util/phi-updater/exceptionPhi.expected.txt
@@ -3,8 +3,8 @@ $start
     @a_1 := invokeStatic `Foo.baz()I`
     goto $end
     catch java.lang.RuntimeException goto $catch
-        @a_2 := ephi @a, @a_1
 $catch
+    @a_2 := phi @a from $start, @a_1 from $start
     @b := 1
     @a_3 := @a_2 + @b as int
     goto $end
diff --git a/core/src/test/resources/model/util/phi-updater/exceptionPhiMultiple.expected.txt b/core/src/test/resources/model/util/phi-updater/exceptionPhiMultiple.expected.txt
index f0051945a..ff34589a1 100644
--- a/core/src/test/resources/model/util/phi-updater/exceptionPhiMultiple.expected.txt
+++ b/core/src/test/resources/model/util/phi-updater/exceptionPhiMultiple.expected.txt
@@ -3,17 +3,15 @@ $start
     @a_1 := invokeStatic `Foo.baz()I`
     goto $second
     catch java.lang.RuntimeException goto $catch
-        @a_2 := ephi @a, @a_1
 $second
-    @a_5 := invokeStatic `Foo.boo()I`
+    @a_2 := invokeStatic `Foo.boo()I`
     goto $end
     catch java.lang.RuntimeException goto $catch
-        @a_6 := ephi @a_1, @a_5
 $catch
-    @a_3 := phi @a_2 from $start, @a_6 from $second
+    @a_3 := phi @a from $start, @a_1 from $start, @a_1 from $second, @a_2 from $second
     @b := 1
     @a_4 := @a_3 + @b as int
     goto $end
 $end
-    @a_7 := phi @a_4 from $catch, @a_5 from $second
-    return @a_7
\ No newline at end of file
+    @a_5 := phi @a_2 from $second, @a_4 from $catch
+    return @a_5
\ No newline at end of file
diff --git a/core/src/test/resources/model/util/phi-updater/existingExceptionPhi.expected.txt b/core/src/test/resources/model/util/phi-updater/existingExceptionPhi.expected.txt
index b5225b749..ca72443eb 100644
--- a/core/src/test/resources/model/util/phi-updater/existingExceptionPhi.expected.txt
+++ b/core/src/test/resources/model/util/phi-updater/existingExceptionPhi.expected.txt
@@ -3,16 +3,14 @@ $start
     @a_1 := invokeStatic `Foo.baz()I`
     goto $second
     catch goto $catch
-        @a_2 := ephi @a, @a_1
 $second
-    @a_3 := invokeStatic `Foo.bar2()I`
-    @a_4 := invokeStatic `Foo.baz2()I`
+    @a_2 := invokeStatic `Foo.bar2()I`
+    @a_3 := invokeStatic `Foo.baz2()I`
     goto $end
     catch goto $catch
-        @a_5 := ephi @a_1, @a_3, @a_4
 $catch
-    @a_6 := phi @a_2 from $start, @a_5 from $second
+    @a_4 := phi @a from $start, @a_1 from $start, @a_2 from $second, @a_3 from $second
     goto $end
 $end
-    @a_7 := phi @a_4 from $second, @a_6 from $catch
-    return @a_7
+    @a_5 := phi @a_3 from $second, @a_4 from $catch
+    return @a_5
diff --git a/core/src/test/resources/model/util/phi-updater/existingExceptionPhi.original.txt b/core/src/test/resources/model/util/phi-updater/existingExceptionPhi.original.txt
index b5225b749..ca72443eb 100644
--- a/core/src/test/resources/model/util/phi-updater/existingExceptionPhi.original.txt
+++ b/core/src/test/resources/model/util/phi-updater/existingExceptionPhi.original.txt
@@ -3,16 +3,14 @@ $start
     @a_1 := invokeStatic `Foo.baz()I`
     goto $second
     catch goto $catch
-        @a_2 := ephi @a, @a_1
 $second
-    @a_3 := invokeStatic `Foo.bar2()I`
-    @a_4 := invokeStatic `Foo.baz2()I`
+    @a_2 := invokeStatic `Foo.bar2()I`
+    @a_3 := invokeStatic `Foo.baz2()I`
     goto $end
     catch goto $catch
-        @a_5 := ephi @a_1, @a_3, @a_4
 $catch
-    @a_6 := phi @a_2 from $start, @a_5 from $second
+    @a_4 := phi @a from $start, @a_1 from $start, @a_2 from $second, @a_3 from $second
     goto $end
 $end
-    @a_7 := phi @a_4 from $second, @a_6 from $catch
-    return @a_7
+    @a_5 := phi @a_3 from $second, @a_4 from $catch
+    return @a_5
diff --git a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/AliasFinder.java b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/AliasFinder.java
index 13e87a876..7d0650af1 100644
--- a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/AliasFinder.java
+++ b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/AliasFinder.java
@@ -22,8 +22,6 @@ import org.teavm.common.DisjointSet;
 import org.teavm.model.BasicBlockReader;
 import org.teavm.model.PhiReader;
 import org.teavm.model.ProgramReader;
-import org.teavm.model.TryCatchBlockReader;
-import org.teavm.model.TryCatchJointReader;
 import org.teavm.model.ValueType;
 import org.teavm.model.VariableReader;
 import org.teavm.model.instructions.AbstractInstructionReader;
@@ -51,16 +49,6 @@ class AliasFinder {
                     set.union(inputs.iterator().next(), phi.getReceiver().getIndex());
                 }
             }
-            for (TryCatchBlockReader tryCatch : block.readTryCatchBlocks()) {
-                for (TryCatchJointReader joint : tryCatch.readJoints()) {
-                    Set<Integer> inputs = joint.readSourceVariables().stream()
-                            .map(sourceVar -> sourceVar.getIndex())
-                            .collect(Collectors.toSet());
-                    if (inputs.size() == 1) {
-                        set.union(inputs.iterator().next(), joint.getReceiver().getIndex());
-                    }
-                }
-            }
         }
 
         int[] map = new int[set.size()];
diff --git a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/optimization/BoxingElimination.java b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/optimization/BoxingElimination.java
index ccb21bb16..d86e41924 100644
--- a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/optimization/BoxingElimination.java
+++ b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/optimization/BoxingElimination.java
@@ -29,8 +29,6 @@ import org.teavm.model.MethodReference;
 import org.teavm.model.Phi;
 import org.teavm.model.PrimitiveType;
 import org.teavm.model.Program;
-import org.teavm.model.TryCatchBlock;
-import org.teavm.model.TryCatchJoint;
 import org.teavm.model.ValueType;
 import org.teavm.model.Variable;
 import org.teavm.model.instructions.AssignInstruction;
@@ -112,13 +110,6 @@ public class BoxingElimination {
                     union(phi.getReceiver().getIndex(), incoming.getValue().getIndex());
                 }
             }
-            for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) {
-                for (TryCatchJoint joint : tryCatch.getJoints()) {
-                    for (Variable sourceVar : joint.getSourceVariables()) {
-                        union(sourceVar.getIndex(), joint.getReceiver().getIndex());
-                    }
-                }
-            }
         }
     }